package approvalapp

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/service/role/execution"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// RoleManagementConsumer is the transport-neutral Role Approval service
// surface. The adapter is intentionally not registered until the hidden MeshX
// preflight transport can be wired fail-closed in production.
type RoleManagementConsumer interface {
	Submit(context.Context, execution.SubmitCommand) (execution.RequestResult, error)
	LookupReplay(context.Context, execution.SubmitCommand) (execution.RequestResult, bool, error)
	Decide(context.Context, approval.DecideParams) (execution.RequestResult, error)
}

type roleManagementSubjectHandler struct {
	approvals *approval.Store
	service   RoleManagementConsumer
	projector *Projector
}

const roleManagementCardStatusProcessing = "processing"

// NewRoleManagementSubjectHandler returns an unregistered thin adapter.
func NewRoleManagementSubjectHandler(
	approvals *approval.Store,
	service RoleManagementConsumer,
	projector *Projector,
) SubjectHandler {
	return &roleManagementSubjectHandler{
		approvals: approvals,
		service:   service,
		projector: projector,
	}
}

func (*roleManagementSubjectHandler) SubjectType() string {
	return execution.SubjectType
}

func (*roleManagementSubjectHandler) RequestType() string {
	return execution.RequestType
}

func (*roleManagementSubjectHandler) CancelPolicy() SubjectCancelPolicy {
	return SubjectCancelNotSupported
}

func (*roleManagementSubjectHandler) ReconcilePolicy() SubjectReconcilePolicy {
	return SubjectReconcileNone
}

func (handler *roleManagementSubjectHandler) Validate(
	_ context.Context,
	instance approval.Instance,
) error {
	if handler == nil || handler.approvals == nil ||
		handler.service == nil || handler.projector == nil {
		return fmt.Errorf("Role management Approval subject handler is not configured")
	}
	if err := execution.ValidateApprovalInstance(instance); err != nil {
		return fmt.Errorf("Role management Approval subject is invalid: %w", err)
	}
	return nil
}

func (*roleManagementSubjectHandler) DecodeSubmitPayload(value any) (any, error) {
	switch value := value.(type) {
	case execution.RoleProposal:
		return value, nil
	case json.RawMessage:
		var request struct {
			Action string                        `json:"action"`
			Create *execution.RoleCreateProposal `json:"create,omitempty"`
			Update *execution.RoleUpdateProposal `json:"update,omitempty"`
			Delete *execution.RoleDeleteProposal `json:"delete,omitempty"`
			Apply  *execution.RoleApplyProposal  `json:"apply,omitempty"`
		}
		decoder := json.NewDecoder(strings.NewReader(string(value)))
		decoder.DisallowUnknownFields()
		if err := decoder.Decode(&request); err != nil {
			return nil, storeerr.BadRequest("Role management payload is invalid")
		}
		var trailing any
		if err := decoder.Decode(&trailing); err != io.EOF {
			return nil, storeerr.BadRequest("Role management payload is invalid")
		}
		var proposal execution.RoleProposal
		switch execution.RoleRequestAction(strings.TrimSpace(request.Action)) {
		case execution.ActionCreate:
			if request.Create == nil || request.Update != nil ||
				request.Delete != nil || request.Apply != nil {
				return nil, storeerr.BadRequest("Role management payload is invalid")
			}
			proposal = *request.Create
		case execution.ActionUpdate:
			if request.Update == nil || request.Create != nil ||
				request.Delete != nil || request.Apply != nil {
				return nil, storeerr.BadRequest("Role management payload is invalid")
			}
			proposal = *request.Update
		case execution.ActionDelete:
			if request.Delete == nil || request.Create != nil ||
				request.Update != nil || request.Apply != nil {
				return nil, storeerr.BadRequest("Role management payload is invalid")
			}
			proposal = *request.Delete
		case execution.ActionApply:
			if request.Apply == nil || request.Create != nil ||
				request.Update != nil || request.Delete != nil {
				return nil, storeerr.BadRequest("Role management payload is invalid")
			}
			proposal = *request.Apply
		default:
			return nil, storeerr.BadRequest("Role management payload is invalid")
		}
		if _, err := execution.NormalizeRoleRequest(execution.RoleRequest{
			WorkspaceID:      "decode",
			ApplicantAgentID: "decode",
			RequestID:        "decode",
			Proposal:         proposal,
		}); err != nil {
			return nil, storeerr.BadRequest("Role management payload is invalid")
		}
		return proposal, nil
	default:
		return nil, storeerr.BadRequest("Role management payload is invalid")
	}
}

func (handler *roleManagementSubjectHandler) Submit(
	ctx context.Context,
	command SubmitCommand,
) (SubmitResult, error) {
	roleCommand, err := roleManagementSubmitCommand(command)
	if err != nil {
		return SubmitResult{}, err
	}
	result, err := handler.service.Submit(ctx, roleCommand)
	if err != nil {
		return SubmitResult{}, err
	}
	return handler.submitResult(ctx, roleCommand.Principal.WorkspaceID, result)
}

func (handler *roleManagementSubjectHandler) LookupReplay(
	ctx context.Context,
	command SubmitCommand,
) (SubmitResult, bool, error) {
	roleCommand, err := roleManagementSubmitCommand(command)
	if err != nil {
		return SubmitResult{}, false, err
	}
	result, found, err := handler.service.LookupReplay(ctx, roleCommand)
	if err != nil || !found {
		return SubmitResult{}, found, err
	}
	submitted, err := handler.submitResult(
		ctx, roleCommand.Principal.WorkspaceID, result,
	)
	return submitted, err == nil, err
}

func (handler *roleManagementSubjectHandler) Decide(
	ctx context.Context,
	command DecisionCommand,
) (DecisionResult, error) {
	result, err := handler.service.Decide(ctx, approval.DecideParams{
		WorkspaceID:      command.WorkspaceID,
		ApprovalID:       command.ApprovalID,
		AssignmentID:     command.AssignmentID,
		ActorMemberID:    command.ActorMemberID,
		ExpectedVersion:  command.ExpectedVersion,
		ClientDecisionID: command.ClientDecisionID,
		Decision:         command.Decision,
		Reason:           command.Reason,
		Now:              command.Now,
	})
	if err != nil {
		return DecisionResult{}, err
	}
	if handler.approvals == nil {
		return DecisionResult{}, fmt.Errorf("Role management Approval store is not configured")
	}
	instance, err := handler.approvals.Load(ctx, command.WorkspaceID, command.ApprovalID)
	if err != nil {
		return DecisionResult{}, err
	}
	return DecisionResult{
		Instance: instance,
		Value:    result,
		Result: map[string]any{
			"approvalId": result.ApprovalID,
			"status":     result.Status,
		},
	}, nil
}

func (handler *roleManagementSubjectHandler) PresentPending(
	ctx context.Context,
	result SubmitResult,
) (app.AppNotificationResult, error) {
	if handler == nil || handler.projector == nil ||
		handler.projector.notifications == nil || handler.projector.provisioner == nil {
		return app.AppNotificationResult{}, fmt.Errorf("Role management Approval projector is not configured")
	}
	instance := result.Instance
	if instance.SubjectType != execution.SubjectType ||
		instance.State != approval.StatePending || instance.Version <= 0 ||
		len(result.Assignments) != 1 {
		return app.AppNotificationResult{}, fmt.Errorf("Role management Approval is not pending")
	}
	assignment := result.Assignments[0]
	if assignment.WorkspaceID != instance.WorkspaceID ||
		assignment.ApprovalInstanceID != instance.ID ||
		assignment.ApproverMemberID == "" ||
		assignment.Status != approval.AssignmentPending {
		return app.AppNotificationResult{}, fmt.Errorf("Role management Approval assignment is invalid")
	}
	payload, err := decodeRoleManagementPayload(instance)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	if _, err := handler.projector.provisioner.EnsureWorkspace(
		ctx, instance.WorkspaceID, time.Now().UTC(),
	); err != nil {
		return app.AppNotificationResult{}, err
	}
	content, err := roleManagementCardContent(
		instance, assignment.ID, assignment.ApproverMemberID,
		payload, approval.StatePending,
	)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	return handler.projector.notifications.CreateAppNotificationForBuiltin(
		ctx, IdentityID, instance.WorkspaceID, app.AppNotificationCommand{
			ClientNotificationID: roleManagementReviewClientNotificationID(instance.ID),
			Target: app.AppNotificationTarget{
				Kind: app.AppNotificationTargetHumanDM, MemberID: assignment.ApproverMemberID,
			},
			Content: content,
			BusinessObject: app.AppNotificationBusinessObject{
				Namespace: BusinessNamespace, Type: BusinessTypeInstance,
				ID: instance.ID, Version: instance.Version,
			},
			RecipientBasis: app.AppNotificationRecipientBasis{
				Kind: RecipientBasisAssignment, Ref: assignment.ID,
			},
			CausedByActor: app.AppNotificationActorRef{
				Type: "agent", ID: payload.ApplicantAgentID,
			},
			CorrelationID: instance.CorrelationID,
		},
	)
}

func (handler *roleManagementSubjectHandler) PresentResolved(
	ctx context.Context,
	result DecisionResult,
	presentation PresentationContext,
) (app.AppNotificationResult, error) {
	if handler == nil || handler.projector == nil ||
		handler.projector.notifications == nil || handler.projector.provisioner == nil ||
		handler.approvals == nil {
		return app.AppNotificationResult{}, fmt.Errorf("Role management Approval projector is not configured")
	}
	instance := result.Instance
	if instance.SubjectType != execution.SubjectType ||
		(instance.State != approval.StateApproved && instance.State != approval.StateRejected) ||
		instance.Version <= 1 {
		return app.AppNotificationResult{}, fmt.Errorf("Role management resolved Approval is invalid")
	}
	notificationID := strings.TrimSpace(presentation.NotificationID)
	if notificationID == "" {
		return app.AppNotificationResult{}, fmt.Errorf("Role management action notification is required")
	}
	provisioned, err := handler.projector.provisioner.EnsureWorkspace(
		ctx, instance.WorkspaceID, time.Now().UTC(),
	)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	stable, found, err := handler.projector.notifications.FindAppNotificationByClientID(
		ctx, instance.WorkspaceID, provisioned.Installation.ID,
		roleManagementReviewClientNotificationID(instance.ID),
	)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	if !found || stable.ID != notificationID {
		return app.AppNotificationResult{}, fmt.Errorf("Role management action notification does not match")
	}
	notification, err := handler.projector.notifications.LoadAppNotification(
		ctx, instance.WorkspaceID, notificationID,
	)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	if notification.Status == app.AppNotificationStatusResolved {
		return handler.projector.notifications.GetAppNotificationForBuiltin(
			ctx, IdentityID, instance.WorkspaceID, notification.ID,
		)
	}
	if notification.Status != app.AppNotificationStatusActive ||
		notification.BusinessObjectID == nil || *notification.BusinessObjectID != instance.ID ||
		notification.RecipientBasisRef == nil {
		return app.AppNotificationResult{}, fmt.Errorf("Role management action notification authority does not match")
	}
	assignment, authority, err := handler.approvals.LoadAssignment(
		ctx, instance.WorkspaceID, *notification.RecipientBasisRef,
	)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	expectedAssignmentStatus := approval.AssignmentRejected
	if instance.State == approval.StateApproved {
		expectedAssignmentStatus = approval.AssignmentApproved
	}
	if authority.ID != instance.ID || authority.Version != instance.Version ||
		authority.State != instance.State || assignment.ApprovalInstanceID != instance.ID ||
		assignment.Status != expectedAssignmentStatus {
		return app.AppNotificationResult{}, fmt.Errorf("Role management action assignment authority does not match")
	}
	payload, err := decodeRoleManagementPayload(instance)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	targetStatus := instance.State
	mutationKind := app.AppNotificationMutationResolve
	if instance.State == approval.StateApproved {
		targetStatus = roleManagementCardStatusProcessing
		mutationKind = app.AppNotificationMutationUpdate
	}
	content, err := roleManagementCardContent(
		instance, assignment.ID, "", payload, targetStatus,
	)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	mutationID := strings.TrimSpace(presentation.MutationID)
	if mutationID == "" {
		return app.AppNotificationResult{}, fmt.Errorf("Role management action mutation id is required")
	}
	version := instance.Version
	command := app.AppNotificationMutationCommand{
		NotificationID:        notification.ID,
		Kind:                  mutationKind,
		ExpectedRevision:      notification.Revision,
		ClientMutationID:      "role-management-action:" + mutationID + ":" + notification.ID,
		Content:               &content,
		BusinessObjectVersion: &version,
		CausedByActor: app.AppNotificationActorRef{
			Type: "human", ID: instance.DecidedByMemberID,
		},
		CorrelationID: instance.CorrelationID,
	}
	if presentation.DeliveryID != "" || presentation.AttemptID != "" {
		if presentation.DeliveryID == "" || presentation.AttemptID == "" {
			return app.AppNotificationResult{}, fmt.Errorf("Role management delivery and attempt must be provided together")
		}
		return handler.projector.notifications.MutateAppNotificationForBuiltinDelivery(
			ctx, IdentityID, instance.WorkspaceID,
			presentation.DeliveryID, presentation.AttemptID, command,
		)
	}
	return handler.projector.notifications.MutateAppNotificationForBuiltin(
		ctx, IdentityID, instance.WorkspaceID, command,
	)
}

func roleManagementSubmitCommand(command SubmitCommand) (execution.SubmitCommand, error) {
	proposal, ok := command.Payload.(execution.RoleProposal)
	if !ok {
		return execution.SubmitCommand{}, storeerr.BadRequest(
			"Role management payload is invalid",
		)
	}
	principal := authenticatedAgent(command.Principal)
	if strings.TrimSpace(principal.WorkspaceID) == "" ||
		strings.TrimSpace(principal.AgentID) == "" ||
		strings.TrimSpace(principal.MemberID) == "" ||
		strings.TrimSpace(principal.MachineID) == "" {
		return execution.SubmitCommand{}, storeerr.Forbidden(
			"Role management authenticated Agent identity is incomplete",
		)
	}
	return execution.SubmitCommand{
		Principal:     principal,
		RequestID:     command.ClientRequestID,
		Proposal:      proposal,
		CorrelationID: command.CorrelationID,
		Now:           command.Now,
	}, nil
}

func decodeRoleManagementPayload(instance approval.Instance) (execution.ApprovalPayload, error) {
	if err := execution.ValidateApprovalInstance(instance); err != nil {
		return execution.ApprovalPayload{}, err
	}
	var payload execution.ApprovalPayload
	if err := json.Unmarshal(instance.Payload, &payload); err != nil {
		return execution.ApprovalPayload{}, err
	}
	return payload, nil
}

func roleManagementCardContent(
	instance approval.Instance,
	assignmentID, approverMemberID string,
	payload execution.ApprovalPayload,
	status string,
) (app.AppNotificationContent, error) {
	assignmentID = strings.TrimSpace(assignmentID)
	approverMemberID = strings.TrimSpace(approverMemberID)
	if instance.ID == "" || instance.Version <= 0 || assignmentID == "" {
		return app.AppNotificationContent{}, fmt.Errorf("Role management card identity is required")
	}
	if status != approval.StatePending &&
		status != roleManagementCardStatusProcessing &&
		status != approval.StateRejected {
		return app.AppNotificationContent{}, fmt.Errorf("Role management card status is invalid")
	}
	if status == approval.StatePending && approverMemberID == "" {
		return app.AppNotificationContent{}, fmt.Errorf("Role management pending approver is required")
	}
	card := map[string]any{
		"schemaVersion": "1",
		"template":      "approval_action",
		"title":         "审批 Role 管理请求",
		"titleKey":      "role_management_review",
		"status":        status,
		"proposal": map[string]any{
			"action":  payload.Action,
			"summary": roleManagementSummary(payload),
		},
		"approval": map[string]any{
			"id": instance.ID, "version": instance.Version,
			"assignmentId": assignmentID,
		},
		"actions": []map[string]any{},
	}
	if status == approval.StatePending {
		actorPolicy := map[string]any{
			"kind": "members", "memberIds": []string{approverMemberID},
		}
		card["actions"] = []map[string]any{
			{
				"actionKey": ActionApprove, "type": "callback", "label": "通过",
				"semantics": ActionSemanticsCommit, "actorPolicy": actorPolicy,
			},
			{
				"actionKey": ActionReject, "type": "callback", "label": "暂不通过",
				"semantics": ActionSemanticsRevise, "actorPolicy": actorPolicy,
			},
		}
	}
	encoded, err := json.Marshal(card)
	if err != nil {
		return app.AppNotificationContent{}, err
	}
	return app.AppNotificationContent{
		Kind: app.AppNotificationContentInteractiveCard,
		FallbackText: fmt.Sprintf(
			"Role 管理请求：%s", payload.Action,
		),
		CardJSON: encoded,
	}, nil
}

func roleManagementSummary(payload execution.ApprovalPayload) map[string]any {
	switch payload.Action {
	case execution.ActionCreate:
		if payload.Create == nil {
			return map[string]any{}
		}
		return map[string]any{
			"name":        payload.Create.Name,
			"description": payload.Create.Description,
			"definition":  payload.Create.Definition,
		}
	case execution.ActionUpdate:
		if payload.Update == nil {
			return map[string]any{}
		}
		summary := map[string]any{
			"roleId":                payload.Update.RoleID,
			"expectedRoleVersionId": payload.Update.ExpectedRoleVersionID,
		}
		if payload.Update.Name != nil {
			summary["name"] = *payload.Update.Name
		}
		if payload.Update.Description != nil {
			summary["description"] = *payload.Update.Description
		}
		if payload.Update.Definition != nil {
			summary["definition"] = *payload.Update.Definition
		}
		if payload.Update.Visibility != "" {
			summary["visibility"] = payload.Update.Visibility
		}
		return summary
	case execution.ActionDelete:
		if payload.Delete == nil {
			return map[string]any{}
		}
		return map[string]any{
			"roleId":                payload.Delete.RoleID,
			"expectedRoleVersionId": payload.Delete.ExpectedRoleVersionID,
		}
	case execution.ActionApply:
		if payload.Apply == nil {
			return map[string]any{}
		}
		return map[string]any{
			"roleId":        payload.Apply.RoleID,
			"roleVersionId": payload.Apply.RoleVersionID,
			"agentId":       payload.Apply.AgentID,
		}
	default:
		return map[string]any{}
	}
}

func (handler *roleManagementSubjectHandler) submitResult(
	ctx context.Context,
	workspaceID string,
	result execution.RequestResult,
) (SubmitResult, error) {
	if handler.approvals == nil {
		return SubmitResult{}, fmt.Errorf("Role management Approval store is not configured")
	}
	instance, err := handler.approvals.Load(ctx, workspaceID, result.ApprovalID)
	if err != nil {
		return SubmitResult{}, err
	}
	assignments, err := handler.approvals.ListAssignments(
		ctx, instance.WorkspaceID, instance.ID,
	)
	if err != nil {
		return SubmitResult{}, err
	}
	return SubmitResult{
		Instance: instance, Assignments: assignments,
		Created: result.Created, Value: result,
	}, nil
}
