package model

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

const (
	AppAuthorizationModeDefaultAllow = "default_allow"
	AppAuthorizationModeExplicitOnly = "explicit_only"

	AppInstallModeGeneric      = "generic"
	AppInstallModeFeatureOwned = "feature_owned"
)

// AppExecutionCapability is the generic execution/install policy stored in
// service_capabilities.config for capability_key='mcp.tools'. Product-owned
// packages publish the data; shared App code only executes the policy.
type AppExecutionCapability struct {
	Tools                 []string
	Scopes                []string
	AuthorizationMode     string
	InstallMode           string
	RequiresSubscriptions bool
}

// DecodeAppExecutionCapability decodes the durable generic App execution
// policy. Older capability rows omit the policy fields; their historical
// behavior is default-allow, generic setup, and subscription-backed.
func DecodeAppExecutionCapability(raw []byte) (AppExecutionCapability, error) {
	document, err := decodeExtensionCapabilityObject(
		"App execution capability",
		raw,
	)
	if err != nil {
		return AppExecutionCapability{}, err
	}
	allowed := map[string]struct{}{
		"commands":              {},
		"tools":                 {},
		"scopes":                {},
		"authorizationMode":     {},
		"installMode":           {},
		"requiresSubscriptions": {},
	}
	for field := range document {
		if _, ok := allowed[field]; !ok {
			return AppExecutionCapability{}, fmt.Errorf(
				"App execution capability contains unknown field %q",
				field,
			)
		}
	}

	capability := AppExecutionCapability{
		AuthorizationMode:     AppAuthorizationModeDefaultAllow,
		InstallMode:           AppInstallModeGeneric,
		RequiresSubscriptions: true,
	}
	if value, ok := document["tools"]; ok {
		if err := json.Unmarshal(value, &capability.Tools); err != nil {
			return AppExecutionCapability{},
				fmt.Errorf("App execution capability tools must be an array of strings: %w", err)
		}
	}
	if value, ok := document["scopes"]; ok {
		if err := json.Unmarshal(value, &capability.Scopes); err != nil {
			return AppExecutionCapability{},
				fmt.Errorf("App execution capability scopes must be an array of strings: %w", err)
		}
	}
	if value, ok := document["authorizationMode"]; ok {
		if err := json.Unmarshal(value, &capability.AuthorizationMode); err != nil {
			return AppExecutionCapability{},
				fmt.Errorf("App execution capability authorizationMode must be a string: %w", err)
		}
	}
	if value, ok := document["installMode"]; ok {
		if err := json.Unmarshal(value, &capability.InstallMode); err != nil {
			return AppExecutionCapability{},
				fmt.Errorf("App execution capability installMode must be a string: %w", err)
		}
	}
	if value, ok := document["requiresSubscriptions"]; ok {
		if err := json.Unmarshal(value, &capability.RequiresSubscriptions); err != nil {
			return AppExecutionCapability{},
				fmt.Errorf("App execution capability requiresSubscriptions must be a boolean: %w", err)
		}
	}
	capability.Tools, err = normalizeAppExecutionStrings(
		"tool",
		capability.Tools,
	)
	if err != nil {
		return AppExecutionCapability{}, err
	}
	capability.Scopes, err = normalizeAppExecutionStrings(
		"scope",
		capability.Scopes,
	)
	if err != nil {
		return AppExecutionCapability{}, err
	}
	switch capability.AuthorizationMode {
	case AppAuthorizationModeDefaultAllow,
		AppAuthorizationModeExplicitOnly:
	default:
		return AppExecutionCapability{}, fmt.Errorf(
			"App execution capability authorizationMode %q is invalid",
			capability.AuthorizationMode,
		)
	}
	switch capability.InstallMode {
	case AppInstallModeGeneric, AppInstallModeFeatureOwned:
	default:
		return AppExecutionCapability{}, fmt.Errorf(
			"App execution capability installMode %q is invalid",
			capability.InstallMode,
		)
	}
	return capability, nil
}

func normalizeAppExecutionStrings(
	label string,
	values []string,
) ([]string, error) {
	normalized := append([]string(nil), values...)
	sort.Strings(normalized)
	for index, value := range normalized {
		if strings.TrimSpace(value) != value || value == "" {
			return nil, fmt.Errorf(
				"App execution capability %s is blank or not canonical",
				label,
			)
		}
		if index > 0 && normalized[index-1] == value {
			return nil, fmt.Errorf(
				"App execution capability %s %q is duplicated",
				label,
				value,
			)
		}
	}
	return normalized, nil
}
