package model

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"regexp"
	"strings"
)

var extensionCapabilityKebabNamePattern = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
var extensionCapabilityDigestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)

const (
	// GitHubBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered GitHub Definition.
	GitHubBuiltinExtensionDefinitionDigest = "sha256:00c3e0056d81f37bffeef9da66956a974ffa3ea25d8ef4ad0258c76382cd2998"
	// GiteaBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered Gitea Definition.
	GiteaBuiltinExtensionDefinitionDigest = "sha256:99d1ff7a20584067485ae0ca495908fc63b0f876736515596eef44b65a4a226d"
	// SearXNGBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered SearXNG Definition.
	SearXNGBuiltinExtensionDefinitionDigest = "sha256:ae8b8b900f4ab31949fdb1125170a3a9ceda9632d675cff67060b0513847a786"
	// BraveBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered Brave Search Definition.
	BraveBuiltinExtensionDefinitionDigest = "sha256:22bb88e164ac6a08d453e14586c657c9801742b871201a3235e84b12b7861a99"
	// TavilyBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered Tavily Search Definition.
	TavilyBuiltinExtensionDefinitionDigest = "sha256:514f6ffde6cb94d2aca9b9d0bf23592558f4a7766ccf2cd18216ea6bff5da2fa"
	// SerperBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered Serper Search Definition.
	SerperBuiltinExtensionDefinitionDigest = "sha256:8b39b6b1bef581ad4fc103bf7ccc23fcf1913d337dce2d623a52e0b89fd258ac"
	// ArxivBuiltinExtensionDefinitionDigest pins the App capability default to
	// the code-registered connectionless arXiv Definition.
	ArxivBuiltinExtensionDefinitionDigest = "sha256:68e815318b43b9c9dc048fa21b238f3053afe21b9e4cc64342957b1aa6e579fa"
	// TodoistBuiltinExtensionDefinitionDigest pins the App capability default to
	// the registered Builtin digest.
	TodoistBuiltinExtensionDefinitionDigest = "sha256:b75ddd098897d2d1b60c6c9936e818aca5db6368d85c2ace7cfb6d968378479b"
	// NotionBuiltinExtensionDefinitionDigest pins the App capability default to
	// the registered Builtin digest.
	NotionBuiltinExtensionDefinitionDigest = "sha256:82fc833264d384a0990f90a43e99730c66e5722d24f8bfcfe19cd8c500a56458"
	// HackernewsBuiltinExtensionDefinitionDigest pins the App capability default
	// to the registered Builtin digest.
	HackernewsBuiltinExtensionDefinitionDigest = "sha256:494f3ec063e9288631ca1b08e42dbccf04fd126010535db2cc91160bcd0c9437"
	// JenkinsBuiltinExtensionDefinitionDigest pins the App capability default
	// to the AgentTeam-native Jenkins Builtin Definition.
	JenkinsBuiltinExtensionDefinitionDigest = "sha256:958b4a9ebd6b7114f7c4760ec6efb9553ad52d532ed60bf0195c7d854704f3bc"
	// OnesBuiltinExtensionDefinitionDigest pins the App capability default to
	// the registered Builtin digest.
	OnesBuiltinExtensionDefinitionDigest = "sha256:d9ad77a9416bda1aef8a0736356c95a10402da22bcfc5dcf9b9c011d91e0d7c3"
	// HotNewsBuiltinExtensionDefinitionDigest pins the App capability default
	// to the registered Builtin digest.
	HotNewsBuiltinExtensionDefinitionDigest = "sha256:07086ba862a5919c0b2b9c3252d99934459f5e6a0ef2dff52c9e569f6954f608"
	// FeishuCustomBotBuiltinExtensionDefinitionDigest pins the App capability
	// default to the Feishu Custom Bot Builtin Definition.
	FeishuCustomBotBuiltinExtensionDefinitionDigest = "sha256:e459269718e03af7782b9c9b175ea5f0f60356127d2f5d8f556ca01dff6a11c8"
	// DingtalkBotBuiltinExtensionDefinitionDigest pins the App capability
	// default to the DingTalk Bot Builtin Definition.
	DingtalkBotBuiltinExtensionDefinitionDigest = "sha256:096faaf1abd858e44b777d17a351c188607fdf9ec189b504bf1e20c03aba0b93"
	// WecomBotBuiltinExtensionDefinitionDigest pins the App capability
	// default to the WeCom Bot Builtin Definition.
	WecomBotBuiltinExtensionDefinitionDigest = "sha256:9e05fae20b38aaaaa08c536b91de3f30048caf446c5db7de3ce6e8f6f4ed1176"
	// TianyanchaBuiltinExtensionDefinitionDigest pins the App capability
	// default to the registered Builtin digest.
	TianyanchaBuiltinExtensionDefinitionDigest = "sha256:abf4eae8bcdfebfdd96aac193e2f7a49a333848ddb9b26d916b3bca920a87704"
	// QichachaBuiltinExtensionDefinitionDigest pins the App capability
	// default to the registered Builtin digest.
	QichachaBuiltinExtensionDefinitionDigest = "sha256:f8d79a259315548f161d2ce0b0f4117aa7bc47742c43bf5d56f45231bda550f9"
	// The finance provider capability defaults are pinned to their registered
	// Builtin digests.
	TushareBuiltinExtensionDefinitionDigest               = "sha256:c7f6fe990a4f5c0a373734d849736fce718486a73643c5f814275d2fe26987ca"
	AlphaVantageBuiltinExtensionDefinitionDigest          = "sha256:f01a275ecaaf51a4286088bb81d24960551615ece9c9f3a0bf7bebff7fea5fdb"
	FinancialModelingPrepBuiltinExtensionDefinitionDigest = "sha256:ba658c00604d4e5eb57d33af1805bcefe7fdf8177aa8f8df1d807f9275f0c887"
	// Jin10BuiltinExtensionDefinitionDigest pins the App capability default
	// to the registered Jin10 MCP Builtin Definition.
	Jin10BuiltinExtensionDefinitionDigest = "sha256:9cbb03a3f72d65a476173a50af5c03903d7da39d054b83d46db54bda7563153a"
	// PKULawBuiltinExtensionDefinitionDigest pins the App capability default
	// to the registered PKULaw MCP Builtin Definition.
	PKULawBuiltinExtensionDefinitionDigest = "sha256:974e5b2f9a4c4fea2d78cfc57d84416e3db958d0db02c733d6ebb57a0d3fc230"
	// AIFinMarketBuiltinExtensionDefinitionDigest pins the App capability
	// default to the registered Wind AIFin Market MCP Builtin Definition.
	AIFinMarketBuiltinExtensionDefinitionDigest = "sha256:70968d21f1545e051cbe28fb9a71f2b0691e8011108a273d8c2e335e93bca706"
)

// ExtensionCapabilityConfig is the secret-free list of Extension Definitions
// that an App Definition allows a Workspace installation to select.
type ExtensionCapabilityConfig struct {
	Extensions []ExtensionCapabilityEntry
}

// ExtensionCapabilityEntry pins one immutable Extension Definition and its App
// installation default.
type ExtensionCapabilityEntry struct {
	Domain           string
	Version          int
	DefinitionDigest string
	DefaultName      string
	DefaultEnabled   bool
}

// DecodeExtensionCapabilityConfig strictly decodes the service_capabilities
// config for capability_key='extensions'.
func DecodeExtensionCapabilityConfig(raw []byte) (ExtensionCapabilityConfig, error) {
	document, err := decodeExtensionCapabilityObject("Extension capability config", raw)
	if err != nil {
		return ExtensionCapabilityConfig{}, err
	}
	if len(document) != 1 {
		return ExtensionCapabilityConfig{}, fmt.Errorf("Extension capability config must contain only extensions")
	}
	rawExtensions, ok := document["extensions"]
	if !ok {
		return ExtensionCapabilityConfig{}, fmt.Errorf("Extension capability config requires extensions")
	}
	var entries []json.RawMessage
	if err := json.Unmarshal(rawExtensions, &entries); err != nil {
		return ExtensionCapabilityConfig{}, fmt.Errorf("decode Extension capability entries: %w", err)
	}
	config := ExtensionCapabilityConfig{Extensions: make([]ExtensionCapabilityEntry, 0, len(entries))}
	for index, rawEntry := range entries {
		entry, err := decodeExtensionCapabilityEntry(rawEntry)
		if err != nil {
			return ExtensionCapabilityConfig{}, fmt.Errorf("decode Extension capability entry %d: %w", index, err)
		}
		config.Extensions = append(config.Extensions, entry)
	}
	if err := ValidateExtensionCapabilityConfig(config); err != nil {
		return ExtensionCapabilityConfig{}, err
	}
	return config, nil
}

// EncodeExtensionCapabilityConfig validates and encodes the exact persisted
// JSON shape without adding HTTP wire tags to store model types.
func EncodeExtensionCapabilityConfig(config ExtensionCapabilityConfig) ([]byte, error) {
	if err := ValidateExtensionCapabilityConfig(config); err != nil {
		return nil, err
	}
	entries := make([]map[string]any, 0, len(config.Extensions))
	for _, entry := range config.Extensions {
		entries = append(entries, map[string]any{
			"domain":           entry.Domain,
			"version":          entry.Version,
			"definitionDigest": entry.DefinitionDigest,
			"defaultName":      entry.DefaultName,
			"defaultEnabled":   entry.DefaultEnabled,
		})
	}
	return MarshalJSONObject("Extension capability config", map[string]any{
		"extensions": entries,
	})
}

// ValidateExtensionCapabilityConfig enforces immutable Definition identity and
// deterministic Agent-visible default names.
func ValidateExtensionCapabilityConfig(config ExtensionCapabilityConfig) error {
	seenDomains := make(map[string]struct{}, len(config.Extensions))
	for index, entry := range config.Extensions {
		if !extensionCapabilityKebabNamePattern.MatchString(entry.Domain) || len(entry.Domain) > 255 {
			return fmt.Errorf("Extension capability entry %d domain must be lowercase kebab-case", index)
		}
		if _, exists := seenDomains[entry.Domain]; exists {
			return fmt.Errorf("Extension capability contains duplicate domain %q", entry.Domain)
		}
		seenDomains[entry.Domain] = struct{}{}
		if entry.Version <= 0 {
			return fmt.Errorf("Extension capability entry %q version must be positive", entry.Domain)
		}
		if !extensionCapabilityDigestPattern.MatchString(entry.DefinitionDigest) {
			return fmt.Errorf("Extension capability entry %q definition digest is invalid", entry.Domain)
		}
		if !extensionCapabilityKebabNamePattern.MatchString(entry.DefaultName) || len(entry.DefaultName) > 255 {
			return fmt.Errorf("Extension capability entry %q default name must be lowercase kebab-case", entry.Domain)
		}
	}
	return nil
}

func decodeExtensionCapabilityEntry(raw []byte) (ExtensionCapabilityEntry, error) {
	document, err := decodeExtensionCapabilityObject("Extension capability entry", raw)
	if err != nil {
		return ExtensionCapabilityEntry{}, err
	}
	required := []string{"domain", "version", "definitionDigest", "defaultName", "defaultEnabled"}
	if len(document) != len(required) {
		return ExtensionCapabilityEntry{}, fmt.Errorf("entry must contain exactly domain, version, definitionDigest, defaultName, and defaultEnabled")
	}
	for _, field := range required {
		if _, ok := document[field]; !ok {
			return ExtensionCapabilityEntry{}, fmt.Errorf("entry requires %s", field)
		}
	}
	var entry ExtensionCapabilityEntry
	if err := json.Unmarshal(document["domain"], &entry.Domain); err != nil {
		return ExtensionCapabilityEntry{}, fmt.Errorf("domain must be a string: %w", err)
	}
	if err := json.Unmarshal(document["version"], &entry.Version); err != nil {
		return ExtensionCapabilityEntry{}, fmt.Errorf("version must be an integer: %w", err)
	}
	if err := json.Unmarshal(document["definitionDigest"], &entry.DefinitionDigest); err != nil {
		return ExtensionCapabilityEntry{}, fmt.Errorf("definitionDigest must be a string: %w", err)
	}
	if err := json.Unmarshal(document["defaultName"], &entry.DefaultName); err != nil {
		return ExtensionCapabilityEntry{}, fmt.Errorf("defaultName must be a string: %w", err)
	}
	if err := json.Unmarshal(document["defaultEnabled"], &entry.DefaultEnabled); err != nil {
		return ExtensionCapabilityEntry{}, fmt.Errorf("defaultEnabled must be a boolean: %w", err)
	}
	return entry, nil
}

func decodeExtensionCapabilityObject(label string, raw []byte) (map[string]json.RawMessage, error) {
	if len(bytes.TrimSpace(raw)) == 0 {
		return nil, fmt.Errorf("%s is required", label)
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	var document map[string]json.RawMessage
	if err := decoder.Decode(&document); err != nil {
		return nil, fmt.Errorf("decode %s: %w", strings.ToLower(label), err)
	}
	if document == nil {
		return nil, fmt.Errorf("%s must be an object", label)
	}
	var trailing json.RawMessage
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return nil, fmt.Errorf("%s must contain exactly one JSON document", label)
		}
		return nil, fmt.Errorf("decode trailing %s data: %w", strings.ToLower(label), err)
	}
	return document, nil
}
