// Package serviceactor owns service actor persistence for installed service
// definitions, installations, Installation Extension selections, members,
// connections, capabilities, target bindings, webhook endpoints, and principal
// authorizations.
package serviceactor

import (
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

const (
	KindApp            = "app"
	KindServiceAccount = "service_account"

	InstallationModeSingleton          = "singleton"
	InstallationModeNamedMultiInstance = "named_multi_instance"
	DefaultInstanceKey                 = "default"

	OwnershipKindWorkspace    = "workspace"
	OwnershipKindPersonal     = "personal"
	PersonalInstanceKeyPrefix = "personal:"

	PrincipalKindHuman     = "human"
	PrincipalKindAgent     = "agent"
	PrincipalKindWorkspace = "workspace"

	AccessRoleUse    = "use"
	AccessRoleManage = "manage"

	StatusActive   = "active"
	StatusDisabled = "disabled"
	StatusInactive = "inactive"

	CapabilityMessageWrite               = "message.write"
	CapabilityAppNotificationsWrite      = "app.notifications.write"
	CapabilityAppNotificationActionsRead = "app.notification_actions.read"
	CapabilityEventsReceive              = "events.receive"
	CapabilitySubscriptionsManage        = "subscriptions.manage"
	CapabilityMCPTools                   = "mcp.tools"
	CapabilityApp                        = "app"
	CapabilityExtensions                 = "extensions"

	EndpointKindProviderWebhook = "provider_webhook"
)

// Store owns service actor data access. It uses the service_* tables as the
// sole runtime source of truth.
type Store struct {
	db coredb.DB
}

// New constructs a service actor Store.
func New(db coredb.DB) *Store {
	return &Store{db: db}
}

// Validate checks whether the store is wired into the graph. Database-backed
// methods validate the DB at query time so lightweight graph accessor tests can
// construct an in-memory graph without opening a database.
func (store *Store) Validate() error {
	if store == nil {
		return fmt.Errorf("service actor store is not configured")
	}
	return nil
}

// InTx returns a transaction-bound service actor store.
func (store *Store) InTx(tx coredb.Tx) *TxStore {
	return &TxStore{db: tx}
}

// TxStore exposes the same persistence helpers bound to an existing transaction.
type TxStore struct {
	db coredb.QueryExecer
}

func queryerFromStore(store *Store) (coredb.Queryer, error) {
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("service actor store database is not configured")
	}
	return store.db, nil
}

func execerFromStore(store *Store) (coredb.QueryExecer, error) {
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("service actor store database is not configured")
	}
	return store.db, nil
}

func execerFromTxStore(store *TxStore) (coredb.QueryExecer, error) {
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("service actor tx store database is not configured")
	}
	return store.db, nil
}

func queryerFromTxStore(store *TxStore) (coredb.Queryer, error) {
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("service actor tx store database is not configured")
	}
	return store.db, nil
}

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

// ServiceAccountTargetRef returns the canonical typed identity target for a
// stable Service Account id.
func ServiceAccountTargetRef(accountID string) string {
	return "service-account:" + trim(accountID)
}

func trimSlice(values []string) []string {
	out := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		trimmed := trim(value)
		if trimmed == "" {
			continue
		}
		if _, exists := seen[trimmed]; exists {
			continue
		}
		seen[trimmed] = struct{}{}
		out = append(out, trimmed)
	}
	return out
}

func nullTimePtr(t *time.Time) any {
	if t == nil {
		return nil
	}
	return *t
}

func marshalJSONMap(label string, value map[string]any) ([]byte, error) {
	if value == nil {
		return nil, nil
	}
	return model.MarshalJSONObject(label, value)
}

// TxStoreFrom adapts a transaction-scoped queryer onto the TxStore surface
// for callers that hold a coredb.Tx typed as a narrower query interface.
func TxStoreFrom(queryer coredb.QueryExecer) *TxStore {
	return &TxStore{db: queryer}
}
