package installation

import (
	"context"
	"errors"
	"fmt"
	"strings"

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

// FeatureOwnedSetupError reports that an App requires its dedicated
// feature-owned setup flow and cannot be installed through the generic
// install paths.
type FeatureOwnedSetupError struct {
	AppID string
}

func (err *FeatureOwnedSetupError) Error() string {
	return "this App must be installed and connected through its feature-owned setup flow"
}

// AppUsesFeatureOwnedSetup reports whether an App requires its dedicated
// feature-owned setup flow: named-multi-instance definitions and singleton
// definitions whose execution capability declares installMode=feature_owned.
// Product-specific install admission (for example GitHub) stays at the
// callers.
func AppUsesFeatureOwnedSetup(ctx context.Context, queryer coredb.QueryRower, appID string) (bool, error) {
	err := appFeatureOwnedSetupErr(ctx, queryer, appID, false)
	if err == nil {
		return false, nil
	}
	var featureOwned *FeatureOwnedSetupError
	if errors.As(err, &featureOwned) {
		return true, nil
	}
	return false, err
}

// RequireGenericInstallAllowed rejects feature-owned Apps for the generic
// install paths.
func RequireGenericInstallAllowed(ctx context.Context, queryer coredb.QueryRower, appID string) error {
	if queryer == nil {
		return fmt.Errorf("app installation queryer is required")
	}
	return appFeatureOwnedSetupErr(ctx, queryer, appID, false)
}

// RequireGenericInstallAllowedForUpdate is the locking current-read variant
// for admission revalidation after the definition mutation lock.
func RequireGenericInstallAllowedForUpdate(ctx context.Context, queryer coredb.QueryRower, appID string) error {
	if queryer == nil {
		return fmt.Errorf("app installation queryer is required")
	}
	return appFeatureOwnedSetupErr(ctx, queryer, appID, true)
}

func appFeatureOwnedSetupErr(ctx context.Context, queryer coredb.QueryRower, appID string, forUpdate bool) error {
	var installationMode string
	err := queryer.QueryRow(ctx, `
SELECT installation_mode
FROM service_definitions
WHERE id = $1
  AND service_kind = $2
LIMIT 1
`+lockSuffix(forUpdate)+`
`, strings.TrimSpace(appID), serviceactor.KindApp).Scan(&installationMode)
	if coredb.IsNotFound(err) {
		return fmt.Errorf("app definition not found")
	}
	if err != nil {
		return fmt.Errorf("load App installation mode: %w", err)
	}
	switch strings.TrimSpace(installationMode) {
	case serviceactor.InstallationModeNamedMultiInstance:
		return &FeatureOwnedSetupError{AppID: appID}
	case serviceactor.InstallationModeSingleton:
	default:
		return fmt.Errorf("App service definition has invalid installation mode %q", installationMode)
	}
	var executionConfig []byte
	queryErr := queryer.QueryRow(ctx, `
SELECT COALESCE(config, '{}')
FROM service_capabilities
WHERE service_definition_id = $1
  AND capability_key = 'mcp.tools'
LIMIT 1
`+lockSuffix(forUpdate)+`
`, strings.TrimSpace(appID)).Scan(&executionConfig)
	if queryErr != nil && !coredb.IsNotFound(queryErr) {
		return fmt.Errorf("load App execution capability: %w", queryErr)
	}
	if len(executionConfig) > 0 {
		capability, decodeErr := model.DecodeAppExecutionCapability(executionConfig)
		if decodeErr != nil {
			return fmt.Errorf("decode App execution capability: %w", decodeErr)
		}
		if strings.TrimSpace(capability.InstallMode) == "feature_owned" {
			return &FeatureOwnedSetupError{AppID: appID}
		}
	}
	return nil
}

func lockSuffix(forUpdate bool) string {
	if forUpdate {
		return "\nFOR UPDATE"
	}
	return ""
}
