package serviceaccount

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	serviceaccountstore "github.com/tidbcloud/AgentTeam/internal/store/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

const (
	// PrivilegedWorkspaceCreateCancellationReason is the durable sentinel used
	// when an ordinary member is promoted after opening a Service Account create
	// Approval. The immutable authorization path is retained, but its old
	// payload is never executed: administrators use a fresh direct-create
	// command instead.
	PrivilegedWorkspaceCreateCancellationReason = "Withdrawn by system: workspace administrators use direct Service Account management."
)

// IsPrivilegedWorkspaceCreateCancellation identifies only the system-owned
// role-promotion cancellation. Human cancellation fields must not suppress an
// ordinary terminal result merely because their text resembles the sentinel.
func IsPrivilegedWorkspaceCreateCancellation(request serviceaccountstore.CreateRequest) bool {
	digest := sha256.Sum256([]byte(PrivilegedWorkspaceCreateCancellationReason))
	return strings.TrimSpace(request.AuthorizationPath) == serviceaccountstore.AuthorizationPathApprovalRequired &&
		strings.TrimSpace(request.BusinessState) == serviceaccountstore.RequestStateCancelled &&
		strings.TrimSpace(request.CancelApprovalReceiptRef) == PrivilegedWorkspaceCreateCancellationReason &&
		strings.TrimSpace(request.CancelClientMutationID) == "system-role-promotion:"+strings.TrimSpace(request.ID) &&
		strings.TrimSpace(request.CancelPayloadDigest) == hex.EncodeToString(digest[:]) &&
		request.CancelResultVersion == request.Version &&
		strings.TrimSpace(request.CreatedServiceAccountID) == ""
}

// SettlePrivilegedWorkspaceCreateRequest is the trusted recovery boundary used
// by request replay and pending projection. It rechecks the canonical requester
// role under lock and atomically cancels an obsolete approval-required request,
// its generic Approval authority, and every still-visible Approval card.
func (service *Service) SettlePrivilegedWorkspaceCreateRequest(
	ctx context.Context,
	workspaceID string,
	requestID string,
	now time.Time,
) (WorkspaceCreateRequestResult, bool, error) {
	if err := service.validate(); err != nil {
		return WorkspaceCreateRequestResult{}, false, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	requestID = strings.TrimSpace(requestID)
	if workspaceID == "" || requestID == "" {
		return WorkspaceCreateRequestResult{}, false, storeerr.BadRequest("workspaceId and requestId are required")
	}
	preview, err := service.accounts.LoadCreateRequest(ctx, workspaceID, requestID)
	if err != nil {
		return WorkspaceCreateRequestResult{}, false, err
	}
	return service.settlePrivilegedWorkspaceCreateRequest(ctx, preview, normalizedNow(now))
}

// SettlePrivilegedWorkspaceCreateApproval is the action-delivery fence. The
// Approval knows the generic Approval id before it knows the business
// request id, so this lookup resolves the immutable binding and then uses the
// same transactional settlement as request replay and projection.
func (service *Service) SettlePrivilegedWorkspaceCreateApproval(
	ctx context.Context,
	workspaceID string,
	approvalID string,
	now time.Time,
) (WorkspaceCreateRequestResult, bool, error) {
	if err := service.validate(); err != nil {
		return WorkspaceCreateRequestResult{}, false, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	approvalID = strings.TrimSpace(approvalID)
	if workspaceID == "" || approvalID == "" {
		return WorkspaceCreateRequestResult{}, false, storeerr.BadRequest("workspaceId and approvalId are required")
	}
	var requestID string
	if err := service.db.QueryRow(ctx, `
SELECT id
FROM service_account_create_requests
WHERE workspace_id = $1
  AND approval_instance_id = $2
LIMIT 1
`, workspaceID, approvalID).Scan(&requestID); err != nil {
		if coredb.IsNotFound(err) {
			return WorkspaceCreateRequestResult{}, false, storeerr.NotFound("service account create request not found")
		}
		return WorkspaceCreateRequestResult{}, false, fmt.Errorf("resolve service account create request by Approval: %w", err)
	}
	return service.SettlePrivilegedWorkspaceCreateRequest(ctx, workspaceID, requestID, now)
}

// ReconcilePrivilegedWorkspaceCreateRequests is the bounded durable-gap scan
// used by the Service Account catalog read path. It deliberately selects
// promoted requesters even when every card was projected successfully: role
// promotion itself is the gap that must converge. Individual settlements are
// idempotent, so a later catalog read safely resumes after a transient error.
func (service *Service) ReconcilePrivilegedWorkspaceCreateRequests(
	ctx context.Context,
	workspaceID string,
	limit int,
	now time.Time,
) (int, error) {
	if err := service.validate(); err != nil {
		return 0, err
	}
	workspaceID = strings.TrimSpace(workspaceID)
	if workspaceID == "" {
		return 0, storeerr.BadRequest("workspaceId is required")
	}
	if limit <= 0 || limit > 100 {
		return 0, storeerr.BadRequest("limit must be between 1 and 100")
	}
	rows, err := service.db.Query(ctx, `
SELECT request.id
FROM service_account_create_requests request
JOIN members requester
  ON requester.workspace_id = request.workspace_id
 AND requester.id = request.requested_by_member_id
 AND requester.type = 'human'
WHERE request.workspace_id = $1
  AND request.authorization_path = 'approval_required'
  AND request.business_state = 'awaiting_authorization'
  AND request.created_service_account_id IS NULL
  AND requester.workspace_role IN ('owner', 'admin')
ORDER BY request.updated_at ASC, request.id ASC
LIMIT $2
`, workspaceID, limit)
	if err != nil {
		return 0, fmt.Errorf("list promoted Service Account create request gaps: %w", err)
	}
	requestIDs := make([]string, 0)
	for rows.Next() {
		var requestID string
		if err := rows.Scan(&requestID); err != nil {
			rows.Close()
			return 0, fmt.Errorf("scan promoted Service Account create request gap: %w", err)
		}
		requestIDs = append(requestIDs, strings.TrimSpace(requestID))
	}
	if err := rows.Err(); err != nil {
		rows.Close()
		return 0, fmt.Errorf("iterate promoted Service Account create request gaps: %w", err)
	}
	rows.Close()

	settledCount := 0
	for _, requestID := range requestIDs {
		_, settled, err := service.SettlePrivilegedWorkspaceCreateRequest(ctx, workspaceID, requestID, now)
		if err != nil {
			return settledCount, err
		}
		if settled {
			settledCount++
		}
	}
	return settledCount, nil
}

func (service *Service) settlePrivilegedWorkspaceCreateRequest(
	ctx context.Context,
	preview serviceaccountstore.CreateRequest,
	now time.Time,
) (WorkspaceCreateRequestResult, bool, error) {
	var result WorkspaceCreateRequestResult
	settled := false
	err := coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		workspaceRole, err := lockWorkspaceCreateRequesterRole(
			ctx, tx, preview.WorkspaceID, preview.RequestedByMemberID,
		)
		if err != nil {
			return err
		}
		request, err := service.accounts.InTx(tx).LoadCreateRequestForUpdate(
			ctx, preview.WorkspaceID, preview.ID,
		)
		if err != nil {
			return err
		}
		if request.RequestedByMemberID != preview.RequestedByMemberID {
			return storeerr.Conflict("service account create request requester changed")
		}
		result.Request = request
		if IsPrivilegedWorkspaceCreateCancellation(request) {
			settled = true
			return nil
		}
		if request.AuthorizationPath != serviceaccountstore.AuthorizationPathApprovalRequired ||
			request.BusinessState != serviceaccountstore.RequestStateAwaitingAuthorization ||
			strings.TrimSpace(request.ApprovalInstanceID) == "" {
			return loadWorkspaceCreateApprovalResultInTx(ctx, service.db, tx, request, &result)
		}

		instance, assignments, err := lockBoundWorkspaceCreateApprovalInTx(ctx, service.db, tx, request)
		if err != nil {
			return err
		}
		result.Approval = &instance
		result.Assignments = assignments
		if !access.HasWorkspaceAdminRole(domain.WorkspaceRole(workspaceRole)) {
			return nil
		}
		request, instance, assignments, err = settleKnownPrivilegedWorkspaceCreateRequestInTx(
			ctx, tx, request, instance, assignments, now,
		)
		if err != nil {
			return err
		}
		result.Request = request
		result.Approval = &instance
		result.Assignments = assignments
		settled = true
		return nil
	})
	if err != nil {
		return WorkspaceCreateRequestResult{}, false, err
	}
	return result, settled, nil
}

func lockWorkspaceCreateRequesterRole(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	memberID string,
) (string, error) {
	var workspaceRole string
	err := tx.QueryRow(ctx, `
SELECT workspace_role
FROM members
WHERE workspace_id = $1
  AND id = $2
  AND type = 'human'
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(workspaceID), strings.TrimSpace(memberID)).Scan(&workspaceRole)
	if err != nil {
		if coredb.IsNotFound(err) {
			return "", storeerr.Conflict("service account create requester is no longer a workspace Human")
		}
		return "", fmt.Errorf("lock service account create requester role: %w", err)
	}
	return strings.TrimSpace(workspaceRole), nil
}

func loadWorkspaceCreateApprovalResultInTx(
	ctx context.Context,
	db coredb.DB,
	tx coredb.Tx,
	request serviceaccountstore.CreateRequest,
	result *WorkspaceCreateRequestResult,
) error {
	if strings.TrimSpace(request.ApprovalInstanceID) == "" {
		return nil
	}
	instance, assignments, err := lockBoundWorkspaceCreateApprovalInTx(ctx, db, tx, request)
	if err != nil {
		return err
	}
	result.Approval = &instance
	result.Assignments = assignments
	return nil
}

func lockBoundWorkspaceCreateApprovalInTx(
	ctx context.Context,
	db coredb.DB,
	tx coredb.Tx,
	request serviceaccountstore.CreateRequest,
) (approval.Instance, []approval.Assignment, error) {
	approvalStore := approval.New(db).InTx(tx)
	instance, err := approvalStore.Load(ctx, request.WorkspaceID, request.ApprovalInstanceID)
	if err != nil {
		return approval.Instance{}, nil, err
	}
	if instance.SubjectType != approvalSubjectServiceAccountCreateRequest ||
		instance.SubjectRef != request.ID ||
		instance.SubjectVersion != request.RequestVersion ||
		instance.PayloadDigest != request.PayloadDigest ||
		instance.ApplicantMemberID != request.RequestedByMemberID {
		return approval.Instance{}, nil, storeerr.Conflict("Approval immutable subject does not match the service account create request")
	}
	assignments, err := approvalStore.ListAssignments(ctx, request.WorkspaceID, instance.ID)
	if err != nil {
		return approval.Instance{}, nil, err
	}
	return instance, assignments, nil
}

func settleKnownPrivilegedWorkspaceCreateRequestInTx(
	ctx context.Context,
	tx coredb.Tx,
	request serviceaccountstore.CreateRequest,
	instance approval.Instance,
	assignments []approval.Assignment,
	now time.Time,
) (serviceaccountstore.CreateRequest, approval.Instance, []approval.Assignment, error) {
	now = normalizedNow(now)
	if request.AuthorizationPath != serviceaccountstore.AuthorizationPathApprovalRequired ||
		request.BusinessState != serviceaccountstore.RequestStateAwaitingAuthorization ||
		request.ApprovalInstanceID != instance.ID ||
		strings.TrimSpace(request.CreatedServiceAccountID) != "" {
		return request, instance, assignments, storeerr.Conflict("service account create request can no longer be cancelled after role promotion")
	}

	if instance.State == approval.StatePending {
		updated, err := tx.Exec(ctx, `
UPDATE approval_instances
SET state = 'cancelled',
    decision = 'cancelled',
    decision_reason = $4,
    decided_by_member_id = NULL,
    version = version + 1,
    updated_at = $5,
    decided_at = $5
WHERE workspace_id = $1
  AND id = $2
  AND version = $3
  AND state = 'pending'
`, request.WorkspaceID, instance.ID, instance.Version,
			PrivilegedWorkspaceCreateCancellationReason, now)
		if err != nil {
			return request, instance, assignments, fmt.Errorf("cancel obsolete Service Account Approval: %w", err)
		}
		if updated.RowsAffected() != 1 {
			return request, instance, assignments, storeerr.Conflict("Service Account Approval version changed")
		}
		instance.State = approval.StateCancelled
		instance.Decision = approval.StateCancelled
		instance.DecisionReason = PrivilegedWorkspaceCreateCancellationReason
		instance.DecidedByMemberID = ""
		instance.Version++
		instance.UpdatedAt = now
		instance.DecidedAt = &now
	}

	// A terminal generic Approval is immutable audit evidence. In the narrow
	// race where it reached approved/rejected before this role fence, retain the
	// terminal decision and only cancel the still-awaiting business request.
	// Any assignment that is genuinely still pending is no longer actionable.
	if _, err := tx.Exec(ctx, `
UPDATE approval_assignments
SET status = 'cancelled',
    version = version + 1,
    updated_at = $3,
    closed_at = COALESCE(closed_at, $3)
WHERE workspace_id = $1
  AND approval_instance_id = $2
  AND status = 'pending'
`, request.WorkspaceID, instance.ID, now); err != nil {
		return request, instance, assignments, fmt.Errorf("cancel obsolete Service Account Approval assignments: %w", err)
	}
	for index := range assignments {
		if assignments[index].Status != approval.AssignmentPending {
			continue
		}
		assignments[index].Status = approval.AssignmentCancelled
		assignments[index].Version++
		assignments[index].UpdatedAt = now
		if assignments[index].ClosedAt == nil {
			closedAt := now
			assignments[index].ClosedAt = &closedAt
		}
	}

	digest := sha256.Sum256([]byte(PrivilegedWorkspaceCreateCancellationReason))
	clientMutationID := "system-role-promotion:" + request.ID
	updated, err := tx.Exec(ctx, `
UPDATE service_account_create_requests
SET business_state = 'cancelled',
    consumed_decision_version = $4,
    cancel_client_mutation_id = $5,
    cancel_payload_digest = $6,
    cancel_approval_receipt_ref = $7,
    cancel_result_version = version + 1,
    version = version + 1,
    updated_at = $8,
    cancelled_at = $8
WHERE workspace_id = $1
  AND id = $2
  AND version = $3
  AND business_state = 'awaiting_authorization'
  AND created_service_account_id IS NULL
`, request.WorkspaceID, request.ID, request.Version, instance.Version,
		clientMutationID, hex.EncodeToString(digest[:]),
		PrivilegedWorkspaceCreateCancellationReason, now)
	if err != nil {
		return request, instance, assignments, fmt.Errorf("cancel obsolete Service Account create request: %w", err)
	}
	if updated.RowsAffected() != 1 {
		return request, instance, assignments, storeerr.Conflict("service account create request version changed")
	}
	request.BusinessState = serviceaccountstore.RequestStateCancelled
	request.ConsumedDecisionVersion = instance.Version
	request.CancelClientMutationID = clientMutationID
	request.CancelPayloadDigest = hex.EncodeToString(digest[:])
	request.CancelApprovalReceiptRef = PrivilegedWorkspaceCreateCancellationReason
	request.CancelResultVersion = request.Version + 1
	request.Version++
	request.UpdatedAt = now
	request.CancelledAt = &now

	if err := withdrawPrivilegedWorkspaceCreateNotificationsInTx(ctx, tx, request, instance.Version, now); err != nil {
		return request, instance, assignments, err
	}
	return request, instance, assignments, nil
}

func withdrawPrivilegedWorkspaceCreateNotificationsInTx(
	ctx context.Context,
	tx coredb.Tx,
	request serviceaccountstore.CreateRequest,
	businessObjectVersion int64,
	now time.Time,
) error {
	if _, err := tx.Exec(ctx, `
UPDATE service_notifications
SET status = 'withdrawn',
    revision = revision + 1,
    business_object_version = $3,
    withdrawn_at = $4,
    updated_at = $4
WHERE workspace_id = $1
  AND business_object_namespace = 'agentteam.approval'
  AND business_object_type = 'approval_instance'
  AND business_object_id = $2
  AND recipient_basis_kind IN ('approval_assignment', 'approval_applicant')
  AND content_kind = 'interactive_card'
  AND status = 'active'
`, request.WorkspaceID, request.ApprovalInstanceID, businessObjectVersion, now); err != nil {
		return fmt.Errorf("withdraw obsolete Service Account Approval notifications: %w", err)
	}
	if _, err := tx.Exec(ctx, `
UPDATE service_notification_cards card
JOIN service_notifications notification
  ON notification.id = card.service_notification_id
SET card.state = 'withdrawn',
    card.card_revision = card.card_revision + 1,
    card.updated_at = $3
WHERE notification.workspace_id = $1
  AND notification.business_object_namespace = 'agentteam.approval'
  AND notification.business_object_type = 'approval_instance'
  AND notification.business_object_id = $2
  AND notification.recipient_basis_kind IN ('approval_assignment', 'approval_applicant')
  AND notification.content_kind = 'interactive_card'
  AND notification.status = 'withdrawn'
  AND card.state = 'active'
`, request.WorkspaceID, request.ApprovalInstanceID, now); err != nil {
		return fmt.Errorf("withdraw obsolete Service Account Approval cards: %w", err)
	}
	return nil
}
