package serviceaccount

import (
	"context"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	serviceaccountstore "github.com/tidbcloud/AgentTeam/internal/store/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

func (service *Service) materializeWorkspaceInTx(ctx context.Context, tx coredb.Tx, input preparedWorkspaceMaterialization) (MaterializationResult, error) {
	accountTx := service.accounts.InTx(tx)
	if existing, found, err := loadLocalAccountForReplay(ctx, accountTx, input.WorkspaceID, input.Identity.AccountID); err != nil {
		return MaterializationResult{}, err
	} else if found {
		if err := requireEquivalentMaterializationReplay(ctx, accountTx, existing, serviceaccountstore.ManagementKindWorkspace, input.ReceiptDigest); err != nil {
			return MaterializationResult{}, err
		}
		return MaterializationResult{Account: existing, Created: false}, nil
	}

	platformOwnerID, err := accountTx.LockNameNamespaceAt(ctx, input.Name, input.Now)
	if err != nil {
		return MaterializationResult{}, err
	}
	if platformOwnerID != "" {
		return MaterializationResult{}, storeerr.Conflict("service account name is owned by a platform account")
	}
	if err := requireHumanMembersActive(ctx, accountTx, input.WorkspaceID, append([]string{input.CreatorMemberID}, input.ManagerMemberIDs...), "creator and managers"); err != nil {
		return MaterializationResult{}, err
	}
	return service.materializeLocalInTx(ctx, tx, localMaterialization{
		identity:          input.Identity,
		workspaceID:       input.WorkspaceID,
		principalID:       input.Identity.AccountID,
		managementKind:    serviceaccountstore.ManagementKindWorkspace,
		name:              input.Name,
		description:       input.Description,
		avatarAssetID:     input.AvatarAssetID,
		avatarURL:         input.AvatarURL,
		createdByMemberID: input.CreatorMemberID,
		creatorActorType:  "human",
		creatorActorID:    input.CreatorMemberID,
		grantMemberID:     input.CreatorMemberID,
		managerMemberIDs:  input.ManagerMemberIDs,
		authorizationPath: input.AuthorizationPath,
		correlationID:     input.CorrelationID,
		receipt:           input.Receipt,
		receiptDigest:     input.ReceiptDigest,
		reconcileIntent: materializationReconcileIntent{
			Audience: audienceReconcileIntent{
				State:         reconcileStatePending,
				PolicyVersion: 1,
			},
		},
		now: input.Now,
	})
}

func (service *Service) materializePlatformInTx(ctx context.Context, tx coredb.Tx, input preparedPlatformMaterialization) (MaterializationResult, error) {
	accountTx := service.accounts.InTx(tx)
	if existing, found, err := loadLocalAccountForReplay(ctx, accountTx, input.WorkspaceID, input.Identity.AccountID); err != nil {
		return MaterializationResult{}, err
	} else if found {
		if err := requireEquivalentMaterializationReplay(ctx, accountTx, existing, serviceaccountstore.ManagementKindPlatform, input.ReceiptDigest); err != nil {
			return MaterializationResult{}, err
		}
		return MaterializationResult{Account: existing, Created: false}, nil
	}

	platformAccount, err := accountTx.LoadPlatformAccountForUpdate(ctx, input.PlatformServiceAccountID)
	if err != nil {
		return MaterializationResult{}, err
	}
	operatorUserID := input.OperatorUserID
	if operatorUserID == "" {
		operatorUserID = platformAccount.CreatedByOperatorUserID
	}
	platformOwnerID, err := accountTx.LockNameNamespaceAt(ctx, platformAccount.Name, input.Now)
	if err != nil {
		return MaterializationResult{}, err
	}
	if platformOwnerID != platformAccount.ID {
		return MaterializationResult{}, storeerr.Conflict("platform service account does not own its name namespace")
	}
	result, err := service.materializeLocalInTx(ctx, tx, localMaterialization{
		identity:                      input.Identity,
		workspaceID:                   input.WorkspaceID,
		principalID:                   platformAccount.ID,
		managementKind:                serviceaccountstore.ManagementKindPlatform,
		platformServiceAccountID:      platformAccount.ID,
		name:                          platformAccount.Name,
		description:                   platformAccount.Description,
		avatarAssetID:                 platformAccount.AvatarAssetID,
		avatarURL:                     input.AvatarURL,
		appliedPlatformProfileVersion: platformAccount.ProfileVersion,
		createdByOperatorUserID:       operatorUserID,
		creatorActorType:              platformOperatorActorType,
		creatorActorID:                operatorUserID,
		correlationID:                 input.CorrelationID,
		receipt:                       input.Receipt,
		receiptDigest:                 input.ReceiptDigest,
		reconcileIntent: materializationReconcileIntent{
			Audience: audienceReconcileIntent{
				State:         reconcileStatePending,
				PolicyVersion: 1,
			},
			PlatformProvisioning: &platformProvisioningReconcileIntent{
				State:                         reconcileStatePending,
				PlatformServiceAccountID:      platformAccount.ID,
				PlatformWorkspaceInstanceID:   input.Identity.PlatformInstanceID,
				OpenedProvisioningGeneration:  input.OpenedProvisioningGeneration,
				EffectiveFromBroadcastSeq:     platformAccount.LatestBroadcastSeq + 1,
				LastAppliedBroadcastSeq:       platformAccount.LatestBroadcastSeq,
				AppliedPlatformProfileVersion: platformAccount.ProfileVersion,
			},
		},
		now: input.Now,
	})
	if err != nil {
		return MaterializationResult{}, err
	}
	if _, err := accountTx.InsertPlatformWorkspaceInstance(ctx, serviceaccountstore.PlatformWorkspaceInstance{
		ID:                           input.Identity.PlatformInstanceID,
		PlatformServiceAccountID:     platformAccount.ID,
		WorkspaceID:                  input.WorkspaceID,
		LocalServiceAccountID:        result.Account.ID,
		OpenedProvisioningGeneration: input.OpenedProvisioningGeneration,
		EffectiveFromBroadcastSeq:    platformAccount.LatestBroadcastSeq + 1,
		LastAppliedBroadcastSeq:      platformAccount.LatestBroadcastSeq,
		AppliedProfileVersion:        platformAccount.ProfileVersion,
		ProvisioningState:            reconcileStatePending,
		Version:                      1,
		CreatedAt:                    input.Now,
		UpdatedAt:                    input.Now,
	}); err != nil {
		return MaterializationResult{}, err
	}
	return result, nil
}
