package serviceactor

import (
	"context"
	"fmt"
	"time"

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

// Capability describes a capability exposed by a service definition.
type Capability struct {
	ID                  string
	ServiceDefinitionID string
	ServiceKind         string
	CapabilityKey       string
	Config              []byte
	DefaultEnabled      bool
	CreatedAt           time.Time
	UpdatedAt           time.Time
}

// CapabilityUpsert describes a service capability write.
type CapabilityUpsert struct {
	ID                  string
	ServiceDefinitionID string
	ServiceKind         string
	CapabilityKey       string
	Config              []byte
	DefaultEnabled      bool
	Now                 time.Time
}

// UpsertCapability creates or updates a service capability.
func (store *Store) UpsertCapability(ctx context.Context, params CapabilityUpsert) (Capability, error) {
	execer, err := execerFromStore(store)
	if err != nil {
		return Capability{}, err
	}
	return upsertCapability(ctx, execer, params)
}

// UpsertCapability creates or updates a service capability in the transaction.
func (store *TxStore) UpsertCapability(ctx context.Context, params CapabilityUpsert) (Capability, error) {
	execer, err := execerFromTxStore(store)
	if err != nil {
		return Capability{}, err
	}
	return upsertCapability(ctx, execer, params)
}

// ListCapabilities lists capabilities for one service definition.
func (store *Store) ListCapabilities(ctx context.Context, serviceDefinitionID string) ([]Capability, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return nil, err
	}
	return listCapabilities(ctx, queryer, serviceDefinitionID)
}

// ListCapabilities lists capabilities in the caller-owned transaction.
func (store *TxStore) ListCapabilities(ctx context.Context, serviceDefinitionID string) ([]Capability, error) {
	queryer, err := queryerFromTxStore(store)
	if err != nil {
		return nil, err
	}
	return listCapabilities(ctx, queryer, serviceDefinitionID)
}

// HasEnabledCapability reports whether a service definition has an enabled capability.
func (store *Store) HasEnabledCapability(ctx context.Context, serviceDefinitionID string, capabilityKey string) (bool, error) {
	queryer, err := queryerFromStore(store)
	if err != nil {
		return false, err
	}
	return hasEnabledCapability(ctx, queryer, serviceDefinitionID, capabilityKey)
}

func upsertCapability(ctx context.Context, execer coredb.QueryExecer, params CapabilityUpsert) (Capability, error) {
	capabilityID := trim(params.ID)
	if capabilityID == "" {
		var err error
		capabilityID, err = identifiers.NewEntityID("svccap")
		if err != nil {
			return Capability{}, fmt.Errorf("generate service capability id: %w", err)
		}
	}
	definitionID := trim(params.ServiceDefinitionID)
	serviceKind := trim(params.ServiceKind)
	capabilityKey := trim(params.CapabilityKey)
	if definitionID == "" || serviceKind == "" || capabilityKey == "" {
		return Capability{}, badRequest("serviceDefinitionId, serviceKind, and capabilityKey are required")
	}
	now := params.Now.UTC()
	if now.IsZero() {
		now = time.Now().UTC()
	}
	if _, err := execer.Exec(ctx, `
INSERT INTO service_capabilities (
    id, service_definition_id, service_kind, capability_key, config,
    default_enabled, created_at, updated_at
) VALUES (
    $1, $2, $3, $4, $5,
    $6, $7, $7
)
ON DUPLICATE KEY UPDATE
    config = VALUES(config),
    default_enabled = VALUES(default_enabled),
    updated_at = VALUES(updated_at)
`, capabilityID, definitionID, serviceKind, capabilityKey, params.Config, params.DefaultEnabled, now); err != nil {
		return Capability{}, fmt.Errorf("upsert service capability: %w", mapCapabilityWriteError(err))
	}
	return loadCapability(ctx, execer, definitionID, capabilityKey)
}

func loadCapability(ctx context.Context, queryer coredb.QueryRower, serviceDefinitionID string, capabilityKey string) (Capability, error) {
	row := queryer.QueryRow(ctx, capabilitySelectSQL()+` WHERE service_definition_id = $1 AND capability_key = $2`, trim(serviceDefinitionID), trim(capabilityKey))
	capability, err := scanCapability(row)
	if err != nil {
		if coredb.IsNotFound(err) {
			return Capability{}, notFound("service capability not found")
		}
		return Capability{}, fmt.Errorf("load service capability: %w", err)
	}
	return capability, nil
}

func listCapabilities(ctx context.Context, queryer coredb.Queryer, serviceDefinitionID string) ([]Capability, error) {
	rows, err := queryer.Query(ctx, capabilitySelectSQL()+`
WHERE service_definition_id = $1
ORDER BY capability_key ASC
`, trim(serviceDefinitionID))
	if err != nil {
		return nil, fmt.Errorf("list service capabilities: %w", err)
	}
	defer rows.Close()
	capabilities := make([]Capability, 0)
	for rows.Next() {
		capability, err := scanCapability(rows)
		if err != nil {
			return nil, fmt.Errorf("scan service capability: %w", err)
		}
		capabilities = append(capabilities, capability)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate service capabilities: %w", err)
	}
	return capabilities, nil
}

func hasEnabledCapability(ctx context.Context, queryer coredb.QueryRower, serviceDefinitionID string, capabilityKey string) (bool, error) {
	var count int
	if err := queryer.QueryRow(ctx, `
SELECT COUNT(*)
FROM service_capabilities
WHERE service_definition_id = $1
  AND capability_key = $2
  AND default_enabled = TRUE
`, trim(serviceDefinitionID), trim(capabilityKey)).Scan(&count); err != nil {
		return false, fmt.Errorf("check service capability: %w", err)
	}
	return count > 0, nil
}

func capabilitySelectSQL() string {
	return `
SELECT id, service_definition_id, service_kind, capability_key, config,
       default_enabled, created_at, updated_at
FROM service_capabilities`
}

func scanCapability(row coredb.Row) (Capability, error) {
	var capability Capability
	if err := row.Scan(
		&capability.ID,
		&capability.ServiceDefinitionID,
		&capability.ServiceKind,
		&capability.CapabilityKey,
		&capability.Config,
		&capability.DefaultEnabled,
		&capability.CreatedAt,
		&capability.UpdatedAt,
	); err != nil {
		return Capability{}, err
	}
	capability.ID = trim(capability.ID)
	capability.ServiceDefinitionID = trim(capability.ServiceDefinitionID)
	capability.ServiceKind = trim(capability.ServiceKind)
	capability.CapabilityKey = trim(capability.CapabilityKey)
	return capability, nil
}

func mapCapabilityWriteError(err error) error {
	if constraint, ok := coredb.UniqueViolationConstraint(err); ok {
		switch constraint {
		case "service_capabilities_definition_key":
			return conflict("Service capability already exists for this definition.")
		}
	}
	return err
}
