package installation

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

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

// DefinitionSeed is the materialized service-definition state an installation
// replays into the service actor substrate.
type DefinitionSeed struct {
	ID                    string
	Slug                  string
	Name                  string
	Description           string
	AvatarURL             string
	Kind                  string
	Visibility            string
	LatestVersion         string
	Manifest              []byte
	CallbackURL           string
	CallbackSigningSecret string
	AvatarAssetID         string
	Config                []byte
	CreatedAt             sql.NullTime
	UpdatedAt             sql.NullTime
}

// DefinitionSeedConfig is the durable definition config projection.
type DefinitionSeedConfig struct {
	CallbackURL           string `json:"callbackUrl"`
	AppKind               string `json:"appKind"`
	CallbackSigningSecret string `json:"callbackSigningSecret"`
	AvatarAssetID         string `json:"avatarAssetId"`
}

// LoadDefinitionSeed materializes one App's definition and latest-version
// manifest state.
func LoadDefinitionSeed(ctx context.Context, queryer coredb.QueryRower, appID string) (DefinitionSeed, error) {
	if queryer == nil {
		return DefinitionSeed{}, fmt.Errorf("app definition queryer is required")
	}
	return loadDefinitionSeed(ctx, queryer, appID, "")
}

// LoadDefinitionSeedForUpdate is the locking current-read variant for
// install replay under the definition mutation lock.
func LoadDefinitionSeedForUpdate(ctx context.Context, queryer coredb.QueryRower, appID string) (DefinitionSeed, error) {
	if queryer == nil {
		return DefinitionSeed{}, fmt.Errorf("app definition queryer is required")
	}
	return loadDefinitionSeed(ctx, queryer, appID, "\nFOR UPDATE")
}

func loadDefinitionSeed(ctx context.Context, queryer coredb.QueryRower, appID string, lockSuffix string) (DefinitionSeed, error) {
	row := queryer.QueryRow(ctx, `
SELECT
    app.id,
    app.slug,
    app.name,
    app.description,
    app.avatar_url,
    COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.appKind')), ''),
    app.visibility,
    app.latest_version,
    version.manifest,
    version.callback_url,
    COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.callbackSigningSecret')), ''),
    COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(app.config, JSON_OBJECT()), '$.avatarAssetId')), ''),
    app.created_at,
    app.updated_at
FROM service_definitions app
LEFT JOIN extension_definition_versions version
  ON version.service_definition_id = app.id
 AND version.version = app.latest_version
WHERE app.id = $1
`+lockSuffix+`
`, strings.TrimSpace(appID))

	var seed DefinitionSeed
	var manifest sql.NullString
	var callbackURL sql.NullString
	if err := row.Scan(
		&seed.ID,
		&seed.Slug,
		&seed.Name,
		&seed.Description,
		&seed.AvatarURL,
		&seed.Kind,
		&seed.Visibility,
		&seed.LatestVersion,
		&manifest,
		&callbackURL,
		&seed.CallbackSigningSecret,
		&seed.AvatarAssetID,
		&seed.CreatedAt,
		&seed.UpdatedAt,
	); err != nil {
		if coredb.IsNotFound(err) {
			return DefinitionSeed{}, fmt.Errorf("app definition not found")
		}
		return DefinitionSeed{}, fmt.Errorf("load service definition seed: %w", err)
	}
	if manifest.Valid {
		seed.Manifest = []byte(manifest.String)
	}
	seed.Kind = strings.TrimSpace(seed.Kind)
	if callbackURL.Valid {
		seed.CallbackURL = strings.TrimSpace(callbackURL.String)
	}
	seed.CallbackSigningSecret = strings.TrimSpace(seed.CallbackSigningSecret)
	config, err := model.MarshalJSONObject("app service definition config", DefinitionSeedConfig{
		CallbackURL:           seed.CallbackURL,
		AppKind:               seed.Kind,
		CallbackSigningSecret: seed.CallbackSigningSecret,
		AvatarAssetID:         seed.AvatarAssetID,
	})
	if err != nil {
		return DefinitionSeed{}, err
	}
	seed.Config = config
	return seed, nil
}

// UpsertDefinitionForSeed replays a definition seed into the substrate.
func UpsertDefinitionForSeed(ctx context.Context, txStore *serviceactor.TxStore, seed DefinitionSeed, now time.Time) (serviceactor.Definition, error) {
	return txStore.UpsertDefinition(ctx, serviceactor.DefinitionUpsert{
		ID:            seed.ID,
		ServiceKind:   serviceactor.KindApp,
		Slug:          seed.Slug,
		Name:          seed.Name,
		Description:   seed.Description,
		AvatarURL:     seed.AvatarURL,
		Visibility:    seed.Visibility,
		LatestVersion: seed.LatestVersion,
		Manifest:      seed.Manifest,
		Config:        seed.Config,
		Status:        serviceactor.StatusActive,
		Now:           now,
	})
}
