package serviceaccount

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	serviceaccountstore "github.com/tidbcloud/AgentTeam/internal/store/serviceaccount"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

type localMaterializationDocuments struct {
	installationMetadata []byte
	deliveryContract     []byte
	scopeSnapshot        []byte
	bindingMetadata      []byte
	auditMetadata        []byte
	auditID              string
}

type localActorSubstrate struct {
	installation serviceactor.Installation
	member       serviceactor.Member
}

func (service *Service) materializeLocalInTx(ctx context.Context, tx coredb.Tx, input localMaterialization) (MaterializationResult, error) {
	documents, err := prepareLocalMaterializationDocuments(input)
	if err != nil {
		return MaterializationResult{}, err
	}
	actorTx := service.actors.InTx(tx)
	actorSubstrate, err := materializeLocalActorSubstrate(ctx, tx, actorTx, input, documents.installationMetadata)
	if err != nil {
		return MaterializationResult{}, err
	}

	accountTx := service.accounts.InTx(tx)
	account, err := accountTx.InsertAccount(ctx, serviceaccountstore.AccountInsert{Account: serviceaccountstore.Account{
		ID:                            input.identity.AccountID,
		PrincipalID:                   input.principalID,
		WorkspaceID:                   input.workspaceID,
		ManagementKind:                input.managementKind,
		PlatformServiceAccountID:      input.platformServiceAccountID,
		ServiceInstallationID:         actorSubstrate.installation.ID,
		ServiceMemberID:               actorSubstrate.member.ID,
		AuthorMemberID:                input.identity.AuthorMemberID,
		MessageScopeID:                input.identity.MessageScopeID,
		Name:                          input.name,
		Description:                   input.description,
		AvatarAssetID:                 input.avatarAssetID,
		AppliedPlatformProfileVersion: input.appliedPlatformProfileVersion,
		AudiencePolicyVersion:         1,
		EffectiveAudienceVersion:      0,
		AudienceState:                 serviceaccountstore.AudienceStatePending,
		Version:                       1,
		CreatedByMemberID:             input.createdByMemberID,
		CreatedByOperatorUserID:       input.createdByOperatorUserID,
		CreatedAt:                     input.now,
		UpdatedAt:                     input.now,
	}})
	if err != nil {
		return MaterializationResult{}, err
	}
	if input.managementKind == serviceaccountstore.ManagementKindWorkspace {
		if err := accountTx.InsertWorkspaceNameClaim(ctx, input.identity.NameClaimID, input.workspaceID, input.name, account.ID, input.now); err != nil {
			return MaterializationResult{}, err
		}
	}

	if err := upsertInitialManagers(ctx, accountTx, input, account.ID); err != nil {
		return MaterializationResult{}, err
	}
	mandatoryAudience := sortedUnique(append([]string{}, input.managerMemberIDs...))
	for _, memberID := range mandatoryAudience {
		if err := upsertServiceAccountAudienceMembership(ctx, tx, input.identity.MessageScopeID, memberID, input.now); err != nil {
			return MaterializationResult{}, err
		}
	}
	createdByMemberID := firstNonEmpty(input.createdByMemberID, input.identity.AuthorMemberID)
	if _, err := actorTx.UpsertTargetBinding(ctx, serviceactor.TargetBindingUpsert{
		WorkspaceID:              input.workspaceID,
		ServiceDefinitionID:      serviceaccountstore.ServiceDefinitionID,
		ServiceInstallationID:    actorSubstrate.installation.ID,
		ServiceMemberID:          actorSubstrate.member.ID,
		ServiceKind:              serviceactor.KindServiceAccount,
		TargetKind:               serviceAccountScopeType,
		TargetRef:                serviceactor.ServiceAccountTargetRef(account.ID),
		DeliveryScopeID:          input.identity.MessageScopeID,
		PermissionCheckScopeID:   input.identity.MessageScopeID,
		AuthoritySubjectMemberID: input.identity.AuthorMemberID,
		DeliveryContract:         documents.deliveryContract,
		CreatedByMemberID:        createdByMemberID,
		CreatedByActorType:       input.creatorActorType,
		CreatedByActorID:         input.creatorActorID,
		CreatedVia:               "service_account_materializer",
		ScopeSnapshot:            documents.scopeSnapshot,
		Metadata:                 documents.bindingMetadata,
		Now:                      input.now,
		ActiveBindingKey:         serviceAccountBindingKey(account.ID),
	}); err != nil {
		return MaterializationResult{}, err
	}

	if err := accountTx.InsertAuditEvent(ctx, serviceaccountstore.AuditEvent{
		ID:                       documents.auditID,
		WorkspaceID:              input.workspaceID,
		ServiceAccountID:         account.ID,
		PlatformServiceAccountID: input.platformServiceAccountID,
		EventType:                "service_account.created",
		SubjectType:              "service_account",
		SubjectID:                account.ID,
		SubjectVersion:           account.Version,
		ActorType:                serviceAccountActorType,
		ActorID:                  input.principalID,
		CausedByActorType:        input.creatorActorType,
		CausedByActorID:          input.creatorActorID,
		ExecutionActorType:       input.creatorActorType,
		ExecutionActorID:         input.creatorActorID,
		PermissionBasisKind:      firstNonEmpty(input.authorizationPath, "platform_provisioning"),
		PermissionBasisRef:       firstNonEmpty(input.createdByMemberID, input.platformServiceAccountID),
		CorrelationID:            input.correlationID,
		Metadata:                 documents.auditMetadata,
		CreatedAt:                input.now,
	}); err != nil {
		return MaterializationResult{}, err
	}
	return MaterializationResult{Account: account, Created: true}, nil
}

// upsertServiceAccountAudienceMembership differs intentionally from the
// generic compatibility helper: a newly opened service-account audience row
// starts after the current tail while retaining full-history read authority.
// This prevents old messages from becoming unread when a Human joins.
func upsertServiceAccountAudienceMembership(ctx context.Context, tx coredb.Tx, scopeID, memberID string, now time.Time) error {
	var latestSeq, latestGlobalSeq int64
	if err := tx.QueryRow(ctx, `
SELECT latest_seq, latest_global_seq
FROM scopes
WHERE id = $1
FOR UPDATE
	`, trimmed(scopeID)).Scan(&latestSeq, &latestGlobalSeq); err != nil {
		return fmt.Errorf("load service account audience baseline: %w", err)
	}
	var openCount int
	if err := tx.QueryRow(ctx, `
SELECT COUNT(*)
FROM scope_memberships
WHERE scope_id = $1
  AND member_id = $2
  AND effective_to_seq IS NULL
  AND removed_at IS NULL
  AND archived_at IS NULL
`, trimmed(scopeID), trimmed(memberID)).Scan(&openCount); err != nil {
		return fmt.Errorf("load service account open audience membership: %w", err)
	}
	repos := message.NewScopeRepositories(tx)
	if openCount == 0 {
		if err := repos.Memberships.Upsert(ctx, message.ScopeMembershipRecord{
			ScopeID:               trimmed(scopeID),
			MemberID:              trimmed(memberID),
			Visibility:            "public",
			LastReadSeq:           latestSeq,
			LastReadGlobalSeq:     latestGlobalSeq,
			LastActivityGlobalSeq: latestGlobalSeq,
			EffectiveFromSeq:      latestSeq + 1,
			CreatedAt:             now,
			UpdatedAt:             now,
		}); err != nil {
			return err
		}
	}
	return repos.Notifications.Upsert(ctx, message.ScopeNotificationSettingRecord{
		ScopeID:   trimmed(scopeID),
		MemberID:  trimmed(memberID),
		State:     "active",
		CreatedAt: now,
		UpdatedAt: now,
	})
}

func prepareLocalMaterializationDocuments(input localMaterialization) (localMaterializationDocuments, error) {
	var documents localMaterializationDocuments
	installationMetadata, err := jsonValue(map[string]any{
		"principalId":    input.principalID,
		"managementKind": input.managementKind,
	})
	if err != nil {
		return documents, err
	}
	documents.installationMetadata = installationMetadata
	deliveryContract, err := jsonValue(map[string]any{"kind": serviceAccountDeliveryContract, "version": 1})
	if err != nil {
		return documents, err
	}
	documents.deliveryContract = deliveryContract
	scopeSnapshot, err := jsonValue(map[string]any{"scopeId": input.identity.MessageScopeID, "scopeType": serviceAccountScopeType})
	if err != nil {
		return documents, err
	}
	documents.scopeSnapshot = scopeSnapshot
	bindingMetadata, err := jsonValue(map[string]any{"principalId": input.principalID})
	if err != nil {
		return documents, err
	}
	documents.bindingMetadata = bindingMetadata
	auditMetadata, err := jsonValue(createdAuditMetadata{
		AuthorMemberID:        input.identity.AuthorMemberID,
		ServiceInstallationID: input.identity.ServiceInstallationID,
		ServiceMemberID:       input.identity.ServiceMemberID,
		MessageScopeID:        input.identity.MessageScopeID,
		Materialization: materializationAuditRecord{
			DigestAlgorithm: receiptDigestAlgorithm,
			Digest:          input.receiptDigest,
			Receipt:         input.receipt,
		},
		ReconcileIntent: input.reconcileIntent,
	})
	if err != nil {
		return documents, err
	}
	documents.auditMetadata = auditMetadata
	auditID, err := identifiers.NewEntityID("saaudit")
	if err != nil {
		return documents, fmt.Errorf("generate service account audit id: %w", err)
	}
	documents.auditID = auditID
	return documents, nil
}

func materializeLocalActorSubstrate(
	ctx context.Context,
	tx coredb.Tx,
	actorTx *serviceactor.TxStore,
	input localMaterialization,
	installationMetadata []byte,
) (localActorSubstrate, error) {
	if err := actorTx.InsertServiceMemberSubstrate(ctx, serviceactor.ServiceMemberSubstrateInsert{
		ID:          input.identity.AuthorMemberID,
		WorkspaceID: input.workspaceID,
		DisplayName: input.name,
		Now:         input.now,
	}); err != nil {
		return localActorSubstrate{}, err
	}

	installation, err := actorTx.UpsertInstallation(ctx, serviceactor.InstallationUpsert{
		ID:                  input.identity.ServiceInstallationID,
		WorkspaceID:         input.workspaceID,
		ServiceDefinitionID: serviceaccountstore.ServiceDefinitionID,
		ServiceKind:         serviceactor.KindServiceAccount,
		InstanceKey:         input.principalID,
		Status:              serviceactor.StatusActive,
		InstalledVersion:    "1",
		Metadata:            installationMetadata,
		CreatedByMemberID:   input.createdByMemberID,
		CreatedByActorType:  input.creatorActorType,
		CreatedByActorID:    input.creatorActorID,
		Now:                 input.now,
	})
	if err != nil {
		return localActorSubstrate{}, err
	}
	serviceMember, err := actorTx.UpsertMember(ctx, serviceactor.MemberUpsert{
		ID:                    input.identity.ServiceMemberID,
		WorkspaceID:           input.workspaceID,
		ServiceDefinitionID:   serviceaccountstore.ServiceDefinitionID,
		ServiceInstallationID: installation.ID,
		MemberID:              input.identity.AuthorMemberID,
		ServiceKind:           serviceactor.KindServiceAccount,
		Handle:                input.name,
		DisplayName:           input.name,
		AvatarURL:             input.avatarURL,
		Status:                serviceactor.StatusActive,
		Now:                   input.now,
	})
	if err != nil {
		return localActorSubstrate{}, err
	}
	grantMemberID := input.grantMemberID
	if grantMemberID == "" {
		grantMemberID = input.identity.AuthorMemberID
	}
	if err := actorTx.SyncGrantScopes(ctx, installation, grantMemberID, []string{serviceactor.CapabilityMessageWrite}, input.now); err != nil {
		return localActorSubstrate{}, err
	}

	if err := message.NewScopeStore(tx).UpsertScope(ctx, message.ScopeRecord{
		ID:                input.identity.MessageScopeID,
		WorkspaceID:       input.workspaceID,
		Type:              serviceAccountScopeType,
		IdentityKey:       fmt.Sprintf("service_account:%s:%s", input.workspaceID, input.principalID),
		Status:            "active",
		CreatedByMemberID: firstNonEmpty(input.createdByMemberID, input.identity.AuthorMemberID),
		CreatedAt:         input.now,
		UpdatedAt:         input.now,
	}); err != nil {
		return localActorSubstrate{}, err
	}
	return localActorSubstrate{installation: installation, member: serviceMember}, nil
}

func upsertInitialManagers(ctx context.Context, store *serviceaccountstore.TxStore, input localMaterialization, accountID string) error {
	for _, memberID := range input.managerMemberIDs {
		managerID, err := identifiers.NewEntityID("samanager")
		if err != nil {
			return fmt.Errorf("generate service account manager id: %w", err)
		}
		if _, err := store.UpsertManager(ctx, serviceaccountstore.ManagerUpsert{
			ID:                 managerID,
			WorkspaceID:        input.workspaceID,
			ServiceAccountID:   accountID,
			MemberID:           memberID,
			GrantedByMemberID:  input.createdByMemberID,
			GrantedByActorType: input.creatorActorType,
			GrantedByActorID:   input.creatorActorID,
			Now:                input.now,
		}); err != nil {
			return err
		}
	}
	return nil
}
