package approvalapp

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

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

type wikiGenerationMembers interface {
	ResolveMember(context.Context, string, string) (string, error)
	ResolveUser(context.Context, string, string) (string, error)
	ListApprovers(context.Context, string) ([]string, error)
}

type publicationApproverAssignment struct {
	MemberID         string
	PrincipalID      string
	DisplayName      string
	MemberStatus     string
	AssignmentStatus string
}

type coreWikiGenerationMembers struct{ db coredb.DB }

func (members coreWikiGenerationMembers) ResolveMember(ctx context.Context, workspaceID, userID string) (string, error) {
	membership, err := access.LoadHumanWorkspaceMember(ctx, members.db, strings.TrimSpace(userID), strings.TrimSpace(workspaceID))
	if err != nil {
		if errors.Is(err, access.ErrForbidden) {
			return "", storeerr.Forbidden("active workspace membership is required")
		}
		return "", err
	}
	return membership.Member.ID, nil
}

func (members coreWikiGenerationMembers) ResolveUser(ctx context.Context, workspaceID, memberID string) (string, error) {
	var userID string
	err := members.db.QueryRow(ctx, `
SELECT user_id
FROM members
WHERE workspace_id=$1 AND id=$2 AND type='human' AND status='active'
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(memberID)).Scan(&userID)
	if err != nil {
		if coredb.IsNotFound(err) {
			return "", storeerr.Forbidden("active workspace membership is required")
		}
		return "", fmt.Errorf("resolve Wiki Approval member user: %w", err)
	}
	return strings.TrimSpace(userID), nil
}

func (members coreWikiGenerationMembers) ListApprovers(ctx context.Context, workspaceID string) ([]string, error) {
	rows, err := members.db.Query(ctx, `
SELECT id
FROM members
WHERE workspace_id=$1 AND type='human' AND status='active'
  AND workspace_role IN ('owner','admin')
ORDER BY id
LIMIT 101
`, strings.TrimSpace(workspaceID))
	if err != nil {
		return nil, fmt.Errorf("list Wiki Approval reviewers: %w", err)
	}
	defer rows.Close()
	approvers := make([]string, 0, 4)
	for rows.Next() {
		var memberID string
		if err := rows.Scan(&memberID); err != nil {
			return nil, fmt.Errorf("scan Wiki Approval reviewer: %w", err)
		}
		approvers = append(approvers, memberID)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate Wiki Approval reviewers: %w", err)
	}
	if len(approvers) == 0 {
		return nil, storeerr.Conflict("Wiki generation requires an active workspace owner or admin reviewer")
	}
	if len(approvers) > 100 {
		return nil, storeerr.Conflict("Wiki generation has too many workspace reviewers")
	}
	return approvers, nil
}

func (members coreWikiGenerationMembers) ListPublicationApprovers(ctx context.Context, workspaceID, approvalID string) ([]publicationApproverAssignment, error) {
	rows, err := members.db.Query(ctx, `
SELECT assignment.approver_member_id, member.user_id, member.display_name,
       member.status, assignment.status
FROM approval_assignments AS assignment
JOIN members AS member
  ON member.workspace_id = assignment.workspace_id
 AND member.id = assignment.approver_member_id
 AND member.type = 'human'
WHERE assignment.workspace_id = $1
  AND assignment.approval_instance_id = $2
ORDER BY assignment.id
LIMIT 101
`, strings.TrimSpace(workspaceID), strings.TrimSpace(approvalID))
	if err != nil {
		return nil, fmt.Errorf("list publication Approval assignments: %w", err)
	}
	defer rows.Close()
	assignments := make([]publicationApproverAssignment, 0, 4)
	for rows.Next() {
		var assignment publicationApproverAssignment
		if err := rows.Scan(&assignment.MemberID, &assignment.PrincipalID, &assignment.DisplayName, &assignment.MemberStatus, &assignment.AssignmentStatus); err != nil {
			return nil, fmt.Errorf("scan publication Approval assignment: %w", err)
		}
		assignments = append(assignments, assignment)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate publication Approval assignments: %w", err)
	}
	if len(assignments) > 100 {
		return nil, storeerr.Conflict("publication Approval has too many assignments")
	}
	return assignments, nil
}

// WikiGenerationSubjectHandler adapts MeshX's revision-fenced generation
// request to AgentTeam's generic Approval authority.
type WikiGenerationSubjectHandler struct {
	approvals *approval.Store
	authority kbgovernance.WikiGenerationAuthority
	members   wikiGenerationMembers
	projector *Projector
}

func NewWikiGenerationSubjectHandler(approvals *approval.Store, authority kbgovernance.WikiGenerationAuthority, db coredb.DB, projector *Projector) *WikiGenerationSubjectHandler {
	return &WikiGenerationSubjectHandler{approvals: approvals, authority: authority, members: coreWikiGenerationMembers{db: db}, projector: projector}
}

func (*WikiGenerationSubjectHandler) SubjectType() string {
	return kbgovernance.WikiGenerationSubjectType
}
func (*WikiGenerationSubjectHandler) RequestType() string {
	return kbgovernance.WikiGenerationRequestType
}
func (*WikiGenerationSubjectHandler) CancelPolicy() SubjectCancelPolicy {
	return SubjectCancelByApplicant
}
func (handler *WikiGenerationSubjectHandler) ReconcilePolicy() SubjectReconcilePolicy {
	if handler == nil || handler.projector == nil {
		return SubjectReconcileNone
	}
	return SubjectReconcileProjection
}

func (handler *WikiGenerationSubjectHandler) Validate(_ context.Context, instance approval.Instance) error {
	if handler == nil || handler.approvals == nil || handler.authority == nil || handler.members == nil {
		return fmt.Errorf("Wiki generation Approval subject handler is not configured")
	}
	if instance.SubjectType != kbgovernance.WikiGenerationSubjectType || instance.SubjectVersion != kbgovernance.WikiGenerationPayloadSchema {
		return fmt.Errorf("Wiki generation Approval subject is invalid")
	}
	var payload kbgovernance.WikiGenerationPayload
	if err := json.Unmarshal(instance.Payload, &payload); err != nil || payload.SchemaVersion != kbgovernance.WikiGenerationPayloadSchema {
		return fmt.Errorf("Wiki generation Approval payload is invalid")
	}
	if payload.WorkspaceID != instance.WorkspaceID || strings.TrimSpace(instance.SubjectRef) == "" {
		return fmt.Errorf("Wiki generation Approval scope is invalid")
	}
	return nil
}

func (*WikiGenerationSubjectHandler) DecodeSubmitPayload(value any) (any, error) {
	prepared, ok := value.(kbgovernance.PreparedWikiGeneration)
	if !ok {
		return nil, storeerr.BadRequest("Wiki generation Approval payload is invalid")
	}
	return prepared, nil
}

func (handler *WikiGenerationSubjectHandler) Submit(ctx context.Context, command SubmitCommand) (SubmitResult, error) {
	prepared, ok := command.Payload.(kbgovernance.PreparedWikiGeneration)
	if !ok {
		return SubmitResult{}, storeerr.BadRequest("Wiki generation Approval payload is invalid")
	}
	payload := prepared.Payload
	if err := validatePreparedWikiGeneration(prepared); err != nil {
		return SubmitResult{}, err
	}
	if command.Principal.ActorType != "human" || command.Principal.WorkspaceID != payload.WorkspaceID ||
		command.Principal.PrincipalID != payload.RequesterID || command.Principal.MemberID == "" {
		return SubmitResult{}, storeerr.Forbidden("Wiki generation applicant does not match the authenticated Human")
	}
	idempotencyKey := wikiApprovalHash("request", payload.WorkspaceID, command.Principal.MemberID, payload.IdempotencyKey)
	approvalID := "appr_" + idempotencyKey[:40]
	requestID := "kbgen_" + idempotencyKey[:40]
	approvers, err := handler.replayApprovers(ctx, payload.WorkspaceID, command.Principal.MemberID, idempotencyKey)
	if err != nil {
		return SubmitResult{}, err
	}
	instance, assignments, created, err := handler.approvals.Create(ctx, approval.CreateParams{
		ID: approvalID, WorkspaceID: payload.WorkspaceID,
		SubjectType: kbgovernance.WikiGenerationSubjectType, SubjectRef: requestID,
		SubjectVersion: kbgovernance.WikiGenerationPayloadSchema,
		Payload:        prepared.PayloadJSON, PayloadDigest: prepared.PayloadDigest,
		IdempotencyKey:    idempotencyKey,
		PendingKey:        wikiApprovalHash("source", payload.WorkspaceID, payload.KnowledgeBaseID, payload.SourceID),
		ApplicantMemberID: command.Principal.MemberID, ApproverMemberIDs: approvers,
		CorrelationID: prepared.CorrelationID, Now: prepared.PreparedAt,
	})
	if err != nil {
		return SubmitResult{}, err
	}
	state, err := handler.authority.RecordWikiGenerationApproval(ctx, kbgovernance.RecordWikiApprovalCommand{
		RequestID: requestID, ApprovalID: instance.ID, ApprovalVersion: instance.Version,
		Prepared: prepared, Now: command.Now,
	})
	if err != nil {
		return SubmitResult{}, err
	}
	return SubmitResult{Instance: instance, Assignments: assignments, Created: created, Value: state}, nil
}

func validatePreparedWikiGeneration(prepared kbgovernance.PreparedWikiGeneration) error {
	payload := prepared.Payload
	if payload.SchemaVersion != kbgovernance.WikiGenerationPayloadSchema ||
		strings.TrimSpace(payload.WorkspaceID) == "" || strings.TrimSpace(payload.KnowledgeBaseID) == "" ||
		strings.TrimSpace(payload.SourceID) == "" || strings.TrimSpace(payload.SourceRevision) == "" ||
		strings.TrimSpace(payload.SourceCommitSHA) == "" || strings.TrimSpace(payload.RequesterID) == "" ||
		strings.TrimSpace(payload.IdempotencyKey) == "" {
		return storeerr.BadRequest("Wiki generation Approval payload is incomplete")
	}
	canonical, digest, err := kbgovernance.EncodePayload(payload)
	if err != nil {
		return fmt.Errorf("encode Wiki generation Approval payload: %w", err)
	}
	if !bytes.Equal(canonical, prepared.PayloadJSON) || digest != prepared.PayloadDigest {
		return storeerr.BadRequest("Wiki generation Approval payload digest does not match its canonical payload")
	}
	return nil
}

func (handler *WikiGenerationSubjectHandler) replayApprovers(ctx context.Context, workspaceID, memberID, key string) ([]string, error) {
	existing, err := handler.approvals.LoadByIdempotencyKey(ctx, workspaceID, memberID, key)
	if err == nil {
		assignments, err := handler.approvals.ListAssignments(ctx, workspaceID, existing.ID)
		if err != nil {
			return nil, err
		}
		approvers := make([]string, 0, len(assignments))
		for _, assignment := range assignments {
			approvers = append(approvers, assignment.ApproverMemberID)
		}
		return approvers, nil
	}
	if !errors.Is(err, storeerr.ErrNotFound) {
		return nil, err
	}
	return handler.members.ListApprovers(ctx, workspaceID)
}

func (handler *WikiGenerationSubjectHandler) Decide(ctx context.Context, command DecisionCommand) (DecisionResult, error) {
	if command.Decision == approval.DecisionRejected && strings.TrimSpace(command.Reason) == "" {
		return DecisionResult{}, storeerr.BadRequest("Wiki generation rejection reason is required")
	}
	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
	}
	actorUserID, err := handler.members.ResolveUser(ctx, command.WorkspaceID, command.ActorMemberID)
	if err != nil {
		return DecisionResult{}, err
	}
	state, err := handler.authority.ResolveWikiGenerationApproval(ctx, kbgovernance.ResolveWikiApprovalCommand{
		WorkspaceID: command.WorkspaceID, ApprovalID: command.ApprovalID,
		ApprovalVersion: decided.Receipt.ResultVersion, ActorPrincipalID: actorUserID,
		ClientDecisionID: command.ClientDecisionID, Decision: command.Decision,
		Reason: command.Reason, Now: command.Now,
	})
	if err != nil {
		return DecisionResult{}, err
	}
	if state.Stale {
		reconciled, err := handler.approvals.ReconcileApprovedDecisionAsStale(ctx, approval.ApprovedStaleParams{
			WorkspaceID: command.WorkspaceID, ApprovalID: command.ApprovalID,
			SubjectType: kbgovernance.WikiGenerationSubjectType, ActorMemberID: command.ActorMemberID,
			ClientDecisionID: command.ClientDecisionID, ApprovedVersion: decided.Receipt.ResultVersion,
			Reason: "stale_subject", Now: command.Now,
		})
		if err != nil {
			return DecisionResult{}, err
		}
		decided = reconciled
	}
	return DecisionResult{Instance: decided.Instance, Replay: decided.AlreadyApplied, Value: state}, nil
}

func (handler *WikiGenerationSubjectHandler) Cancel(ctx context.Context, command CancelCommand) (DecisionResult, error) {
	cancelled, err := handler.approvals.CancelByApplicant(ctx, approval.ApplicantCancelParams{
		WorkspaceID: command.Principal.WorkspaceID, ApprovalID: command.ApprovalID,
		ActorMemberID: command.Principal.MemberID, ExpectedVersion: command.ExpectedVersion,
		ClientCancellationID: command.ClientRequestID, Reason: "withdrawn_by_applicant", Now: command.Now,
	})
	if err != nil {
		return DecisionResult{}, err
	}
	state, err := handler.authority.ResolveWikiGenerationApproval(ctx, kbgovernance.ResolveWikiApprovalCommand{
		WorkspaceID: command.Principal.WorkspaceID, ApprovalID: command.ApprovalID,
		ApprovalVersion: cancelled.Instance.Version, ActorPrincipalID: command.Principal.PrincipalID,
		ClientDecisionID: command.ClientRequestID, Decision: kbgovernance.DecisionWithdrawn,
		Reason: "", Now: command.Now,
	})
	if err != nil {
		return DecisionResult{}, err
	}
	return DecisionResult{Instance: cancelled.Instance, Replay: cancelled.AlreadyApplied, Value: state}, nil
}

func wikiApprovalHash(parts ...string) string {
	digest := sha256.Sum256([]byte(strings.Join(parts, "\x00")))
	return hex.EncodeToString(digest[:])
}

// WikiGenerationApprovalGateway is the authenticated AgentTeam-side entry point
// injected into MeshX. It resolves member and assignment facts server-side.
type WikiGenerationApprovalGateway struct {
	registry             *SubjectRegistry
	approvals            *approval.Store
	members              wikiGenerationMembers
	projectNotifications bool
}

func NewWikiGenerationApprovalGateway(registry *SubjectRegistry, approvals *approval.Store, db coredb.DB, projector *Projector) *WikiGenerationApprovalGateway {
	return &WikiGenerationApprovalGateway{
		registry: registry, approvals: approvals, members: coreWikiGenerationMembers{db: db},
		projectNotifications: projector != nil,
	}
}

func (gateway *WikiGenerationApprovalGateway) SubmitWikiGeneration(ctx context.Context, command kbgovernance.SubmitWikiGenerationCommand) (kbgovernance.WikiGenerationState, error) {
	payload := command.Prepared.Payload
	memberID, err := gateway.members.ResolveMember(ctx, payload.WorkspaceID, command.ActorPrincipalID)
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	result, err := gateway.registry.SubmitRequest(ctx, kbgovernance.WikiGenerationRequestType, SubmitCommand{
		Principal:       SubjectPrincipal{ActorType: "human", WorkspaceID: payload.WorkspaceID, PrincipalID: command.ActorPrincipalID, MemberID: memberID},
		ClientRequestID: payload.IdempotencyKey, Payload: command.Prepared,
		CorrelationID: command.Prepared.CorrelationID, Now: command.Prepared.PreparedAt,
	})
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	if gateway.projectNotifications && result.Instance.State == approval.StatePending {
		if _, err := gateway.registry.PresentPending(ctx, result); err != nil {
			return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
		}
	}
	return wikiGenerationValue(result.Value)
}

func (gateway *WikiGenerationApprovalGateway) DecideWikiGeneration(ctx context.Context, command kbgovernance.DecideWikiGenerationCommand) (kbgovernance.WikiGenerationState, error) {
	memberID, err := gateway.members.ResolveMember(ctx, command.WorkspaceID, command.ActorPrincipalID)
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	assignmentID, err := gateway.assignmentForMember(ctx, command.WorkspaceID, command.ApprovalID, memberID)
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	result, err := gateway.registry.Decide(ctx, DecisionCommand{
		WorkspaceID: command.WorkspaceID, ApprovalID: command.ApprovalID,
		AssignmentID: assignmentID, ActorMemberID: memberID, ExpectedVersion: command.ExpectedVersion,
		ClientDecisionID: command.ClientDecisionID, Decision: command.Decision,
		Reason: command.Reason, CorrelationID: command.CorrelationID, Now: command.Now,
	})
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	if gateway.projectNotifications {
		if _, err := gateway.registry.PresentResolved(ctx, result, PresentationContext{}); err != nil {
			return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
		}
	}
	return wikiGenerationValue(result.Value)
}

func (gateway *WikiGenerationApprovalGateway) WithdrawWikiGeneration(ctx context.Context, command kbgovernance.WithdrawWikiGenerationCommand) (kbgovernance.WikiGenerationState, error) {
	memberID, err := gateway.members.ResolveMember(ctx, command.WorkspaceID, command.ActorPrincipalID)
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	result, err := gateway.registry.Cancel(ctx, CancelCommand{
		Principal:  SubjectPrincipal{ActorType: "human", WorkspaceID: command.WorkspaceID, PrincipalID: command.ActorPrincipalID, MemberID: memberID},
		ApprovalID: command.ApprovalID, ExpectedVersion: command.ExpectedVersion,
		ClientRequestID: command.ClientRequestID, Now: command.Now,
	})
	if err != nil {
		return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
	}
	if gateway.projectNotifications {
		if _, err := gateway.registry.PresentResolved(ctx, result, PresentationContext{}); err != nil {
			return kbgovernance.WikiGenerationState{}, wikiGatewayError(err)
		}
	}
	return wikiGenerationValue(result.Value)
}

func (gateway *WikiGenerationApprovalGateway) assignmentForMember(ctx context.Context, workspaceID, approvalID, memberID string) (string, error) {
	assignments, err := gateway.approvals.ListAssignments(ctx, workspaceID, approvalID)
	if err != nil {
		return "", err
	}
	for _, assignment := range assignments {
		if assignment.ApproverMemberID == memberID {
			return assignment.ID, nil
		}
	}
	return "", storeerr.Forbidden("Wiki generation Approval is not assigned to the current Human")
}

func wikiGenerationValue(value any) (kbgovernance.WikiGenerationState, error) {
	state, ok := value.(kbgovernance.WikiGenerationState)
	if !ok {
		return kbgovernance.WikiGenerationState{}, fmt.Errorf("Wiki generation Approval returned an invalid result")
	}
	return state, nil
}

func wikiGatewayError(err error) error {
	kind := ""
	switch {
	case errors.Is(err, storeerr.ErrBadRequest), errors.Is(err, storeerr.ErrUnprocessable):
		kind = kbgovernance.ErrorInvalidArgument
	case errors.Is(err, storeerr.ErrForbidden):
		kind = kbgovernance.ErrorForbidden
	case errors.Is(err, storeerr.ErrNotFound):
		kind = kbgovernance.ErrorNotFound
	case errors.Is(err, storeerr.ErrConflict), errors.Is(err, storeerr.ErrGone):
		kind = kbgovernance.ErrorConflict
	default:
		return err
	}
	return kbgovernance.GatewayError{Kind: kind, Code: "kb-wiki-approval-" + strings.ReplaceAll(kind, "_", "-"), Message: err.Error()}
}

var (
	_ SubjectHandler                             = (*WikiGenerationSubjectHandler)(nil)
	_ SubjectSubmitter                           = (*WikiGenerationSubjectHandler)(nil)
	_ SubjectCanceller                           = (*WikiGenerationSubjectHandler)(nil)
	_ kbgovernance.WikiGenerationApprovalGateway = (*WikiGenerationApprovalGateway)(nil)
)
