package approvalapp

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

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

// RoleManagementTerminalObserver resolves the approved request's existing
// processing notification before its execution ledger is settled.
type RoleManagementTerminalObserver struct {
	approvals *approval.Store
	projector *Projector
}

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

type roleManagementTerminalContext struct {
	instance     approval.Instance
	assignment   approval.Assignment
	payload      execution.ApprovalPayload
	notification app.AppNotificationRecord
	card         app.AppNotificationCardRecord
}

// ProjectTerminal validates the complete Approval/card authority chain and
// resolves exactly the notification that was changed to processing at approve
// time. A fully equivalent terminal card is an idempotent success.
func (observer *RoleManagementTerminalObserver) ProjectTerminal(
	ctx context.Context,
	instance approval.Instance,
	result execution.RequestResult,
) error {
	if err := observer.validateConfiguration(); err != nil {
		return err
	}
	terminal, err := observer.loadRoleManagementTerminalContext(ctx, instance)
	if err != nil {
		return err
	}
	if err := validateRoleManagementTerminalResult(
		terminal.instance, terminal.payload, result,
	); err != nil {
		return err
	}
	content, err := roleManagementTerminalCardContent(
		terminal.instance, terminal.assignment.ID, terminal.payload, result,
	)
	if err != nil {
		return err
	}
	if roleManagementTerminalAlreadyConverged(
		terminal.notification, terminal.card, terminal.instance.Version,
		content.CardJSON,
	) {
		return nil
	}
	if terminal.notification.Status != app.AppNotificationStatusActive ||
		terminal.card.State != app.AppNotificationCardStateActive {
		return fmt.Errorf("Role management processing card cannot converge")
	}
	processing, err := roleManagementCardContent(
		terminal.instance,
		terminal.assignment.ID,
		"",
		terminal.payload,
		roleManagementCardStatusProcessing,
	)
	if err != nil {
		return err
	}
	if !equalRoleManagementCardJSON(
		terminal.card.RenderPayloadJSON,
		processing.CardJSON,
	) {
		return fmt.Errorf("Role management processing card content does not match")
	}
	version := terminal.instance.Version
	_, err = observer.projector.notifications.MutateAppNotificationForBuiltin(
		ctx,
		IdentityID,
		terminal.instance.WorkspaceID,
		app.AppNotificationMutationCommand{
			NotificationID:   terminal.notification.ID,
			Kind:             app.AppNotificationMutationResolve,
			ExpectedRevision: terminal.notification.Revision,
			ClientMutationID: fmt.Sprintf(
				"role-management-execution:%s:terminal",
				terminal.instance.ID,
			),
			Content:               &content,
			BusinessObjectVersion: &version,
			CausedByActor: app.AppNotificationActorRef{
				Type: "agent", ID: terminal.payload.ApplicantAgentID,
			},
			CorrelationID: terminal.instance.CorrelationID,
		},
	)
	return err
}

// LoadProjectedTerminal validates the complete durable processing-card
// authority chain before execution consults current authority. Exact active
// content remains non-terminal; exact resolved content is a stable receipt.
func (observer *RoleManagementTerminalObserver) LoadProjectedTerminal(
	ctx context.Context,
	instance approval.Instance,
	payload execution.ApprovalPayload,
) (execution.RequestResult, bool, error) {
	if err := observer.validateConfiguration(); err != nil {
		return execution.RequestResult{}, false, err
	}
	terminal, err := observer.loadRoleManagementTerminalContext(ctx, instance)
	if err != nil {
		return execution.RequestResult{}, false, err
	}
	if !reflect.DeepEqual(terminal.payload, payload) {
		return execution.RequestResult{}, false, fmt.Errorf(
			"Role management projected payload does not match",
		)
	}
	switch {
	case terminal.notification.Status == app.AppNotificationStatusActive &&
		terminal.card.State == app.AppNotificationCardStateActive:
		processing, err := roleManagementCardContent(
			terminal.instance,
			terminal.assignment.ID,
			"",
			terminal.payload,
			roleManagementCardStatusProcessing,
		)
		if err != nil {
			return execution.RequestResult{}, false, err
		}
		if !equalRoleManagementCardJSON(
			terminal.card.RenderPayloadJSON,
			processing.CardJSON,
		) {
			return execution.RequestResult{}, false, fmt.Errorf(
				"Role management processing card content does not match",
			)
		}
		return execution.RequestResult{}, false, nil
	case terminal.notification.Status == app.AppNotificationStatusResolved &&
		terminal.card.State == app.AppNotificationCardStateResolved:
		result, err := roleManagementProjectedTerminalResult(terminal)
		if err != nil {
			return execution.RequestResult{}, false, err
		}
		return result, true, nil
	default:
		return execution.RequestResult{}, false, fmt.Errorf(
			"Role management projected notification and card state do not match",
		)
	}
}

func (observer *RoleManagementTerminalObserver) validateConfiguration() error {
	if observer == nil || observer.approvals == nil || observer.projector == nil ||
		observer.projector.notifications == nil || observer.projector.provisioner == nil {
		return fmt.Errorf("Role management terminal observer is not configured")
	}
	return nil
}

func (observer *RoleManagementTerminalObserver) loadRoleManagementTerminalContext(
	ctx context.Context,
	instance approval.Instance,
) (roleManagementTerminalContext, error) {
	if strings.TrimSpace(instance.ID) == "" ||
		strings.TrimSpace(instance.WorkspaceID) == "" ||
		instance.SubjectType != execution.SubjectType ||
		instance.State != approval.StateApproved ||
		instance.Decision != approval.DecisionApproved ||
		instance.Version <= 1 {
		return roleManagementTerminalContext{}, fmt.Errorf(
			"Role management terminal Approval is invalid",
		)
	}
	authority, err := observer.approvals.Load(
		ctx, instance.WorkspaceID, instance.ID,
	)
	if err != nil {
		return roleManagementTerminalContext{}, err
	}
	if !reflect.DeepEqual(authority, instance) {
		return roleManagementTerminalContext{}, fmt.Errorf(
			"Role management terminal Approval authority does not match",
		)
	}
	payload, err := decodeRoleManagementPayload(authority)
	if err != nil {
		return roleManagementTerminalContext{}, err
	}
	provisioned, err := observer.projector.provisioner.EnsureWorkspace(
		ctx, authority.WorkspaceID, time.Now().UTC(),
	)
	if err != nil {
		return roleManagementTerminalContext{}, err
	}
	clientID := roleManagementReviewClientNotificationID(authority.ID)
	notification, found, err := observer.projector.notifications.FindAppNotificationByClientID(
		ctx,
		authority.WorkspaceID,
		provisioned.Installation.ID,
		clientID,
	)
	if err != nil {
		return roleManagementTerminalContext{}, err
	}
	if !found {
		return roleManagementTerminalContext{}, fmt.Errorf(
			"Role management processing notification was not found",
		)
	}
	if err := validateRoleManagementTerminalNotification(
		notification, authority, clientID,
	); err != nil {
		return roleManagementTerminalContext{}, err
	}
	card, err := observer.projector.notifications.LoadAppNotificationCardInTx(
		ctx,
		observer.projector.provisioner.db,
		notification.MessageID,
		false,
	)
	if err != nil {
		return roleManagementTerminalContext{}, err
	}
	var snapshot roleManagementTerminalCardSnapshot
	if err := json.Unmarshal(card.RenderPayloadJSON, &snapshot); err != nil {
		return roleManagementTerminalContext{}, fmt.Errorf(
			"decode Role management processing card: %w",
			err,
		)
	}
	if card.AppNotificationID != notification.ID ||
		card.MessageID != notification.MessageID ||
		(card.State != app.AppNotificationCardStateActive &&
			card.State != app.AppNotificationCardStateResolved) ||
		snapshot.SchemaVersion != "1" ||
		snapshot.Template != "approval_action" ||
		snapshot.Approval.ID != authority.ID ||
		snapshot.Approval.Version != authority.Version ||
		strings.TrimSpace(snapshot.Approval.AssignmentID) == "" {
		return roleManagementTerminalContext{}, fmt.Errorf(
			"Role management processing card authority does not match",
		)
	}
	assignment, assignmentAuthority, err := observer.approvals.LoadAssignment(
		ctx,
		authority.WorkspaceID,
		snapshot.Approval.AssignmentID,
	)
	if err != nil {
		return roleManagementTerminalContext{}, err
	}
	if assignment.ApprovalInstanceID != authority.ID ||
		assignment.WorkspaceID != authority.WorkspaceID ||
		assignment.Status != approval.AssignmentApproved ||
		assignment.ApproverMemberID != authority.DecidedByMemberID ||
		!reflect.DeepEqual(assignmentAuthority, authority) ||
		notification.RecipientMemberID == nil ||
		*notification.RecipientMemberID != assignment.ApproverMemberID ||
		notification.RecipientBasisRef == nil ||
		*notification.RecipientBasisRef != assignment.ID {
		return roleManagementTerminalContext{}, fmt.Errorf(
			"Role management terminal assignment authority does not match",
		)
	}
	return roleManagementTerminalContext{
		instance: authority, assignment: assignment, payload: payload,
		notification: notification, card: card,
	}, nil
}

func roleManagementProjectedTerminalResult(
	terminal roleManagementTerminalContext,
) (execution.RequestResult, error) {
	var snapshot roleManagementTerminalCardSnapshot
	if err := json.Unmarshal(terminal.card.RenderPayloadJSON, &snapshot); err != nil {
		return execution.RequestResult{}, fmt.Errorf(
			"decode Role management terminal card: %w",
			err,
		)
	}
	result := execution.RequestResult{
		ApprovalID:       terminal.instance.ID,
		ApplicantAgentID: terminal.payload.ApplicantAgentID,
		RequestID:        terminal.payload.RequestID,
		Action:           terminal.payload.Action,
		Status:           snapshot.Status,
	}
	if terminal.payload.Apply != nil {
		result.AgentID = terminal.payload.Apply.AgentID
	}
	switch snapshot.Status {
	case execution.RequestSucceeded:
		result.RoleID = snapshot.Result.RoleID
		result.RoleVersionID = snapshot.Result.RoleVersionID
		if snapshot.Result.AgentID != result.AgentID {
			return execution.RequestResult{}, fmt.Errorf(
				"Role management terminal success target does not match",
			)
		}
	case execution.RequestFailed:
		result.FailureCode = snapshot.Result.FailureCode
		result.FailureMessage = snapshot.Result.FailureMessage
	default:
		return execution.RequestResult{}, fmt.Errorf(
			"Role management terminal status is invalid",
		)
	}
	if err := validateRoleManagementTerminalResult(
		terminal.instance, terminal.payload, result,
	); err != nil {
		return execution.RequestResult{}, err
	}
	expected, err := roleManagementTerminalCardContent(
		terminal.instance, terminal.assignment.ID, terminal.payload, result,
	)
	if err != nil {
		return execution.RequestResult{}, err
	}
	if !equalRoleManagementCardJSON(
		terminal.card.RenderPayloadJSON,
		expected.CardJSON,
	) {
		return execution.RequestResult{}, fmt.Errorf(
			"Role management terminal card content does not match",
		)
	}
	return result, nil
}

func validateRoleManagementTerminalNotification(
	notification app.AppNotificationRecord,
	instance approval.Instance,
	clientID string,
) error {
	if notification.WorkspaceID != instance.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 != instance.ID ||
		notification.BusinessObjectVersion == nil ||
		*notification.BusinessObjectVersion != instance.Version ||
		notification.RecipientBasisKind == nil ||
		*notification.RecipientBasisKind != RecipientBasisAssignment ||
		(notification.Status != app.AppNotificationStatusActive &&
			notification.Status != app.AppNotificationStatusResolved) {
		return fmt.Errorf(
			"Role management terminal notification authority does not match",
		)
	}
	return nil
}

func validateRoleManagementTerminalResult(
	instance approval.Instance,
	payload execution.ApprovalPayload,
	result execution.RequestResult,
) error {
	expectedAgentID := ""
	if payload.Apply != nil {
		expectedAgentID = payload.Apply.AgentID
	}
	if result.ApprovalID != instance.ID ||
		result.ApplicantAgentID != payload.ApplicantAgentID ||
		result.RequestID != payload.RequestID ||
		result.Action != payload.Action ||
		result.AgentID != expectedAgentID {
		return fmt.Errorf("Role management terminal result authority does not match")
	}
	switch result.Status {
	case execution.RequestSucceeded:
		if strings.TrimSpace(result.RoleID) == "" ||
			strings.TrimSpace(result.RoleVersionID) == "" ||
			result.RoleID != strings.TrimSpace(result.RoleID) ||
			result.RoleVersionID != strings.TrimSpace(result.RoleVersionID) ||
			result.FailureCode != "" ||
			result.FailureMessage != "" {
			return fmt.Errorf("Role management terminal success is invalid")
		}
	case execution.RequestFailed:
		if result.RoleID != "" ||
			result.RoleVersionID != "" ||
			!safeRoleManagementTerminalFailure(
				result.FailureCode,
				result.FailureMessage,
			) {
			return fmt.Errorf("Role management terminal failure is invalid")
		}
	default:
		return fmt.Errorf("Role management terminal status is invalid")
	}
	return nil
}

func safeRoleManagementTerminalFailure(code, message string) bool {
	messages := map[string]string{
		string(execution.ApplyInvalidRequest):    "The approved role request is invalid.",
		string(execution.ApplyAuthorityDenied):   "The role operation is no longer authorized.",
		string(execution.ApplyNotFound):          "The requested role resource was not found.",
		string(execution.ApplyVersionConflict):   "The role changed before this request was applied.",
		string(execution.ApplyOperationConflict): "A conflicting role operation already exists.",
	}
	return messages[code] != "" && messages[code] == message
}

type roleManagementTerminalCardSnapshot struct {
	SchemaVersion string                  `json:"schemaVersion"`
	Template      string                  `json:"template"`
	Status        execution.RequestStatus `json:"status"`
	Approval      struct {
		ID           string `json:"id"`
		Version      int64  `json:"version"`
		AssignmentID string `json:"assignmentId"`
	} `json:"approval"`
	Result struct {
		RoleID         string `json:"roleId"`
		RoleVersionID  string `json:"roleVersionId"`
		AgentID        string `json:"agentId"`
		FailureCode    string `json:"failureCode"`
		FailureMessage string `json:"failureMessage"`
	} `json:"result"`
}

func roleManagementTerminalCardContent(
	instance approval.Instance,
	assignmentID string,
	payload execution.ApprovalPayload,
	result execution.RequestResult,
) (app.AppNotificationContent, error) {
	if err := validateRoleManagementTerminalResult(instance, payload, result); err != nil {
		return app.AppNotificationContent{}, err
	}
	content, err := roleManagementCardContent(
		instance,
		assignmentID,
		"",
		payload,
		roleManagementCardStatusProcessing,
	)
	if err != nil {
		return app.AppNotificationContent{}, err
	}
	var card map[string]any
	if err := json.Unmarshal(content.CardJSON, &card); err != nil {
		return app.AppNotificationContent{}, err
	}
	card["status"] = result.Status
	if result.Status == execution.RequestSucceeded {
		successResult := map[string]any{
			"roleId": result.RoleID, "roleVersionId": result.RoleVersionID,
		}
		if result.AgentID != "" {
			successResult["agentId"] = result.AgentID
		}
		card["result"] = successResult
	} else {
		card["result"] = map[string]any{
			"failureCode":    result.FailureCode,
			"failureMessage": result.FailureMessage,
		}
	}
	encoded, err := json.Marshal(card)
	if err != nil {
		return app.AppNotificationContent{}, err
	}
	content.CardJSON = encoded
	content.FallbackText = fmt.Sprintf(
		"Role 管理请求：%s",
		result.Status,
	)
	return content, nil
}

func roleManagementTerminalAlreadyConverged(
	notification app.AppNotificationRecord,
	card app.AppNotificationCardRecord,
	version int64,
	expectedCard json.RawMessage,
) bool {
	return notification.Status == app.AppNotificationStatusResolved &&
		card.State == app.AppNotificationCardStateResolved &&
		notification.BusinessObjectVersion != nil &&
		*notification.BusinessObjectVersion == version &&
		equalRoleManagementCardJSON(card.RenderPayloadJSON, expectedCard)
}

func equalRoleManagementCardJSON(left, right json.RawMessage) bool {
	var leftValue, rightValue any
	leftDecoder := json.NewDecoder(bytes.NewReader(left))
	leftDecoder.UseNumber()
	rightDecoder := json.NewDecoder(bytes.NewReader(right))
	rightDecoder.UseNumber()
	if leftDecoder.Decode(&leftValue) != nil ||
		rightDecoder.Decode(&rightValue) != nil {
		return false
	}
	return reflect.DeepEqual(leftValue, rightValue)
}
