package serviceaccount

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/json"
	"fmt"
	"math/big"
	"strings"
	"time"

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

// CreatePlatformAccount atomically claims the deployment-wide name, creates
// the global principal, captures current workspaces, and appends an operator
// audit fact. Reconciliation materializes the workspace-local identities.
func (service *PlatformService) CreatePlatformAccount(ctx context.Context, input PlatformAccountCreate) (serviceaccountstore.PlatformAccount, error) {
	if err := service.validateControlPlane(); err != nil {
		return serviceaccountstore.PlatformAccount{}, err
	}
	input.ID = strings.TrimSpace(input.ID)
	input.Name = strings.TrimSpace(input.Name)
	input.Description = strings.TrimSpace(input.Description)
	input.AvatarAssetID = strings.TrimSpace(input.AvatarAssetID)
	input.OperatorUserID = strings.TrimSpace(input.OperatorUserID)
	input.CorrelationID = strings.TrimSpace(input.CorrelationID)
	input.Now = service.normalizedNow(input.Now)
	if input.OperatorUserID == "" || input.Name == "" {
		return serviceaccountstore.PlatformAccount{}, storeerr.BadRequest("operatorUserId and name are required")
	}
	name, err := serviceaccountstore.NormalizeName(input.Name)
	if err != nil {
		return serviceaccountstore.PlatformAccount{}, err
	}
	input.Name = name
	if input.ID == "" {
		input.ID, err = identifiers.NewEntityID("psacct")
		if err != nil {
			return serviceaccountstore.PlatformAccount{}, fmt.Errorf("generate platform service account id: %w", err)
		}
	}
	if len(input.ID) > 64 {
		return serviceaccountstore.PlatformAccount{}, storeerr.BadRequest("platform service account id must be at most 64 bytes")
	}

	var result serviceaccountstore.PlatformAccount
	err = coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		if err := requirePlatformOperatorTx(ctx, tx, input.OperatorUserID); err != nil {
			return err
		}
		accounts := service.accounts.InTx(tx)
		if _, err := accounts.LockNameNamespaceAt(ctx, input.Name, input.Now); err != nil {
			return err
		}
		created, err := accounts.InsertPlatformAccount(ctx, serviceaccountstore.PlatformAccount{
			ID: input.ID, Name: input.Name,
			Description: input.Description, AvatarAssetID: input.AvatarAssetID,
			ProfileVersion: 1, Version: 1,
			ProvisioningMode: "all_workspaces", ProvisioningState: serviceaccountstore.PlatformProvisioningStatePending,
			AudiencePolicyVersion:   1,
			CreatedByOperatorUserID: input.OperatorUserID, CreatedAt: input.Now, UpdatedAt: input.Now,
		})
		if err != nil {
			return err
		}
		for {
			discovered, _, err := discoverPlatformProvisioningInputsTx(ctx, tx, created.ID, serviceaccountstore.PlatformProvisioningSourceAccountCreate, input.Now, platformProcessBatchSize)
			if err != nil {
				return err
			}
			if discovered < platformProcessBatchSize {
				break
			}
		}
		metadata, err := json.Marshal(map[string]any{
			"name": input.Name, "provisioningMode": "all_workspaces",
		})
		if err != nil {
			return fmt.Errorf("encode platform service account creation audit: %w", err)
		}
		if err := accounts.InsertAuditEvent(ctx, serviceaccountstore.AuditEvent{
			ID:                       platformDeterministicID("saaudit", "platform-account-created", created.ID),
			PlatformServiceAccountID: created.ID, EventType: "platform_service_account.created",
			SubjectType: "platform_service_account", SubjectID: created.ID, SubjectVersion: created.Version,
			ActorType: "platform_operator", ActorID: input.OperatorUserID,
			CausedByActorType: "platform_operator", CausedByActorID: input.OperatorUserID,
			ExecutionActorType: "platform_operator", ExecutionActorID: input.OperatorUserID,
			PermissionBasisKind: "platform_operator", PermissionBasisRef: input.OperatorUserID,
			CorrelationID: input.CorrelationID, Metadata: metadata, CreatedAt: input.Now,
		}); err != nil {
			return err
		}
		result, err = accounts.LoadPlatformAccountForUpdate(ctx, created.ID)
		return err
	})
	return result, err
}

func (service *PlatformService) GetPlatformAccount(ctx context.Context, accountID string) (serviceaccountstore.PlatformAccount, error) {
	if service == nil || service.accounts == nil {
		return serviceaccountstore.PlatformAccount{}, fmt.Errorf("platform service account service is not configured")
	}
	return service.accounts.LoadPlatformAccount(ctx, strings.TrimSpace(accountID))
}

func (service *PlatformService) ListPlatformAccounts(ctx context.Context, afterID string, query string, limit int) ([]serviceaccountstore.PlatformAccount, error) {
	if service == nil || service.accounts == nil {
		return nil, fmt.Errorf("platform service account service is not configured")
	}
	return service.accounts.ListPlatformAccounts(ctx, afterID, query, limit)
}

// UpdatePlatformAccountAvatar applies one profile asset to the global
// principal and every existing workspace-local author projection atomically.
func (service *PlatformService) UpdatePlatformAccountAvatar(ctx context.Context, input PlatformAccountAvatarMutation) (serviceaccountstore.PlatformAccount, error) {
	if err := service.validateControlPlane(); err != nil {
		return serviceaccountstore.PlatformAccount{}, err
	}
	input.PlatformServiceAccountID = strings.TrimSpace(input.PlatformServiceAccountID)
	input.AvatarAssetID = strings.TrimSpace(input.AvatarAssetID)
	input.OperatorUserID = strings.TrimSpace(input.OperatorUserID)
	input.CorrelationID = strings.TrimSpace(input.CorrelationID)
	input.Now = service.normalizedNow(input.Now)
	if input.PlatformServiceAccountID == "" || input.OperatorUserID == "" || input.ExpectedVersion <= 0 {
		return serviceaccountstore.PlatformAccount{}, storeerr.BadRequest("platformServiceAccountId, operatorUserId, and expectedVersion are required")
	}
	avatarURL := attachment.AvatarAssetURL(input.AvatarAssetID)
	var updated serviceaccountstore.PlatformAccount
	err := coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		if err := requirePlatformOperatorTx(ctx, tx, input.OperatorUserID); err != nil {
			return err
		}
		accounts := service.accounts.InTx(tx)
		current, err := accounts.LoadPlatformAccountForUpdate(ctx, input.PlatformServiceAccountID)
		if err != nil {
			return err
		}
		if current.Version != input.ExpectedVersion {
			return storeerr.Conflict("platform service account profile changed")
		}
		if err := requireServiceAccountAvatarAsset(ctx, tx, input.AvatarAssetID, current.ID, ""); err != nil {
			return err
		}
		updated, err = accounts.UpdatePlatformAccountAvatar(
			ctx, current.ID, current.Version, input.AvatarAssetID, input.Now,
		)
		if err != nil {
			return err
		}
		if _, err := tx.Exec(ctx, `
UPDATE service_members
SET avatar_url = $2,
    updated_at = $3
WHERE id IN (
  SELECT service_member_id
  FROM service_accounts
  WHERE platform_service_account_id = $1
    AND management_kind = 'platform'
)
`, updated.ID, avatarURL, input.Now); err != nil {
			return fmt.Errorf("update platform service member avatars: %w", err)
		}
		if _, err := tx.Exec(ctx, `
UPDATE service_accounts
SET avatar_asset_id = $2,
    applied_platform_profile_version = $3,
    version = version + 1,
    updated_at = $4
WHERE platform_service_account_id = $1
  AND management_kind = 'platform'
`, updated.ID, updated.AvatarAssetID, updated.ProfileVersion, input.Now); err != nil {
			return fmt.Errorf("update platform local account avatars: %w", err)
		}
		if _, err := tx.Exec(ctx, `
UPDATE platform_service_account_workspace_instances
SET applied_profile_version = $2,
    version = version + 1,
    updated_at = $3
WHERE platform_service_account_id = $1
`, updated.ID, updated.ProfileVersion, input.Now); err != nil {
			return fmt.Errorf("update platform instance profile versions: %w", err)
		}
		metadata, err := json.Marshal(map[string]any{
			"avatarAssetId":  updated.AvatarAssetID,
			"profileVersion": updated.ProfileVersion,
		})
		if err != nil {
			return fmt.Errorf("encode platform service account avatar audit: %w", err)
		}
		auditID, err := identifiers.NewEntityID("saaudit")
		if err != nil {
			return err
		}
		return accounts.InsertAuditEvent(ctx, serviceaccountstore.AuditEvent{
			ID: auditID, PlatformServiceAccountID: updated.ID,
			EventType:   "platform_service_account.avatar_updated",
			SubjectType: "platform_service_account", SubjectID: updated.ID,
			SubjectVersion: updated.Version,
			ActorType:      "platform_operator", ActorID: input.OperatorUserID,
			CausedByActorType: "platform_operator", CausedByActorID: input.OperatorUserID,
			ExecutionActorType: "platform_operator", ExecutionActorID: input.OperatorUserID,
			PermissionBasisKind: "platform_operator", PermissionBasisRef: input.OperatorUserID,
			CorrelationID: input.CorrelationID, Metadata: metadata, CreatedAt: input.Now,
		})
	})
	return updated, err
}

func discoverPlatformProvisioningInputsTx(ctx context.Context, tx coredb.Tx, platformAccountID string, sourceKind string, now time.Time, limit int) (int, int64, error) {
	platformAccountID = strings.TrimSpace(platformAccountID)
	if limit <= 0 || limit > 500 {
		limit = platformProcessBatchSize
	}
	var currentGeneration int64
	if err := tx.QueryRow(ctx, `
SELECT COALESCE(MAX(generation), 0)
FROM platform_service_account_provisioning_inputs
WHERE platform_service_account_id = $1
FOR UPDATE
`, platformAccountID).Scan(&currentGeneration); err != nil {
		return 0, 0, fmt.Errorf("lock platform provisioning generation: %w", err)
	}
	rows, err := tx.Query(ctx, `
SELECT workspace.id
FROM workspaces workspace
LEFT JOIN platform_service_account_provisioning_inputs input
  ON input.platform_service_account_id = $1
 AND input.workspace_id = workspace.id
WHERE input.id IS NULL
ORDER BY workspace.id ASC
LIMIT $2
`, platformAccountID, limit)
	if err != nil {
		return 0, currentGeneration, fmt.Errorf("discover platform provisioning workspaces: %w", err)
	}
	workspaceIDs := make([]string, 0, limit)
	for rows.Next() {
		var workspaceID string
		if err := rows.Scan(&workspaceID); err != nil {
			rows.Close()
			return 0, currentGeneration, fmt.Errorf("scan platform provisioning workspace: %w", err)
		}
		workspaceIDs = append(workspaceIDs, strings.TrimSpace(workspaceID))
	}
	if err := rows.Err(); err != nil {
		rows.Close()
		return 0, currentGeneration, fmt.Errorf("iterate platform provisioning workspaces: %w", err)
	}
	rows.Close()
	if len(workspaceIDs) == 0 {
		return 0, currentGeneration, nil
	}
	generation := currentGeneration + 1
	for _, workspaceID := range workspaceIDs {
		if _, err := tx.Exec(ctx, `
INSERT INTO platform_service_account_provisioning_inputs (
    id, platform_service_account_id, workspace_id, generation, source_kind,
    state, attempt_count, last_error, version, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, 'pending', 0, '', 1, $6, $6)
`, platformDeterministicID("sapi", "platform-provisioning-input", platformAccountID, workspaceID), platformAccountID, workspaceID, generation, sourceKind, now); err != nil {
			if coredb.IsUniqueViolation(err) {
				continue
			}
			return 0, currentGeneration, fmt.Errorf("insert platform provisioning input: %w", err)
		}
	}
	if _, err := tx.Exec(ctx, `
UPDATE platform_service_accounts
SET provisioning_state = 'pending', version = version + 1, updated_at = $2
WHERE id = $1 AND provisioning_state <> 'pending'
`, platformAccountID, now); err != nil {
		return 0, currentGeneration, fmt.Errorf("mark platform account provisioning pending: %w", err)
	}
	return len(workspaceIDs), generation, nil
}

func requirePlatformOperatorTx(ctx context.Context, queryer coredb.QueryRower, userID string) error {
	var isAdmin bool
	err := queryer.QueryRow(ctx, `
SELECT is_admin
FROM users
WHERE id = $1
FOR UPDATE
`, strings.TrimSpace(userID)).Scan(&isAdmin)
	if err != nil {
		if coredb.IsNotFound(err) {
			return storeerr.Forbidden("platform operator is not authorized")
		}
		return fmt.Errorf("load platform operator: %w", err)
	}
	if !isAdmin {
		return storeerr.Forbidden("platform operator is not authorized")
	}
	return nil
}

func platformDeterministicID(prefix, namespace string, parts ...string) string {
	payload := strings.TrimSpace(namespace)
	for _, part := range parts {
		payload += "\x00" + strings.TrimSpace(part)
	}
	sum := sha256.Sum256([]byte(payload))
	suffix := new(big.Int).SetBytes(sum[:9]).Text(36)
	if len(suffix) < 14 {
		suffix = strings.Repeat("0", 14-len(suffix)) + suffix
	}
	return strings.TrimSpace(prefix) + "_" + suffix
}

func (service *PlatformService) validateControlPlane() error {
	if service == nil || service.db == nil || service.accounts == nil || service.materializer == nil {
		return fmt.Errorf("platform service account service is not configured")
	}
	if !service.controlPlane {
		return storeerr.Forbidden("platform service account control plane is not enabled on this process")
	}
	return nil
}

func scanNullableTime(value sql.NullTime) *time.Time {
	if !value.Valid {
		return nil
	}
	result := value.Time.UTC()
	return &result
}
