package approvalapp

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

const (
	SkillDeleteSubjectType   = "skill_delete"
	SkillDeleteRequestType   = "skill_delete"
	skillDeletePayloadSchema = 1

	SkillDeletePending  = "pending"
	SkillDeleteRejected = "rejected"
	SkillDeleteDeleted  = "deleted"
)

// SkillDeleteRequest contains only model-authored business input. The
// authenticated Agent and current Human authority are always server-derived.
type SkillDeleteRequest struct {
	SkillID string `json:"skillId"`
}

type SkillDeleteResult struct {
	ApprovalID string
	Status     string
}

type SkillDeleteAuthorityCommand struct {
	Principal SubjectPrincipal
	SkillID   string
}

type SkillDeletePrepared struct {
	SkillID          string
	SkillName        string
	ApproverMemberID string
}

// SkillDeleteAuthority owns current Core identity/permission checks and the
// existing Resource Skill DELETE transport. Execute must repeat every current
// authority check and normalize an already-missing Skill to success.
type SkillDeleteAuthority interface {
	Prepare(context.Context, SkillDeleteAuthorityCommand) (SkillDeletePrepared, error)
	Execute(context.Context, SkillDeleteAuthorityCommand) error
}

type skillDeleteApprovalStore interface {
	LoadByIdempotencyKey(context.Context, string, string, string) (approval.Instance, error)
	Create(context.Context, approval.CreateParams) (approval.Instance, []approval.Assignment, bool, error)
	ListAssignments(context.Context, string, string) ([]approval.Assignment, error)
	LoadAssignment(context.Context, string, string) (approval.Assignment, approval.Instance, error)
	Decide(context.Context, approval.DecideParams) (approval.DecisionResult, error)
}

type skillDeleteSubjectHandler struct {
	approvals skillDeleteApprovalStore
	authority SkillDeleteAuthority
	projector *Projector
}

type skillDeletePayload struct {
	ApplicantAgentID string `json:"applicantAgentId"`
	RequestID        string `json:"requestId"`
	SchemaVersion    int    `json:"schemaVersion"`
	SkillID          string `json:"skillId"`
	SkillName        string `json:"skillName"`
}

func NewSkillDeleteSubjectHandler(
	approvals skillDeleteApprovalStore,
	authority SkillDeleteAuthority,
	projector *Projector,
) SubjectHandler {
	return &skillDeleteSubjectHandler{
		approvals: approvals, authority: authority, projector: projector,
	}
}

func (*skillDeleteSubjectHandler) SubjectType() string { return SkillDeleteSubjectType }
func (*skillDeleteSubjectHandler) RequestType() string { return SkillDeleteRequestType }
func (*skillDeleteSubjectHandler) CancelPolicy() SubjectCancelPolicy {
	return SubjectCancelNotSupported
}
func (*skillDeleteSubjectHandler) ReconcilePolicy() SubjectReconcilePolicy {
	return SubjectReconcileNone
}

func (handler *skillDeleteSubjectHandler) Validate(
	_ context.Context,
	instance approval.Instance,
) error {
	if handler == nil || handler.approvals == nil ||
		handler.authority == nil || handler.projector == nil {
		return fmt.Errorf("Skill delete Approval subject handler is not configured")
	}
	_, err := decodeSkillDeletePayload(instance)
	return err
}

func (*skillDeleteSubjectHandler) DecodeSubmitPayload(value any) (any, error) {
	var request SkillDeleteRequest
	switch value := value.(type) {
	case SkillDeleteRequest:
		request = value
	case json.RawMessage:
		decoder := json.NewDecoder(strings.NewReader(string(value)))
		decoder.DisallowUnknownFields()
		if err := decoder.Decode(&request); err != nil {
			return nil, storeerr.BadRequest("Skill delete payload is invalid")
		}
		var trailing any
		if err := decoder.Decode(&trailing); err != io.EOF {
			return nil, storeerr.BadRequest("Skill delete payload is invalid")
		}
	default:
		return nil, storeerr.BadRequest("Skill delete payload is invalid")
	}
	request.SkillID = strings.TrimSpace(request.SkillID)
	if request.SkillID == "" {
		return nil, storeerr.BadRequest("Skill delete requires skillId")
	}
	return request, nil
}

func (handler *skillDeleteSubjectHandler) Submit(
	ctx context.Context,
	command SubmitCommand,
) (SubmitResult, error) {
	request, ok := command.Payload.(SkillDeleteRequest)
	if !ok {
		return SubmitResult{}, storeerr.BadRequest("Skill delete payload is invalid")
	}
	command, err := normalizeSkillDeleteCommand(command)
	if err != nil {
		return SubmitResult{}, err
	}
	request.SkillID = strings.TrimSpace(request.SkillID)
	key := skillDeleteIdempotencyKey(
		command.Principal.WorkspaceID,
		command.Principal.PrincipalID,
		command.ClientRequestID,
	)
	existing, err := handler.approvals.LoadByIdempotencyKey(
		ctx, command.Principal.WorkspaceID, command.Principal.MemberID, key,
	)
	switch {
	case err == nil:
		return handler.replay(ctx, command, request, existing, key)
	case !errors.Is(err, storeerr.ErrNotFound):
		return SubmitResult{}, err
	}

	prepared, err := handler.authority.Prepare(ctx, SkillDeleteAuthorityCommand{
		Principal: command.Principal,
		SkillID:   request.SkillID,
	})
	if err != nil {
		return SubmitResult{}, err
	}
	prepared.SkillID = strings.TrimSpace(prepared.SkillID)
	prepared.ApproverMemberID = strings.TrimSpace(prepared.ApproverMemberID)
	if prepared.SkillID != request.SkillID || prepared.ApproverMemberID == "" {
		return SubmitResult{}, fmt.Errorf("Skill delete authority returned an invalid snapshot")
	}
	payload := skillDeletePayload{
		ApplicantAgentID: command.Principal.PrincipalID,
		RequestID:        command.ClientRequestID,
		SchemaVersion:    skillDeletePayloadSchema,
		SkillID:          prepared.SkillID,
		SkillName:        strings.TrimSpace(prepared.SkillName),
	}
	deterministic, digest, err := encodeSkillDeletePayload(payload)
	if err != nil {
		return SubmitResult{}, err
	}
	instance, assignments, created, err := handler.approvals.Create(
		ctx, approval.CreateParams{
			ID:                  skillDeleteApprovalID(key),
			WorkspaceID:         command.Principal.WorkspaceID,
			SubjectType:         SkillDeleteSubjectType,
			SubjectRef:          key,
			SubjectVersion:      skillDeletePayloadSchema,
			Payload:             deterministic,
			PayloadDigest:       digest,
			IdempotencyKey:      key,
			ApplicantMemberID:   command.Principal.MemberID,
			AllowAgentApplicant: true,
			ApproverMemberIDs:   []string{prepared.ApproverMemberID},
			CorrelationID:       command.CorrelationID,
			Now:                 command.Now,
		},
	)
	if err != nil {
		return SubmitResult{}, err
	}
	return handler.result(ctx, command, instance, assignments, created)
}

func (handler *skillDeleteSubjectHandler) replay(
	ctx context.Context,
	command SubmitCommand,
	request SkillDeleteRequest,
	instance approval.Instance,
	key string,
) (SubmitResult, error) {
	payload, err := decodeSkillDeletePayload(instance)
	if err != nil {
		return SubmitResult{}, err
	}
	if instance.WorkspaceID != command.Principal.WorkspaceID ||
		instance.ApplicantMemberID != command.Principal.MemberID ||
		instance.IdempotencyKey != key || instance.SubjectRef != key ||
		payload.ApplicantAgentID != command.Principal.PrincipalID ||
		payload.RequestID != command.ClientRequestID ||
		payload.SkillID != request.SkillID {
		return SubmitResult{}, storeerr.Conflict(
			"Skill delete requestId is already bound to another immutable request",
		)
	}
	assignments, err := handler.approvals.ListAssignments(
		ctx, instance.WorkspaceID, instance.ID,
	)
	if err != nil {
		return SubmitResult{}, err
	}
	return handler.result(ctx, command, instance, assignments, false)
}

func (handler *skillDeleteSubjectHandler) result(
	ctx context.Context,
	command SubmitCommand,
	instance approval.Instance,
	assignments []approval.Assignment,
	created bool,
) (SubmitResult, error) {
	public := SkillDeleteResult{ApprovalID: instance.ID}
	switch instance.State {
	case approval.StatePending:
		public.Status = SkillDeletePending
	case approval.StateRejected:
		public.Status = SkillDeleteRejected
	case approval.StateApproved:
		payload, err := decodeSkillDeletePayload(instance)
		if err != nil {
			return SubmitResult{}, err
		}
		if payload.ApplicantAgentID != command.Principal.PrincipalID {
			return SubmitResult{}, storeerr.Conflict(
				"Skill delete Approval applicant does not match",
			)
		}
		if err := handler.authority.Execute(ctx, SkillDeleteAuthorityCommand{
			Principal: command.Principal,
			SkillID:   payload.SkillID,
		}); err != nil {
			return SubmitResult{}, err
		}
		public.Status = SkillDeleteDeleted
	default:
		return SubmitResult{}, storeerr.Conflict(
			"Skill delete Approval is not executable",
		)
	}
	return SubmitResult{
		Instance: instance, Assignments: assignments,
		Created: created, Value: public,
	}, nil
}

func (handler *skillDeleteSubjectHandler) Decide(
	ctx context.Context,
	command DecisionCommand,
) (DecisionResult, error) {
	decided, err := handler.approvals.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
	}
	status := decided.Instance.State
	if decided.Instance.State == approval.StateApproved {
		payload, err := decodeSkillDeletePayload(decided.Instance)
		if err != nil {
			return DecisionResult{}, err
		}
		if err := handler.authority.Execute(ctx, SkillDeleteAuthorityCommand{
			Principal: SubjectPrincipal{
				ActorType:   "agent",
				WorkspaceID: decided.Instance.WorkspaceID,
				PrincipalID: payload.ApplicantAgentID,
				MemberID:    decided.Instance.ApplicantMemberID,
			},
			SkillID: payload.SkillID,
		}); err != nil {
			return DecisionResult{}, err
		}
		status = SkillDeleteDeleted
	}
	return DecisionResult{
		Instance: decided.Instance,
		Replay:   decided.AlreadyApplied,
		Result: map[string]any{
			"approvalId": decided.Instance.ID,
			"status":     status,
		},
	}, nil
}

func (*skillDeleteSubjectHandler) pendingProjection(
	result SubmitResult,
) (approval.Instance, approval.Assignment, skillDeletePayload, error) {
	instance := result.Instance
	payload, err := decodeSkillDeletePayload(instance)
	if err != nil {
		return approval.Instance{}, approval.Assignment{}, skillDeletePayload{}, err
	}
	if instance.State != approval.StatePending || instance.Version <= 0 ||
		len(result.Assignments) != 1 {
		return approval.Instance{}, approval.Assignment{}, skillDeletePayload{}, fmt.Errorf(
			"Skill delete Approval is not pending",
		)
	}
	assignment := result.Assignments[0]
	if assignment.ApprovalInstanceID != instance.ID ||
		assignment.Status != approval.AssignmentPending ||
		strings.TrimSpace(assignment.ApproverMemberID) == "" {
		return approval.Instance{}, approval.Assignment{}, skillDeletePayload{}, fmt.Errorf(
			"Skill delete Approval assignment is invalid",
		)
	}
	return instance, assignment, payload, nil
}

func (*skillDeleteSubjectHandler) resolutionNotificationID(
	instance approval.Instance,
	presentation PresentationContext,
) (string, error) {
	if instance.State != approval.StateApproved &&
		instance.State != approval.StateRejected {
		return "", fmt.Errorf(
			"Skill delete resolved Approval is invalid",
		)
	}
	notificationID := strings.TrimSpace(presentation.NotificationID)
	if notificationID == "" {
		return "", fmt.Errorf(
			"Skill delete action notification is required",
		)
	}
	return notificationID, nil
}

func (handler *skillDeleteSubjectHandler) resolvedAssignment(
	ctx context.Context,
	instance approval.Instance,
	recipientBasisRef string,
) (approval.Assignment, error) {
	assignment, authority, err := handler.approvals.LoadAssignment(
		ctx, instance.WorkspaceID, recipientBasisRef,
	)
	if err != nil {
		return approval.Assignment{}, err
	}
	expectedStatus := approval.AssignmentRejected
	if instance.State == approval.StateApproved {
		expectedStatus = approval.AssignmentApproved
	}
	if authority.ID != instance.ID || authority.Version != instance.Version ||
		authority.State != instance.State ||
		assignment.ApprovalInstanceID != instance.ID ||
		assignment.Status != expectedStatus {
		return approval.Assignment{}, fmt.Errorf(
			"Skill delete action assignment authority does not match",
		)
	}
	return assignment, nil
}

func normalizeSkillDeleteCommand(command SubmitCommand) (SubmitCommand, error) {
	command.Principal.ActorType = strings.TrimSpace(command.Principal.ActorType)
	command.Principal.WorkspaceID = strings.TrimSpace(command.Principal.WorkspaceID)
	command.Principal.PrincipalID = strings.TrimSpace(command.Principal.PrincipalID)
	command.Principal.MemberID = strings.TrimSpace(command.Principal.MemberID)
	command.Principal.MachineID = strings.TrimSpace(command.Principal.MachineID)
	command.ClientRequestID = strings.TrimSpace(command.ClientRequestID)
	command.CorrelationID = strings.TrimSpace(command.CorrelationID)
	if command.Principal.ActorType != "agent" ||
		command.Principal.WorkspaceID == "" ||
		command.Principal.PrincipalID == "" ||
		command.Principal.MemberID == "" ||
		command.Principal.MachineID == "" {
		return SubmitCommand{}, storeerr.Forbidden(
			"Skill delete authenticated Agent identity is incomplete",
		)
	}
	if command.ClientRequestID == "" || len(command.ClientRequestID) > 128 {
		return SubmitCommand{}, storeerr.BadRequest(
			"Skill delete requestId is invalid",
		)
	}
	return command, nil
}

func skillDeleteIdempotencyKey(workspaceID, agentID, requestID string) string {
	sum := sha256.Sum256([]byte(strings.Join([]string{
		"skill-delete-v1",
		strings.TrimSpace(workspaceID),
		strings.TrimSpace(agentID),
		strings.TrimSpace(requestID),
	}, "\x00")))
	return hex.EncodeToString(sum[:])
}

func skillDeleteApprovalID(key string) string {
	return "appr_skill_delete_" + strings.TrimSpace(key)[:40]
}

func encodeSkillDeletePayload(
	payload skillDeletePayload,
) ([]byte, string, error) {
	raw, err := json.Marshal(payload)
	if err != nil {
		return nil, "", err
	}
	deterministic, err := deterministicSkillDeleteJSON(raw)
	if err != nil {
		return nil, "", err
	}
	sum := sha256.Sum256(deterministic)
	return deterministic, hex.EncodeToString(sum[:]), nil
}

func decodeSkillDeletePayload(
	instance approval.Instance,
) (skillDeletePayload, error) {
	if instance.SubjectType != SkillDeleteSubjectType ||
		instance.SubjectVersion != skillDeletePayloadSchema {
		return skillDeletePayload{}, fmt.Errorf(
			"Skill delete Approval subject is invalid",
		)
	}
	var payload skillDeletePayload
	decoder := json.NewDecoder(strings.NewReader(string(instance.Payload)))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&payload); err != nil {
		return skillDeletePayload{}, fmt.Errorf(
			"decode Skill delete Approval payload: %w", err,
		)
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return skillDeletePayload{}, fmt.Errorf(
			"decode Skill delete Approval payload: invalid trailing value",
		)
	}
	payload.ApplicantAgentID = strings.TrimSpace(payload.ApplicantAgentID)
	payload.RequestID = strings.TrimSpace(payload.RequestID)
	payload.SkillID = strings.TrimSpace(payload.SkillID)
	if payload.SchemaVersion != skillDeletePayloadSchema ||
		payload.ApplicantAgentID == "" ||
		payload.RequestID == "" ||
		payload.SkillID == "" {
		return skillDeletePayload{}, fmt.Errorf(
			"Skill delete Approval payload is invalid",
		)
	}
	deterministic, digest, err := encodeSkillDeletePayload(payload)
	if err != nil {
		return skillDeletePayload{}, err
	}
	storedDeterministic, err := deterministicSkillDeleteJSON(instance.Payload)
	if err != nil {
		return skillDeletePayload{}, fmt.Errorf(
			"Skill delete Approval payload is not deterministic JSON: %w", err,
		)
	}
	key := skillDeleteIdempotencyKey(
		instance.WorkspaceID, payload.ApplicantAgentID, payload.RequestID,
	)
	if !json.Valid(instance.Payload) ||
		!bytes.Equal(deterministic, storedDeterministic) ||
		digest != instance.PayloadDigest ||
		instance.SubjectRef != key ||
		instance.IdempotencyKey != key {
		return skillDeletePayload{}, fmt.Errorf(
			"Skill delete Approval payload does not match its immutable identity",
		)
	}
	return payload, nil
}

func deterministicSkillDeleteJSON(raw []byte) ([]byte, error) {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var value any
	if err := decoder.Decode(&value); err != nil {
		return nil, err
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return nil, fmt.Errorf("trailing JSON content")
	}
	return json.Marshal(value)
}

func skillDeleteReviewClientNotificationID(approvalID string) string {
	return "skill-delete:" + strings.TrimSpace(approvalID) + ":review"
}

var _ SubjectSubmitter = (*skillDeleteSubjectHandler)(nil)
var _ SubjectPendingPresenter = (*skillDeleteSubjectHandler)(nil)
var _ SubjectResolvedPresenter = (*skillDeleteSubjectHandler)(nil)
