// Package serviceaccount owns the atomic projection of a Service Account
// principal into the shared member, Service Actor, scope, audience, and audit
// substrates. The service-account repositories remain the authority for
// account, manager, name, platform-instance, and audit facts.
package serviceaccount

import (
	"context"
	"fmt"
	"strings"
	"time"

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

// IdentityRefs are the durable local identities used by one materialization.
// AccountID is required because it is the idempotency fence shared with the
// create-request or platform-provisioning ledger. Other refs may be omitted and
// are generated inside MaterializeWorkspace or MaterializePlatformInstance.
type IdentityRefs struct {
	AccountID             string
	ServiceInstallationID string
	ServiceMemberID       string
	AuthorMemberID        string
	MessageScopeID        string
	NameClaimID           string
	PlatformInstanceID    string
}

// WorkspaceMaterialization describes one workspace-owned Service Account.
// ManagerMemberIDs is the initial Human manager set. Managers hold one
// indivisible authority that covers both administration and publishing.
// Materialization keeps the all-Human audience pending and does not enumerate
// the workspace roster inside the identity transaction.
type WorkspaceMaterialization struct {
	Identity          IdentityRefs
	WorkspaceID       string
	CreatorMemberID   string
	Name              string
	Description       string
	AvatarAssetID     string
	AvatarURL         string
	ManagerMemberIDs  []string
	AuthorizationPath string
	CorrelationID     string
	Now               time.Time
}

// PlatformInstanceMaterialization describes one workspace-local projection of
// an existing global platform Service Account. Profile authority is read from
// platform_service_accounts; AvatarURL is the already-resolved display
// projection for service_members.
type PlatformInstanceMaterialization struct {
	Identity                     IdentityRefs
	WorkspaceID                  string
	PlatformServiceAccountID     string
	OperatorUserID               string
	AvatarURL                    string
	OpenedProvisioningGeneration int64
	CorrelationID                string
	Now                          time.Time
}

// MaterializationResult is deliberately small: every returned ref is backed by
// the canonical local account row. Created is false for an idempotent replay.
type MaterializationResult struct {
	Account serviceaccountstore.Account
	Created bool
}

// Service coordinates the two repository domains and the shared scope/member
// substrates in one TiDB transaction.
type Service struct {
	db               coredb.DB
	accounts         *serviceaccountstore.Store
	actors           *serviceactor.Store
	deletionObserver LocalAccountDeletionObserver
}

// New constructs the Service Account materializer.
func New(db coredb.DB) *Service {
	return &Service{
		db:       db,
		accounts: serviceaccountstore.New(db),
		actors:   serviceactor.New(db),
	}
}

func (service *Service) validate() error {
	if service == nil || service.db == nil || service.accounts == nil || service.actors == nil {
		return fmt.Errorf("service account materializer is not configured")
	}
	return nil
}

// MaterializeWorkspace atomically creates a workspace-owned Service Account.
func (service *Service) MaterializeWorkspace(ctx context.Context, input WorkspaceMaterialization) (MaterializationResult, error) {
	if err := service.validate(); err != nil {
		return MaterializationResult{}, err
	}
	prepared, err := prepareWorkspaceMaterialization(input)
	if err != nil {
		return MaterializationResult{}, err
	}
	var result MaterializationResult
	if err := coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		var txErr error
		result, txErr = service.materializeWorkspaceInTx(ctx, tx, prepared)
		return txErr
	}); err != nil {
		return MaterializationResult{}, err
	}
	return result, nil
}

// MaterializePlatformInstance atomically projects an existing global platform
// Service Account into one workspace.
func (service *Service) MaterializePlatformInstance(ctx context.Context, input PlatformInstanceMaterialization) (MaterializationResult, error) {
	if err := service.validate(); err != nil {
		return MaterializationResult{}, err
	}
	prepared, err := preparePlatformMaterialization(input)
	if err != nil {
		return MaterializationResult{}, err
	}
	var result MaterializationResult
	if err := coredb.WithTx(ctx, service.db, func(tx coredb.Tx) error {
		var txErr error
		result, txErr = service.materializePlatformInTx(ctx, tx, prepared)
		return txErr
	}); err != nil {
		return MaterializationResult{}, err
	}
	return result, nil
}

func trimmed(value string) string { return strings.TrimSpace(value) }

func normalizedNow(value time.Time) time.Time {
	if value.IsZero() {
		return time.Now().UTC()
	}
	return value.UTC()
}
