package serviceactor

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

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

// Installation is a workspace-scoped service installation.
type Installation struct {
	ID                  string
	WorkspaceID         string
	ServiceDefinitionID string
	ServiceKind         string
	InstanceKey         string
	OwnershipKind       string
	OwnerMemberID       string
	Status              string
	InstalledVersion    string
	Config              []byte
	Metadata            []byte
	CreatedByMemberID   string
	CreatedByActorType  string
	CreatedByActorID    string
	CreatedAt           time.Time
	UpdatedAt           time.Time
	DisabledAt          *time.Time
}

// InstallationUpsert describes a service installation write.
type InstallationUpsert struct {
	ID                  string
	WorkspaceID         string
	ServiceDefinitionID string
	ServiceKind         string
	InstanceKey         string
	OwnershipKind       string
	OwnerMemberID       string
	Status              string
	InstalledVersion    string
	Config              []byte
	Metadata            []byte
	CreatedByMemberID   string
	CreatedByActorType  string
	CreatedByActorID    string
	Now                 time.Time
	DisabledAt          *time.Time
}

// UpsertInstallation creates or updates a service installation.
func (store *Store) UpsertInstallation(ctx context.Context, params InstallationUpsert) (Installation, error) {
	if _, err := execerFromStore(store); err != nil {
		return Installation{}, err
	}
	var installation Installation
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		installation, err = upsertInstallation(ctx, tx, params)
		return err
	})
	return installation, err
}

// UpsertInstallation creates or updates a service installation in the transaction.
func (store *TxStore) UpsertInstallation(ctx context.Context, params InstallationUpsert) (Installation, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return Installation{}, err
	}
	return upsertInstallation(ctx, execer, params)
}

// LoadInstallation loads one service installation.
func (store *Store) LoadInstallation(ctx context.Context, serviceInstallationID string) (Installation, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return Installation{}, err
	}
	return loadInstallation(ctx, queryer, serviceInstallationID)
}

// LoadInstallationForUpdate locks one service installation.
func (store *TxStore) LoadInstallationForUpdate(ctx context.Context, serviceInstallationID string) (Installation, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return Installation{}, err
	}
	return loadInstallationForUpdate(ctx, execer, serviceInstallationID)
}

// LoadInstallationByInstance loads one installation by its durable named identity.
func (store *Store) LoadInstallationByInstance(ctx context.Context, workspaceID string, serviceDefinitionID string, instanceKey string) (Installation, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return Installation{}, err
	}
	return loadInstallationByInstance(ctx, queryer, workspaceID, serviceDefinitionID, instanceKey)
}

// LoadInstallationByInstanceForUpdate locks one installation by its durable named identity.
func (store *TxStore) LoadInstallationByInstanceForUpdate(ctx context.Context, workspaceID string, serviceDefinitionID string, instanceKey string) (Installation, bool, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return Installation{}, false, err
	}
	return loadInstallationByInstanceForUpdate(ctx, execer, workspaceID, serviceDefinitionID, instanceKey)
}

// ListInstallations lists installations for one workspace and optional service kind.
func (store *Store) ListInstallations(ctx context.Context, workspaceID string, serviceKind string) ([]Installation, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return nil, err
	}
	return listInstallations(ctx, queryer, workspaceID, serviceKind)
}

// ListInstallationsByDefinition lists every named installation for one
// definition in a workspace.
func (store *Store) ListInstallationsByDefinition(ctx context.Context, workspaceID string, serviceDefinitionID string) ([]Installation, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return nil, err
	}
	return listInstallationsByDefinition(ctx, queryer, workspaceID, serviceDefinitionID)
}

func upsertInstallation(ctx context.Context, execer coredb.QueryExecer, params InstallationUpsert) (Installation, error) {
	installationID := trim(params.ID)
	workspaceID := trim(params.WorkspaceID)
	definitionID := trim(params.ServiceDefinitionID)
	serviceKind := trim(params.ServiceKind)
	instanceKey := trim(params.InstanceKey)
	if instanceKey == "" {
		instanceKey = DefaultInstanceKey
	}
	if installationID == "" || workspaceID == "" || definitionID == "" || serviceKind == "" {
		return Installation{}, badRequest("service installation id, workspaceId, serviceDefinitionId, and kind are required")
	}
	definition, err := loadDefinition(ctx, execer, definitionID)
	if err != nil {
		return Installation{}, err
	}
	if serviceKind != definition.ServiceKind {
		return Installation{}, badRequest("service installation kind must match its definition")
	}
	if err := validateInstallationInstanceKey(definition.InstallationMode, instanceKey); err != nil {
		return Installation{}, err
	}
	ownershipKind, ownerMemberID, err := normalizeInstallationOwnership(params.OwnershipKind, params.OwnerMemberID, instanceKey)
	if err != nil {
		return Installation{}, err
	}
	createdByMemberID, createdByActorType, createdByActorID, err := normalizeInstallationCreator(params)
	if err != nil {
		return Installation{}, err
	}
	existingByID, foundByID, err := findInstallationByIDForUpdate(ctx, execer, installationID)
	if err != nil {
		return Installation{}, err
	}
	if foundByID && !installationIdentityMatches(existingByID, workspaceID, definitionID, serviceKind, instanceKey) {
		return Installation{}, conflict("Service installation id is already bound to another durable identity.")
	}
	existingByInstance, foundByInstance, err := loadInstallationByInstanceForUpdate(ctx, execer, workspaceID, definitionID, instanceKey)
	if err != nil {
		return Installation{}, err
	}
	if foundByInstance && (existingByInstance.ID != installationID || existingByInstance.ServiceKind != serviceKind) {
		return Installation{}, conflict("Service installation durable identity is already bound to another id.")
	}
	if err := validateInstallationCreatorSubstrate(ctx, execer, workspaceID, createdByMemberID, createdByActorType, createdByActorID); err != nil {
		return Installation{}, err
	}
	status := trim(params.Status)
	if status == "" {
		status = StatusActive
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	disabledAt := params.DisabledAt
	if status == StatusDisabled && disabledAt == nil {
		disabledAt = &now
	}
	if status == StatusActive {
		disabledAt = nil
	}
	if foundByID || foundByInstance {
		if _, err := execer.Exec(ctx, `
UPDATE service_installations
SET status = $6,
    installed_version = $7,
    config = $8,
    metadata = $9,
    created_by_member_id = $10,
    created_by_actor_type = $11,
    created_by_actor_id = $12,
    updated_at = $13,
    disabled_at = $14,
    ownership_kind = $15,
    owner_member_id = $16
WHERE id = $1
  AND workspace_id = $2
  AND service_definition_id = $3
  AND service_kind = $4
  AND instance_key = $5
`, installationID, workspaceID, definitionID, serviceKind, instanceKey, status, trim(params.InstalledVersion), params.Config, params.Metadata, nullableString(createdByMemberID), createdByActorType, createdByActorID, now, nullTimePtr(disabledAt), ownershipKind, nullableString(ownerMemberID)); err != nil {
			return Installation{}, fmt.Errorf("update service installation: %w", mapInstallationWriteError(err))
		}
		return loadInstallationByInstance(ctx, execer, workspaceID, definitionID, instanceKey)
	}

	if _, err := execer.Exec(ctx, `
INSERT INTO service_installations (
    id, workspace_id, service_definition_id, service_kind, instance_key, status,
    installed_version, config, metadata, created_by_member_id,
    created_by_actor_type, created_by_actor_id, created_at, updated_at, disabled_at,
    ownership_kind, owner_member_id
) VALUES (
    $1, $2, $3, $4, $5, $6,
    $7, $8, $9, $10,
    $11, $12, $13, $13, $14,
    $15, $16
)
`, installationID, workspaceID, definitionID, serviceKind, instanceKey, status, trim(params.InstalledVersion), params.Config, params.Metadata, nullableString(createdByMemberID), createdByActorType, createdByActorID, now, nullTimePtr(disabledAt), ownershipKind, nullableString(ownerMemberID)); err != nil {
		return Installation{}, fmt.Errorf("insert service installation: %w", mapInstallationWriteError(err))
	}
	return loadInstallationByInstance(ctx, execer, workspaceID, definitionID, instanceKey)
}

func loadInstallation(ctx context.Context, queryer coredb.QueryRower, serviceInstallationID string) (Installation, error) {
	row := queryer.QueryRow(ctx, installationSelectSQL()+` WHERE id = $1`, trim(serviceInstallationID))
	installation, err := scanInstallation(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Installation{}, notFound("service installation not found")
		}
		return Installation{}, fmt.Errorf("load service installation: %w", err)
	}
	return installation, nil
}

func loadInstallationForUpdate(ctx context.Context, queryer coredb.QueryRower, serviceInstallationID string) (Installation, error) {
	installation, found, err := findInstallationByIDForUpdate(ctx, queryer, serviceInstallationID)
	if err != nil {
		return Installation{}, err
	}
	if !found {
		return Installation{}, notFound("service installation not found")
	}
	return installation, nil
}

func findInstallationByIDForUpdate(ctx context.Context, queryer coredb.QueryRower, serviceInstallationID string) (Installation, bool, error) {
	row := queryer.QueryRow(ctx, installationSelectSQL()+` WHERE id = $1 FOR UPDATE`, trim(serviceInstallationID))
	installation, err := scanInstallation(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Installation{}, false, nil
		}
		return Installation{}, false, fmt.Errorf("lock service installation by id: %w", err)
	}
	return installation, true, nil
}

func loadInstallationByInstanceForUpdate(ctx context.Context, queryer coredb.QueryRower, workspaceID string, serviceDefinitionID string, instanceKey string) (Installation, bool, error) {
	row := queryer.QueryRow(ctx, installationSelectSQL()+` WHERE workspace_id = $1 AND service_definition_id = $2 AND instance_key = $3 FOR UPDATE`, trim(workspaceID), trim(serviceDefinitionID), trim(instanceKey))
	installation, err := scanInstallation(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Installation{}, false, nil
		}
		return Installation{}, false, fmt.Errorf("load service installation by instance: %w", err)
	}
	return installation, true, nil
}

func loadInstallationByInstance(ctx context.Context, queryer coredb.QueryRower, workspaceID string, serviceDefinitionID string, instanceKey string) (Installation, error) {
	row := queryer.QueryRow(ctx, installationSelectSQL()+` WHERE workspace_id = $1 AND service_definition_id = $2 AND instance_key = $3`, trim(workspaceID), trim(serviceDefinitionID), trim(instanceKey))
	installation, err := scanInstallation(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Installation{}, notFound("service installation not found")
		}
		return Installation{}, fmt.Errorf("load service installation by instance: %w", err)
	}
	return installation, nil
}

func listInstallationsByDefinition(ctx context.Context, queryer coredb.Queryer, workspaceID string, serviceDefinitionID string) ([]Installation, error) {
	workspaceID = trim(workspaceID)
	serviceDefinitionID = trim(serviceDefinitionID)
	if workspaceID == "" || serviceDefinitionID == "" {
		return nil, badRequest("workspaceId and serviceDefinitionId are required")
	}
	rows, err := queryer.Query(ctx, installationSelectSQL()+`
WHERE workspace_id = $1 AND service_definition_id = $2
ORDER BY instance_key ASC, id ASC`, workspaceID, serviceDefinitionID)
	if err != nil {
		return nil, fmt.Errorf("list service installations by definition: %w", err)
	}
	defer rows.Close()
	return scanInstallations(rows)
}

func listInstallations(ctx context.Context, queryer coredb.Queryer, workspaceID string, serviceKind string) ([]Installation, error) {
	if trim(workspaceID) == "" {
		return nil, badRequest("workspaceId is required")
	}
	query := installationSelectSQL() + ` WHERE workspace_id = $1`
	args := []any{trim(workspaceID)}
	if trim(serviceKind) != "" {
		query += ` AND service_kind = $2`
		args = append(args, trim(serviceKind))
	}
	query += ` ORDER BY updated_at DESC, id ASC`
	rows, err := queryer.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("list service installations: %w", err)
	}
	defer rows.Close()
	return scanInstallations(rows)
}

func installationSelectSQL() string {
	return `
SELECT id, workspace_id, service_definition_id, service_kind, instance_key, status,
       installed_version, config, metadata, COALESCE(created_by_member_id, ''),
       created_by_actor_type, created_by_actor_id,
       created_at, updated_at, disabled_at,
       ownership_kind, COALESCE(owner_member_id, '')
FROM service_installations`
}

func scanInstallation(row coredb.Row) (Installation, error) {
	var installation Installation
	var disabledAt sql.NullTime
	if err := row.Scan(
		&installation.ID,
		&installation.WorkspaceID,
		&installation.ServiceDefinitionID,
		&installation.ServiceKind,
		&installation.InstanceKey,
		&installation.Status,
		&installation.InstalledVersion,
		&installation.Config,
		&installation.Metadata,
		&installation.CreatedByMemberID,
		&installation.CreatedByActorType,
		&installation.CreatedByActorID,
		&installation.CreatedAt,
		&installation.UpdatedAt,
		&disabledAt,
		&installation.OwnershipKind,
		&installation.OwnerMemberID,
	); err != nil {
		return Installation{}, err
	}
	installation.ID = trim(installation.ID)
	installation.WorkspaceID = trim(installation.WorkspaceID)
	installation.ServiceDefinitionID = trim(installation.ServiceDefinitionID)
	installation.ServiceKind = trim(installation.ServiceKind)
	installation.InstanceKey = trim(installation.InstanceKey)
	installation.Status = trim(installation.Status)
	installation.CreatedByMemberID = trim(installation.CreatedByMemberID)
	installation.CreatedByActorType = trim(installation.CreatedByActorType)
	installation.CreatedByActorID = trim(installation.CreatedByActorID)
	installation.OwnershipKind = trim(installation.OwnershipKind)
	installation.OwnerMemberID = trim(installation.OwnerMemberID)
	if installation.OwnershipKind == "" {
		installation.OwnershipKind = OwnershipKindWorkspace
	}
	installation.DisabledAt = ptrFromNullTime(disabledAt)
	return installation, nil
}

func scanInstallations(rows coredb.Rows) ([]Installation, error) {
	installations := make([]Installation, 0)
	for rows.Next() {
		installation, err := scanInstallation(rows)
		if err != nil {
			return nil, fmt.Errorf("scan service installation: %w", err)
		}
		installations = append(installations, installation)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate service installations: %w", err)
	}
	return installations, nil
}

func validateInstallationInstanceKey(installationMode string, instanceKey string) error {
	if len(trim(instanceKey)) > 64 {
		return badRequest("service installation instance key must be at most 64 bytes")
	}
	if strings.HasPrefix(trim(instanceKey), PersonalInstanceKeyPrefix) {
		// Personal installations are a platform-level namespace carved out of
		// every definition mode: each member may hold one per definition per
		// workspace under the durable (definition, workspace, instance) key.
		return nil
	}
	switch trim(installationMode) {
	case InstallationModeSingleton:
		if trim(instanceKey) != DefaultInstanceKey {
			return badRequest("singleton service installations must use the default instance key")
		}
	case InstallationModeNamedMultiInstance:
		if trim(instanceKey) == "" || trim(instanceKey) == DefaultInstanceKey {
			return badRequest("named multi-instance service installations require a product instance key")
		}
	default:
		return badRequest("service definition installation mode is invalid")
	}
	return nil
}

// normalizeInstallationOwnership validates the ownership fields against the
// instance key. An unset kind derives from the instance-key namespace: personal
// keys imply personal ownership by the addressed member, everything else stays
// workspace-owned and unowned.
func normalizeInstallationOwnership(ownershipKind string, ownerMemberID string, instanceKey string) (string, string, error) {
	ownershipKind = trim(ownershipKind)
	ownerMemberID = trim(ownerMemberID)
	if ownershipKind == "" {
		if strings.HasPrefix(trim(instanceKey), PersonalInstanceKeyPrefix) {
			ownershipKind = OwnershipKindPersonal
			ownerMemberID = strings.TrimPrefix(trim(instanceKey), PersonalInstanceKeyPrefix)
		} else {
			ownershipKind = OwnershipKindWorkspace
		}
	}
	switch ownershipKind {
	case OwnershipKindWorkspace:
		if ownerMemberID != "" {
			return "", "", badRequest("workspace-owned service installations cannot carry an owner member")
		}
		if strings.HasPrefix(trim(instanceKey), PersonalInstanceKeyPrefix) {
			return "", "", badRequest("personal instance keys require personal ownership")
		}
	case OwnershipKindPersonal:
		if ownerMemberID == "" {
			return "", "", badRequest("personal service installations require an owner member")
		}
		if instanceKey != PersonalInstanceKeyPrefix+ownerMemberID {
			return "", "", badRequest("personal service installation instance key must address its owner")
		}
	default:
		return "", "", badRequest("service installation ownership kind is invalid")
	}
	return ownershipKind, ownerMemberID, nil
}

func installationIdentityMatches(installation Installation, workspaceID string, definitionID string, serviceKind string, instanceKey string) bool {
	return installation.WorkspaceID == workspaceID &&
		installation.ServiceDefinitionID == definitionID &&
		installation.ServiceKind == serviceKind &&
		installation.InstanceKey == instanceKey
}

func normalizeInstallationCreator(params InstallationUpsert) (string, string, string, error) {
	memberID := trim(params.CreatedByMemberID)
	actorType := trim(params.CreatedByActorType)
	actorID := trim(params.CreatedByActorID)
	if actorType == "" && actorID == "" && memberID != "" {
		return memberID, "human", memberID, nil
	}
	if actorType == "" || actorID == "" {
		return "", "", "", badRequest("service installation creator actor type and id are required")
	}
	return memberID, actorType, actorID, nil
}

func mapInstallationWriteError(err error) error {
	if constraint, ok := coredb.UniqueViolationConstraint(err); ok {
		switch constraint {
		case "PRIMARY":
			return conflict("Service installation id already exists.")
		case "service_installations_definition_workspace_key", "service_installations_definition_workspace_instance_key":
			return conflict("Service installation durable identity already exists.")
		}
	}
	return err
}
