package approvalapp

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

	"github.com/tidbcloud/AgentTeam/internal/service/agentapproval"
	"github.com/tidbcloud/AgentTeam/internal/store/agentops"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
)

// AgentManagementTerminalObserver resolves the approved request's existing
// processing notification after every operation item is terminal.
type AgentManagementTerminalObserver struct {
	approvals *approval.Store
	projector *Projector
}

func NewAgentManagementTerminalObserver(
	approvals *approval.Store,
	projector *Projector,
) *AgentManagementTerminalObserver {
	return &AgentManagementTerminalObserver{
		approvals: approvals,
		projector: projector,
	}
}

type agentManagementTerminalContext struct {
	instance     approval.Instance
	payload      agentapproval.Payload
	notification app.AppNotificationRecord
	card         app.AppNotificationCardRecord
	snapshot     agentManagementReviewCard
	assignmentID string
}

func (observer *AgentManagementTerminalObserver) ObserveOperationTerminal(
	ctx context.Context,
	input agentops.OperationTerminalInput,
) error {
	if observer == nil || observer.approvals == nil || observer.projector == nil ||
		observer.projector.notifications == nil || observer.projector.provisioner == nil {
		return fmt.Errorf("agent management terminal observer is not configured")
	}
	terminal, err := observer.loadAgentManagementTerminalContext(ctx, input)
	if err != nil {
		return err
	}
	processorName, err := observer.projector.memberDisplayName(
		ctx,
		input.Operation.WorkspaceID,
		terminal.instance.DecidedByMemberID,
		"",
	)
	if err != nil {
		return err
	}
	content, err := agentManagementTerminalCardContent(
		input, terminal.payload, terminal.assignmentID, terminal.instance.Version,
		processorName,
	)
	if err != nil {
		return err
	}
	if agentManagementTerminalAlreadyConverged(
		terminal.notification, terminal.card, terminal.snapshot, input.TerminalStatus,
		terminal.instance.Version, content.CardJSON,
	) {
		return nil
	}
	if terminal.notification.Status != app.AppNotificationStatusActive ||
		terminal.card.State != app.AppNotificationCardStateActive ||
		terminal.snapshot.Status != agentManagementCardStatusProcessing ||
		len(terminal.snapshot.Actions) != 0 {
		return fmt.Errorf("agent management processing card cannot converge")
	}
	version := terminal.instance.Version
	_, err = observer.projector.notifications.MutateAppNotificationForBuiltin(
		ctx, IdentityID, input.Operation.WorkspaceID, app.AppNotificationMutationCommand{
			NotificationID:   terminal.notification.ID,
			Kind:             app.AppNotificationMutationResolve,
			ExpectedRevision: terminal.notification.Revision,
			ClientMutationID: fmt.Sprintf(
				"agent-management-operation:%d:terminal", input.Operation.ID,
			),
			Content:               &content,
			BusinessObjectVersion: &version,
			CausedByActor: app.AppNotificationActorRef{
				Type: "agent", ID: input.Operation.ApplicantAgentID,
			},
			CorrelationID: terminal.instance.CorrelationID,
		},
	)
	return err
}

func (observer *AgentManagementTerminalObserver) loadAgentManagementTerminalContext(
	ctx context.Context,
	input agentops.OperationTerminalInput,
) (agentManagementTerminalContext, error) {
	operation := input.Operation
	if strings.TrimSpace(operation.WorkspaceID) == "" ||
		strings.TrimSpace(operation.ApprovalID) == "" || operation.ID <= 0 ||
		!agentManagementTerminalStatus(input.TerminalStatus) || len(input.Items) == 0 {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management terminal input is invalid")
	}
	for index, item := range input.Items {
		if item.OperationID != operation.ID || item.ItemIndex != index ||
			(item.Status != "applied" && item.Status != "failed") {
			return agentManagementTerminalContext{}, fmt.Errorf("agent management terminal items are invalid")
		}
	}
	instance, err := observer.approvals.Load(ctx, operation.WorkspaceID, operation.ApprovalID)
	if err != nil {
		return agentManagementTerminalContext{}, err
	}
	if instance.ID != operation.ApprovalID || instance.WorkspaceID != operation.WorkspaceID ||
		instance.SubjectType != agentapproval.SubjectType || instance.State != approval.StateApproved {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management terminal approval authority does not match")
	}
	provisioned, err := observer.projector.provisioner.EnsureWorkspace(ctx, operation.WorkspaceID, time.Now().UTC())
	if err != nil {
		return agentManagementTerminalContext{}, err
	}
	clientID := agentManagementReviewClientNotificationID(operation.ApprovalID)
	notification, found, err := observer.projector.notifications.FindAppNotificationByClientID(
		ctx, operation.WorkspaceID, provisioned.Installation.ID, clientID,
	)
	if err != nil {
		return agentManagementTerminalContext{}, err
	}
	if !found {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management processing notification was not found")
	}
	if err := validateAgentManagementTerminalNotification(notification, operation, instance, clientID); err != nil {
		return agentManagementTerminalContext{}, err
	}
	card, err := observer.projector.notifications.LoadAppNotificationCardInTx(
		ctx, observer.projector.provisioner.db, notification.MessageID, false,
	)
	if err != nil {
		return agentManagementTerminalContext{}, err
	}
	snapshot, err := decodeAgentManagementReviewCard(card.RenderPayloadJSON)
	if err != nil {
		return agentManagementTerminalContext{}, err
	}
	if card.AppNotificationID != notification.ID || card.MessageID != notification.MessageID ||
		snapshot.Approval.ID != operation.ApprovalID || snapshot.Approval.Version != instance.Version ||
		(card.State != app.AppNotificationCardStateActive && card.State != app.AppNotificationCardStateResolved) {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management processing card authority does not match")
	}
	var payload agentapproval.Payload
	if json.Unmarshal(instance.Payload, &payload) != nil {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management terminal approval payload is invalid")
	}
	if !equalAgentManagementCardJSON(operation.Payload, instance.Payload) || operation.Action != payload.Action ||
		operation.ApplicantAgentID != payload.Applicant.AgentID || operation.OwnerUserID != payload.Owner.UserID ||
		operation.RequestID != payload.ClientRequestID {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management terminal operation authority does not match")
	}
	assignment, authorityInstance, err := observer.approvals.LoadAssignment(
		ctx, operation.WorkspaceID, snapshot.Approval.AssignmentID,
	)
	if err != nil {
		return agentManagementTerminalContext{}, err
	}
	if assignment.ApprovalInstanceID != instance.ID ||
		assignment.ApproverMemberID != strings.TrimSpace(payload.Owner.MemberID) ||
		assignment.Status != approval.AssignmentApproved || authorityInstance.ID != instance.ID ||
		authorityInstance.Version != instance.Version || authorityInstance.State != approval.StateApproved ||
		notification.RecipientMemberID == nil || *notification.RecipientMemberID != assignment.ApproverMemberID ||
		notification.RecipientBasisRef == nil || *notification.RecipientBasisRef != assignment.ID {
		return agentManagementTerminalContext{}, fmt.Errorf("agent management terminal assignment authority does not match")
	}
	return agentManagementTerminalContext{
		instance: instance, payload: payload, notification: notification,
		card: card, snapshot: snapshot, assignmentID: assignment.ID,
	}, nil
}

func validateAgentManagementTerminalNotification(
	notification app.AppNotificationRecord,
	operation agentops.Operation,
	instance approval.Instance,
	clientID string,
) error {
	if notification.WorkspaceID != operation.WorkspaceID ||
		notification.ClientNotificationID != clientID ||
		notification.TargetKind != app.AppNotificationTargetHumanDM ||
		notification.ContentKind != app.AppNotificationContentInteractiveCard ||
		notification.BusinessObjectNamespace == nil ||
		*notification.BusinessObjectNamespace != BusinessNamespace ||
		notification.BusinessObjectType == nil ||
		*notification.BusinessObjectType != BusinessTypeInstance ||
		notification.BusinessObjectID == nil ||
		*notification.BusinessObjectID != operation.ApprovalID ||
		notification.BusinessObjectVersion == nil ||
		*notification.BusinessObjectVersion != instance.Version ||
		notification.RecipientBasisKind == nil ||
		*notification.RecipientBasisKind != RecipientBasisAssignment ||
		(notification.Status != app.AppNotificationStatusActive &&
			notification.Status != app.AppNotificationStatusResolved) {
		return fmt.Errorf("agent management terminal notification authority does not match")
	}
	return nil
}

func agentManagementTerminalStatus(status string) bool {
	return status == "applied" || status == "partial" || status == "failed"
}

func agentManagementTerminalAlreadyConverged(
	notification app.AppNotificationRecord,
	card app.AppNotificationCardRecord,
	snapshot agentManagementReviewCard,
	status string,
	version int64,
	expectedCard json.RawMessage,
) bool {
	return notification.Status == app.AppNotificationStatusResolved &&
		card.State == app.AppNotificationCardStateResolved &&
		snapshot.Status == status &&
		len(snapshot.Actions) == 0 &&
		snapshot.Approval.Version == version &&
		notification.BusinessObjectVersion != nil &&
		*notification.BusinessObjectVersion == version &&
		equalAgentManagementCardJSON(card.RenderPayloadJSON, expectedCard)
}

func equalAgentManagementCardJSON(left, right json.RawMessage) bool {
	var leftValue, rightValue any
	if json.Unmarshal(left, &leftValue) != nil ||
		json.Unmarshal(right, &rightValue) != nil {
		return false
	}
	return reflect.DeepEqual(leftValue, rightValue)
}

func agentManagementTerminalCardContent(
	input agentops.OperationTerminalInput,
	payload agentapproval.Payload,
	assignmentID string,
	approvalVersion int64,
	processorName string,
) (app.AppNotificationContent, error) {
	fields := make([]map[string]any, 0, len(input.Items))
	for _, item := range input.Items {
		if agentID := safeAgentManagementResultAgentID(item.Result); item.Status == "applied" && agentID != "" &&
			(input.Operation.Action == agentapproval.ActionCreate ||
				input.Operation.Action == agentapproval.ActionDuplicate) {
			fields = append(fields, map[string]any{
				"label": "result_agent",
				"value": agentID,
			})
		} else if item.Status == "failed" {
			fields = append(fields, map[string]any{
				"label": "failed_item",
				"value": fmt.Sprintf("#%d", item.ItemIndex+1),
			})
		}
	}
	summary := agentManagementProposalSummary(payload)
	card := map[string]any{
		"schemaVersion": "1",
		"template":      "approval_action",
		"title":         "Agent 管理请求结果",
		"titleKey":      "agent_management_review",
		"status":        input.TerminalStatus,
		"proposal": map[string]any{
			"action":  input.Operation.Action,
			"summary": summary,
		},
		"summary": map[string]any{
			"action": input.Operation.Action,
			"status": input.TerminalStatus,
		},
		"fields": fields,
		"approval": map[string]any{
			"id": input.Operation.ApprovalID, "version": approvalVersion,
			"assignmentId":  assignmentID,
			"processorName": strings.TrimSpace(processorName),
		},
		"actions": []map[string]any{},
	}
	encoded, err := json.Marshal(card)
	if err != nil {
		return app.AppNotificationContent{}, err
	}
	return app.AppNotificationContent{
		Kind: app.AppNotificationContentInteractiveCard,
		FallbackText: fmt.Sprintf(
			"Agent 管理请求结果：%s", input.TerminalStatus,
		),
		CardJSON: encoded,
	}, nil
}

func safeAgentManagementResultAgentID(raw json.RawMessage) string {
	var result struct {
		AgentID string `json:"agentId"`
	}
	if json.Unmarshal(raw, &result) != nil {
		return ""
	}
	value := strings.TrimSpace(result.AgentID)
	if value == "" || len(value) > 255 {
		return ""
	}
	for _, r := range value {
		if !(r == '_' || r == '-' || r == '.' ||
			r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' ||
			r >= '0' && r <= '9') {
			return ""
		}
	}
	return value
}

var _ agentops.OperationTerminalObserver = (*AgentManagementTerminalObserver)(nil)
