package serviceactor

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

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

// Definition is the canonical service actor definition record.
type Definition struct {
	ID               string
	ServiceKind      string
	InstallationMode string
	Slug             string
	Name             string
	Description      string
	AvatarURL        string
	Visibility       string
	DeveloperID      string
	LatestVersion    string
	Manifest         []byte
	Config           []byte
	Status           string
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

// DefinitionUpsert describes a service actor definition write.
type DefinitionUpsert struct {
	ID               string
	ServiceKind      string
	InstallationMode string
	Slug             string
	Name             string
	Description      string
	AvatarURL        string
	Visibility       string
	DeveloperID      string
	LatestVersion    string
	Manifest         []byte
	Config           []byte
	Status           string
	Now              time.Time
}

// UpsertDefinition creates or updates a service definition.
func (store *Store) UpsertDefinition(ctx context.Context, params DefinitionUpsert) (Definition, error) {
	execer, err := execerFromStore(store)
	if err != nil {
		return Definition{}, err
	}
	return upsertDefinition(ctx, execer, params)
}

// UpsertDefinition creates or updates a service definition in the transaction.
func (store *TxStore) UpsertDefinition(ctx context.Context, params DefinitionUpsert) (Definition, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return Definition{}, err
	}
	return upsertDefinition(ctx, execer, params)
}

// LoadDefinition loads a service definition by id.
func (store *Store) LoadDefinition(ctx context.Context, serviceDefinitionID string) (Definition, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return Definition{}, err
	}
	return loadDefinition(ctx, queryer, serviceDefinitionID)
}

// LoadDefinitionBySlug loads a service definition by kind and slug.
func (store *Store) LoadDefinitionBySlug(ctx context.Context, serviceKind string, slug string) (Definition, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return Definition{}, err
	}
	return loadDefinitionBySlug(ctx, queryer, serviceKind, slug)
}

// ListDefinitions lists service definitions by kind. Empty kind lists all kinds.
func (store *Store) ListDefinitions(ctx context.Context, serviceKind string) ([]Definition, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return nil, err
	}
	return listDefinitions(ctx, queryer, serviceKind)
}

func upsertDefinition(ctx context.Context, execer coredb.QueryExecer, params DefinitionUpsert) (Definition, error) {
	serviceDefinitionID := trim(params.ID)
	serviceKind := trim(params.ServiceKind)
	slug := trim(params.Slug)
	if serviceDefinitionID == "" || serviceKind == "" || slug == "" {
		return Definition{}, badRequest("service definition id, kind, and slug are required")
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	status := trim(params.Status)
	if status == "" {
		status = StatusActive
	}
	visibility := trim(params.Visibility)
	if visibility == "" {
		visibility = "private"
	}
	installationMode, err := normalizeInstallationMode(params.InstallationMode)
	if err != nil {
		return Definition{}, err
	}
	installationModeExplicit := 0
	if trim(params.InstallationMode) != "" {
		installationModeExplicit = 1
	}

	if _, err := execer.Exec(ctx, `
INSERT INTO service_definitions (
    id, service_kind, installation_mode, slug, name, description, avatar_url, visibility,
    developer_id, latest_version, manifest, config, status, created_at, updated_at
) VALUES (
    $1, $2, $3, $4, $5, $6, $7, $8,
    $9, $10, $11, $12, $13, $14, $14
)
ON DUPLICATE KEY UPDATE
    installation_mode = CASE WHEN $15 = 1 THEN VALUES(installation_mode) ELSE installation_mode END,
    name = VALUES(name),
    description = VALUES(description),
    avatar_url = VALUES(avatar_url),
    visibility = VALUES(visibility),
    developer_id = VALUES(developer_id),
    latest_version = VALUES(latest_version),
    manifest = VALUES(manifest),
    config = VALUES(config),
    status = VALUES(status),
    updated_at = VALUES(updated_at)
`, serviceDefinitionID, serviceKind, installationMode, slug, strings.TrimSpace(params.Name), strings.TrimSpace(params.Description), strings.TrimSpace(params.AvatarURL), visibility, strings.TrimSpace(params.DeveloperID), strings.TrimSpace(params.LatestVersion), params.Manifest, params.Config, status, now, installationModeExplicit); err != nil {
		return Definition{}, fmt.Errorf("upsert service definition: %w", mapDefinitionWriteError(err))
	}
	return loadDefinitionBySlug(ctx, execer, serviceKind, slug)
}

func loadDefinition(ctx context.Context, queryer coredb.QueryRower, serviceDefinitionID string) (Definition, error) {
	row := queryer.QueryRow(ctx, definitionSelectSQL()+` WHERE id = $1`, trim(serviceDefinitionID))
	definition, err := scanDefinition(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Definition{}, notFound("service definition not found")
		}
		return Definition{}, fmt.Errorf("load service definition: %w", err)
	}
	return definition, nil
}

func loadDefinitionBySlug(ctx context.Context, queryer coredb.QueryRower, serviceKind string, slug string) (Definition, error) {
	row := queryer.QueryRow(ctx, definitionSelectSQL()+` WHERE service_kind = $1 AND slug = $2`, trim(serviceKind), trim(slug))
	definition, err := scanDefinition(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Definition{}, notFound("service definition not found")
		}
		return Definition{}, fmt.Errorf("load service definition by slug: %w", err)
	}
	return definition, nil
}

func listDefinitions(ctx context.Context, queryer coredb.Queryer, serviceKind string) ([]Definition, error) {
	query := definitionSelectSQL()
	args := []any{}
	if trim(serviceKind) != "" {
		query += ` WHERE service_kind = $1`
		args = append(args, trim(serviceKind))
	}
	query += ` ORDER BY service_kind ASC, slug ASC, id ASC`
	rows, err := queryer.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("list service definitions: %w", err)
	}
	defer rows.Close()

	definitions := make([]Definition, 0)
	for rows.Next() {
		definition, err := scanDefinition(rows)
		if err != nil {
			return nil, fmt.Errorf("scan service definition: %w", err)
		}
		definitions = append(definitions, definition)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate service definitions: %w", err)
	}
	return definitions, nil
}

func definitionSelectSQL() string {
	return `
SELECT id, service_kind, installation_mode, slug, name, description, avatar_url,
       visibility, developer_id, latest_version, manifest,
       config, status, created_at, updated_at
FROM service_definitions`
}

func scanDefinition(row coredb.Row) (Definition, error) {
	var definition Definition
	if err := row.Scan(
		&definition.ID,
		&definition.ServiceKind,
		&definition.InstallationMode,
		&definition.Slug,
		&definition.Name,
		&definition.Description,
		&definition.AvatarURL,
		&definition.Visibility,
		&definition.DeveloperID,
		&definition.LatestVersion,
		&definition.Manifest,
		&definition.Config,
		&definition.Status,
		&definition.CreatedAt,
		&definition.UpdatedAt,
	); err != nil {
		return Definition{}, err
	}
	definition.ID = trim(definition.ID)
	definition.ServiceKind = trim(definition.ServiceKind)
	definition.InstallationMode = trim(definition.InstallationMode)
	definition.Slug = trim(definition.Slug)
	definition.Name = trim(definition.Name)
	definition.Status = trim(definition.Status)
	return definition, nil
}

func normalizeInstallationMode(value string) (string, error) {
	switch mode := trim(value); mode {
	case "", InstallationModeSingleton:
		return InstallationModeSingleton, nil
	case InstallationModeNamedMultiInstance:
		return InstallationModeNamedMultiInstance, nil
	default:
		return "", badRequest("service definition installation mode is invalid")
	}
}

func mapDefinitionWriteError(err error) error {
	if constraint, ok := coredb.UniqueViolationConstraint(err); ok {
		switch constraint {
		case "service_definitions_kind_slug_key":
			return conflict("Service definition slug already exists for this kind.")
		}
	}
	if err != nil {
		return err
	}
	return storeerr.ErrConflict
}
