package installation

import (
	"context"
	"fmt"
	"strings"

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

// LoadInstallableApp loads an app definition that the caller may install.
func (store *Store) LoadInstallableApp(ctx context.Context, appID string, appSlug string) (model.InstallableAppRecord, error) {
	if store == nil || store.db == nil {
		return model.InstallableAppRecord{}, fmt.Errorf("app installation store is required")
	}
	return LoadInstallableApp(ctx, store.db, appID, appSlug)
}

// LoadInstallableApp loads an app definition that the caller may install using
// the supplied queryer. It owns the installable App authoring boundary read
// model, while callers own authorization and transaction boundaries. Runtime
// install/update/uninstall writes converge through the Service Actor tables.
func LoadInstallableApp(ctx context.Context, queryer coredb.Queryer, appID string, appSlug string) (model.InstallableAppRecord, error) {
	if queryer == nil {
		return model.InstallableAppRecord{}, fmt.Errorf("app installation queryer is required")
	}
	trimmedAppID := strings.TrimSpace(appID)
	trimmedAppSlug := strings.TrimSpace(appSlug)
	if trimmedAppID == "" && trimmedAppSlug == "" {
		return model.InstallableAppRecord{}, badRequest("appId or appSlug is required")
	}

	var (
		row        coredb.Row
		app        model.InstallableAppRecord
		visibility string
	)
	switch {
	case trimmedAppID != "" && trimmedAppSlug != "":
		row = queryer.QueryRow(ctx, `
SELECT a.id, a.slug, a.name, a.visibility
FROM service_definitions a
WHERE a.id = $1
  AND a.slug = $2
  AND a.visibility IN ('public', 'unlisted')
`, trimmedAppID, trimmedAppSlug)
	case trimmedAppID != "":
		row = queryer.QueryRow(ctx, `
SELECT a.id, a.slug, a.name, a.visibility
FROM service_definitions a
WHERE a.id = $1
  AND a.visibility IN ('public', 'unlisted')
`, trimmedAppID)
	default:
		row = queryer.QueryRow(ctx, `
SELECT a.id, a.slug, a.name, a.visibility
FROM service_definitions a
WHERE a.slug = $1
  AND a.visibility IN ('public', 'unlisted')
`, trimmedAppSlug)
	}

	if err := row.Scan(&app.ID, &app.Slug, &app.Name, &visibility); err != nil {
		if coredb.IsNotFound(err) {
			return model.InstallableAppRecord{}, ErrNotFound
		}
		return model.InstallableAppRecord{}, fmt.Errorf("load installable app: %w", err)
	}
	if strings.TrimSpace(visibility) == "public" && !registry.IsProductApp(app.ID, app.Slug) {
		return model.InstallableAppRecord{}, ErrNotFound
	}
	metadataByAppID, err := registry.LoadLatestInstallableMetadataByAppID(ctx, queryer, []string{app.ID})
	if err != nil {
		return model.InstallableAppRecord{}, err
	}
	if metadata, ok := metadataByAppID[app.ID]; ok {
		app.LatestVersion = metadata.Version
	}
	return app, nil
}

// LoadWorkspaceInstallableApp resolves the current immutable App definition
// that a workspace may request. Product Apps are public; workspace Apps require
// an authoritative workspace attribution.
func LoadWorkspaceInstallableApp(
	ctx context.Context,
	queryer coredb.Queryer,
	workspaceID string,
	appID string,
	appSlug string,
) (model.InstallableAppRecord, error) {
	return loadWorkspaceInstallableApp(ctx, queryer, workspaceID, appID, appSlug, "")
}

// LoadWorkspaceInstallableAppForUpdate is the current-read variant used after
// taking the App-definition mutation lock: the locking read resolves the
// latest committed row instead of the transaction's pre-lock snapshot.
func LoadWorkspaceInstallableAppForUpdate(
	ctx context.Context,
	queryer coredb.Queryer,
	workspaceID string,
	appID string,
	appSlug string,
) (model.InstallableAppRecord, error) {
	return loadWorkspaceInstallableApp(ctx, queryer, workspaceID, appID, appSlug, "\nLIMIT 1\nFOR UPDATE")
}

func loadWorkspaceInstallableApp(
	ctx context.Context,
	queryer coredb.Queryer,
	workspaceID string,
	appID string,
	appSlug string,
	lockSuffix string,
) (model.InstallableAppRecord, error) {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedAppID := strings.TrimSpace(appID)
	trimmedAppSlug := strings.TrimSpace(appSlug)
	if trimmedWorkspaceID == "" {
		return model.InstallableAppRecord{}, badRequest("workspaceId is required")
	}
	if trimmedAppID == "" && trimmedAppSlug == "" {
		return model.InstallableAppRecord{}, badRequest("appId or appSlug is required")
	}

	var (
		row           coredb.Row
		app           model.InstallableAppRecord
		kind          string
		visibility    string
		latestVersion string
	)
	switch {
	case trimmedAppID != "" && trimmedAppSlug != "":
		row = queryer.QueryRow(ctx, `
SELECT id, slug, name, COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(config, JSON_OBJECT()), '$.appKind')), ''), visibility, latest_version
FROM service_definitions
WHERE id = $1
  AND slug = $2`+lockSuffix+`
`, trimmedAppID, trimmedAppSlug)
	case trimmedAppID != "":
		row = queryer.QueryRow(ctx, `
SELECT id, slug, name, COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(config, JSON_OBJECT()), '$.appKind')), ''), visibility, latest_version
FROM service_definitions
WHERE id = $1`+lockSuffix+`
`, trimmedAppID)
	default:
		row = queryer.QueryRow(ctx, `
SELECT id, slug, name, COALESCE(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(config, JSON_OBJECT()), '$.appKind')), ''), visibility, latest_version
FROM service_definitions
WHERE slug = $1`+lockSuffix+`
`, trimmedAppSlug)
	}
	if err := row.Scan(&app.ID, &app.Slug, &app.Name, &kind, &visibility, &latestVersion); err != nil {
		if coredb.IsNotFound(err) {
			return model.InstallableAppRecord{}, ErrNotFound
		}
		return model.InstallableAppRecord{}, fmt.Errorf("load workspace-installable app: %w", err)
	}

	if registry.IsProductApp(app.ID, app.Slug) {
		if strings.TrimSpace(visibility) != "public" || strings.TrimSpace(latestVersion) == "" {
			return model.InstallableAppRecord{}, ErrNotFound
		}
		app.LatestVersion = strings.TrimSpace(latestVersion)
		return app, nil
	}
	if strings.TrimSpace(kind) != "third_party" {
		return model.InstallableAppRecord{}, ErrNotFound
	}

	var associated bool
	var associatedErr error
	if lockSuffix != "" {
		associated, associatedErr = workspaceAppAttributedForUpdate(ctx, queryer, trimmedWorkspaceID, app.ID)
	} else {
		associated, associatedErr = workspaceAppAttributed(ctx, queryer, trimmedWorkspaceID, app.ID)
	}
	if associatedErr != nil {
		return model.InstallableAppRecord{}, associatedErr
	}
	if !associated {
		return model.InstallableAppRecord{}, ErrNotFound
	}
	latestVersion = strings.TrimSpace(latestVersion)
	if latestVersion == "" {
		return model.InstallableAppRecord{}, ErrNotFound
	}

	var manifestJSON []byte
	if err := queryer.QueryRow(ctx, `
SELECT manifest
FROM extension_definition_versions
WHERE service_definition_id = $1
  AND version = $2
`+lockSuffix+`
`, app.ID, latestVersion).Scan(&manifestJSON); err != nil {
		if coredb.IsNotFound(err) {
			return model.InstallableAppRecord{}, ErrNotFound
		}
		return model.InstallableAppRecord{}, fmt.Errorf("load workspace-installable App manifest: %w", err)
	}
	if _, supported := registry.SupportedExternalMentionRuntimeCapability(app.Slug, kind, manifestJSON); !supported {
		return model.InstallableAppRecord{}, ErrNotFound
	}
	app.LatestVersion = latestVersion
	return app, nil
}

func workspaceAppAttributed(ctx context.Context, queryer coredb.QueryRower, workspaceID string, appID string) (bool, error) {
	var marker int
	err := queryer.QueryRow(ctx, `
SELECT 1
FROM extension_catalog_attributions
WHERE workspace_id = $1
  AND service_definition_id = $2
LIMIT 1
`, strings.TrimSpace(workspaceID), strings.TrimSpace(appID)).Scan(&marker)
	if coredb.IsNotFound(err) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("load workspace App attribution: %w", err)
	}
	return true, nil
}

// LoadInstallableAppVersion loads an app version from the installable App
// authoring boundary that the caller may install.
func LoadInstallableAppVersion(ctx context.Context, queryer coredb.QueryRower, appID string, snapshotVersion string, currentVersion string) (model.InstallableAppVersionRecord, error) {
	if queryer == nil {
		return model.InstallableAppVersionRecord{}, fmt.Errorf("app installation queryer is required")
	}
	return loadInstallableAppVersion(ctx, queryer, appID, snapshotVersion, currentVersion, "")
}

// LoadInstallableAppVersionForUpdate is the current-read variant for locked
// install admission.
func LoadInstallableAppVersionForUpdate(ctx context.Context, queryer coredb.QueryRower, appID string, snapshotVersion string, currentVersion string) (model.InstallableAppVersionRecord, error) {
	if queryer == nil {
		return model.InstallableAppVersionRecord{}, fmt.Errorf("app installation queryer is required")
	}
	return loadInstallableAppVersion(ctx, queryer, appID, snapshotVersion, currentVersion, "\nLIMIT 1\nFOR UPDATE")
}

func loadInstallableAppVersion(ctx context.Context, queryer coredb.QueryRower, appID string, snapshotVersion string, currentVersion string, lockSuffix string) (model.InstallableAppVersionRecord, error) {
	version := strings.TrimSpace(snapshotVersion)
	if version == "" {
		version = strings.TrimSpace(currentVersion)
	}
	if version == "" {
		return model.InstallableAppVersionRecord{}, badRequest("App does not have an installable version.")
	}

	row := queryer.QueryRow(ctx, `
SELECT
    av.version,
    av.manifest
FROM extension_definition_versions av
JOIN service_definitions a ON a.id = av.service_definition_id
WHERE av.service_definition_id = $1
  AND av.version = $2`+lockSuffix+`
`, strings.TrimSpace(appID), version)

	var (
		record       model.InstallableAppVersionRecord
		manifestJSON []byte
	)
	if err := row.Scan(&record.Version, &manifestJSON); err != nil {
		if coredb.IsNotFound(err) {
			return model.InstallableAppVersionRecord{}, badRequest("App version is not installable.")
		}
		return model.InstallableAppVersionRecord{}, fmt.Errorf("load app version: %w", err)
	}
	manifest, err := registry.ParseStoredManifest(manifestJSON)
	if err != nil {
		return model.InstallableAppVersionRecord{}, fmt.Errorf("decode app version manifest: %w", err)
	}
	record.Scopes = manifest.Scopes
	record.ConfigSchema = strings.TrimSpace(manifest.ConfigSchema)
	record.CredentialsSchema = strings.TrimSpace(manifest.CredentialsSchema)
	record.AppCallbackURL = strings.TrimSpace(manifest.AppCallbackURL)
	runtimeCapability, err := registry.ParseStoredAppRuntimeCapability(manifestJSON)
	if err != nil {
		return model.InstallableAppVersionRecord{}, fmt.Errorf("decode app App capability: %w", err)
	}
	record.RuntimeCapabilitySupported = runtimeCapability.Supported
	record.AppRuntimeTransports = append([]string(nil), runtimeCapability.Transports...)
	return record, nil
}

// workspaceAppAttributedForUpdate locks the attribution row so an
// attribution revoked while this transaction waited on the definition lock
// is visible to install admission.
func workspaceAppAttributedForUpdate(ctx context.Context, queryer coredb.QueryRower, workspaceID string, appID string) (bool, error) {
	var marker int
	err := queryer.QueryRow(ctx, `
SELECT 1
FROM extension_catalog_attributions
WHERE workspace_id = $1
  AND service_definition_id = $2
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(workspaceID), strings.TrimSpace(appID)).Scan(&marker)
	if coredb.IsNotFound(err) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("lock workspace App attribution: %w", err)
	}
	return true, nil
}
