package serviceaccount

import (
	"context"
	"encoding/json"
	"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"
)

// ConsumeApprovalDecision is the Service Account domain command applied after
// the generic Approval authority reaches a terminal version.
type ConsumeApprovalDecision struct {
	WorkspaceID     string
	RequestID       string
	ApprovalID      string
	DecisionVersion int64
	CorrelationID   string
	Now             time.Time
}

// ConsumeWorkspaceCreateApproval validates a terminal Approval snapshot and
// atomically advances the create request. Approved decisions materialize the
// account and attach its unique ref; rejected decisions only close the request.
func (service *Service) ConsumeWorkspaceCreateApproval(ctx context.Context, input ConsumeApprovalDecision) (WorkspaceCreateRequestResult, error) {
	if err := service.validate(); err != nil {
		return WorkspaceCreateRequestResult{}, err
	}
	input.WorkspaceID = strings.TrimSpace(input.WorkspaceID)
	input.RequestID = strings.TrimSpace(input.RequestID)
	input.ApprovalID = strings.TrimSpace(input.ApprovalID)
	input.CorrelationID = strings.TrimSpace(input.CorrelationID)
	input.Now = normalizedNow(input.Now)
	if input.WorkspaceID == "" || input.RequestID == "" || input.ApprovalID == "" || input.DecisionVersion <= 0 {
		return WorkspaceCreateRequestResult{}, storeerr.BadRequest("workspaceId, requestId, approvalId, and decisionVersion are required")
	}
	preview, err := service.accounts.LoadCreateRequest(ctx, input.WorkspaceID, input.RequestID)
	if err != nil {
		return WorkspaceCreateRequestResult{}, err
	}

	var result WorkspaceCreateRequestResult
	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
		}
		requestStore := service.accounts.InTx(tx)
		request, err := requestStore.LoadCreateRequestForUpdate(ctx, input.WorkspaceID, input.RequestID)
		if err != nil {
			return err
		}
		if request.RequestedByMemberID != preview.RequestedByMemberID {
			return storeerr.Conflict("service account create request requester changed")
		}
		if request.AuthorizationPath != serviceaccountstore.AuthorizationPathApprovalRequired ||
			request.ApprovalInstanceID != input.ApprovalID {
			return storeerr.Conflict("Approval decision does not belong to the service account create request")
		}
		instance, assignments, err := lockBoundWorkspaceCreateApprovalInTx(ctx, service.db, tx, request)
		if err != nil {
			return err
		}
		if IsPrivilegedWorkspaceCreateCancellation(request) {
			result.Request = request
			result.Approval = &instance
			result.Assignments = assignments
			return nil
		}
		if request.BusinessState == serviceaccountstore.RequestStateAwaitingAuthorization &&
			access.HasWorkspaceAdminRole(domain.WorkspaceRole(workspaceRole)) {
			request, instance, assignments, err = settleKnownPrivilegedWorkspaceCreateRequestInTx(
				ctx, tx, request, instance, assignments, input.Now,
			)
			if err != nil {
				return err
			}
			result.Request = request
			result.Approval = &instance
			result.Assignments = assignments
			return nil
		}
		if instance.Version != input.DecisionVersion {
			return storeerr.Conflict("Approval decision version changed")
		}
		if request.ConsumedDecisionVersion == instance.Version {
			result.Request = request
			result.Approval = &instance
			if request.CreatedServiceAccountID != "" {
				account, err := requestStore.LoadAccountForUpdate(ctx, request.WorkspaceID, request.CreatedServiceAccountID)
				if err != nil {
					return err
				}
				result.Account = &account
			}
			return nil
		}
		if request.ConsumedDecisionVersion > 0 {
			return storeerr.Conflict("service account create request already consumed another Approval version")
		}
		result.Approval = &instance

		switch instance.State {
		case approval.StateRejected:
			request, err = requestStore.TransitionCreateRequest(
				ctx, request.WorkspaceID, request.ID, request.Version,
				serviceaccountstore.RequestStateDeclined, instance.ID,
				instance.Version, "", input.Now,
			)
			if err != nil {
				return err
			}
			result.Request = request
			result.Created = true
			return nil
		case approval.StateApproved:
		default:
			return storeerr.Conflict("Approval decision is not terminal")
		}

		request, err = requestStore.TransitionCreateRequest(
			ctx, request.WorkspaceID, request.ID, request.Version,
			serviceaccountstore.RequestStateAuthorized, instance.ID,
			instance.Version, "", input.Now,
		)
		if err != nil {
			return err
		}
		var payload WorkspaceCreatePayload
		if err := json.Unmarshal(request.PayloadSnapshot, &payload); err != nil {
			return fmt.Errorf("decode service account create payload snapshot: %w", err)
		}
		payload, err = normalizeWorkspaceCreatePayload(payload)
		if err != nil {
			return storeerr.Conflict("service account create payload snapshot is invalid")
		}
		accountID := deterministicCreateRequestAccountID(request.ID)
		materialization, err := prepareWorkspaceMaterialization(WorkspaceMaterialization{
			Identity:          IdentityRefs{AccountID: accountID},
			WorkspaceID:       request.WorkspaceID,
			CreatorMemberID:   request.RequestedByMemberID,
			Name:              payload.Name,
			Description:       payload.Description,
			AvatarAssetID:     payload.AvatarAssetID,
			AvatarURL:         payload.AvatarURL,
			ManagerMemberIDs:  []string{request.RequestedByMemberID},
			AuthorizationPath: request.AuthorizationPath,
			CorrelationID:     firstNonEmpty(input.CorrelationID, instance.CorrelationID),
			Now:               input.Now,
		})
		if err != nil {
			return err
		}
		materialized, err := service.materializeWorkspaceInTx(ctx, tx, materialization)
		if err != nil {
			return err
		}
		request, err = requestStore.TransitionCreateRequest(
			ctx, request.WorkspaceID, request.ID, request.Version,
			serviceaccountstore.RequestStateProvisioning, instance.ID,
			instance.Version, materialized.Account.ID, input.Now,
		)
		if err != nil {
			return err
		}
		result.Request = request
		result.Account = &materialized.Account
		result.Created = materialized.Created
		return nil
	})
	return result, err
}

func deterministicCreateRequestAccountID(requestID string) string {
	// The approval consumer has no caller-owned account id. A deterministic
	// entity-shaped ref makes retries converge even if a response is lost
	// before the request's created-account ref is observed.
	value := deterministicApprovalID("account:" + strings.TrimSpace(requestID))
	return "sacct_" + strings.TrimPrefix(value, "appr_")
}
