package config

import (
	"os"
	"strings"

	"gopkg.in/yaml.v3"
)

// antigravityModelConversionTable maps old built-in aliases to actual model names
// for the antigravity channel during migration.
var antigravityModelConversionTable = map[string]string{
	"gemini-2.5-computer-use-preview-10-2025": "rev19-uic3-1p",
	"gemini-3-pro-image-preview":              "gemini-3-pro-image",
	"gemini-3-pro-preview":                    "gemini-3-pro-high",
	"gemini-3-flash-preview":                  "gemini-3-flash",
	"gemini-claude-sonnet-4-5":                "claude-sonnet-4-5",
	"gemini-claude-sonnet-4-5-thinking":       "claude-sonnet-4-5-thinking",
	"gemini-claude-opus-4-5-thinking":         "claude-opus-4-5-thinking",
}

// defaultAntigravityAliases returns the default oauth-model-alias configuration
// for the antigravity channel when neither field exists.
func defaultAntigravityAliases() []OAuthModelAlias {
	return []OAuthModelAlias{
		{Name: "rev19-uic3-1p", Alias: "gemini-2.5-computer-use-preview-10-2025"},
		{Name: "gemini-3-pro-image", Alias: "gemini-3-pro-image-preview"},
		{Name: "gemini-3-pro-high", Alias: "gemini-3-pro-preview"},
		{Name: "gemini-3-flash", Alias: "gemini-3-flash-preview"},
		{Name: "claude-sonnet-4-5", Alias: "gemini-claude-sonnet-4-5"},
		{Name: "claude-sonnet-4-5-thinking", Alias: "gemini-claude-sonnet-4-5-thinking"},
		{Name: "claude-opus-4-5-thinking", Alias: "gemini-claude-opus-4-5-thinking"},
	}
}

// MigrateOAuthModelAlias checks for and performs migration from oauth-model-mappings
// to oauth-model-alias at startup. Returns true if migration was performed.
//
// Migration flow:
// 1. Check if oauth-model-alias exists -> skip migration
// 2. Check if oauth-model-mappings exists -> convert and migrate
//   - For antigravity channel, convert old built-in aliases to actual model names
//
// 3. Neither exists -> add default antigravity config
func MigrateOAuthModelAlias(configFile string) (bool, error) {
	data, err := os.ReadFile(configFile)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	if len(data) == 0 {
		return false, nil
	}

	// Parse YAML into node tree to preserve structure
	var root yaml.Node
	if err := yaml.Unmarshal(data, &root); err != nil {
		return false, nil
	}
	if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
		return false, nil
	}
	rootMap := root.Content[0]
	if rootMap == nil || rootMap.Kind != yaml.MappingNode {
		return false, nil
	}

	// Check if oauth-model-alias already exists
	if findMapKeyIndex(rootMap, "oauth-model-alias") >= 0 {
		return false, nil
	}

	// Check if oauth-model-mappings exists
	oldIdx := findMapKeyIndex(rootMap, "oauth-model-mappings")
	if oldIdx >= 0 {
		// Migrate from old field
		return migrateFromOldField(configFile, &root, rootMap, oldIdx)
	}

	// Neither field exists - add default antigravity config
	return addDefaultAntigravityConfig(configFile, &root, rootMap)
}

// migrateFromOldField converts oauth-model-mappings to oauth-model-alias
func migrateFromOldField(configFile string, root *yaml.Node, rootMap *yaml.Node, oldIdx int) (bool, error) {
	if oldIdx+1 >= len(rootMap.Content) {
		return false, nil
	}
	oldValue := rootMap.Content[oldIdx+1]
	if oldValue == nil || oldValue.Kind != yaml.MappingNode {
		return false, nil
	}

	// Parse the old aliases
	oldAliases := parseOldAliasNode(oldValue)
	if len(oldAliases) == 0 {
		// Remove the old field and write
		removeMapKeyByIndex(rootMap, oldIdx)
		return writeYAMLNode(configFile, root)
	}

	// Convert model names for antigravity channel
	newAliases := make(map[string][]OAuthModelAlias, len(oldAliases))
	for channel, entries := range oldAliases {
		converted := make([]OAuthModelAlias, 0, len(entries))
		for _, entry := range entries {
			newEntry := OAuthModelAlias{
				Name:  entry.Name,
				Alias: entry.Alias,
				Fork:  entry.Fork,
			}
			// Convert model names for antigravity channel
			if strings.EqualFold(channel, "antigravity") {
				if actual, ok := antigravityModelConversionTable[entry.Name]; ok {
					newEntry.Name = actual
				}
			}
			converted = append(converted, newEntry)
		}
		newAliases[channel] = converted
	}

	// For antigravity channel, supplement missing default aliases
	if antigravityEntries, exists := newAliases["antigravity"]; exists {
		// Build a set of already configured model names (upstream names)
		configuredModels := make(map[string]bool, len(antigravityEntries))
		for _, entry := range antigravityEntries {
			configuredModels[entry.Name] = true
		}

		// Add missing default aliases
		for _, defaultAlias := range defaultAntigravityAliases() {
			if !configuredModels[defaultAlias.Name] {
				antigravityEntries = append(antigravityEntries, defaultAlias)
			}
		}
		newAliases["antigravity"] = antigravityEntries
	}

	// Build new node
	newNode := buildOAuthModelAliasNode(newAliases)

	// Replace old key with new key and value
	rootMap.Content[oldIdx].Value = "oauth-model-alias"
	rootMap.Content[oldIdx+1] = newNode

	return writeYAMLNode(configFile, root)
}

// addDefaultAntigravityConfig adds the default antigravity configuration
func addDefaultAntigravityConfig(configFile string, root *yaml.Node, rootMap *yaml.Node) (bool, error) {
	defaults := map[string][]OAuthModelAlias{
		"antigravity": defaultAntigravityAliases(),
	}
	newNode := buildOAuthModelAliasNode(defaults)

	// Add new key-value pair
	keyNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "oauth-model-alias"}
	rootMap.Content = append(rootMap.Content, keyNode, newNode)

	return writeYAMLNode(configFile, root)
}

// parseOldAliasNode parses the old oauth-model-mappings node structure
func parseOldAliasNode(node *yaml.Node) map[string][]OAuthModelAlias {
	if node == nil || node.Kind != yaml.MappingNode {
		return nil
	}
	result := make(map[string][]OAuthModelAlias)
	for i := 0; i+1 < len(node.Content); i += 2 {
		channelNode := node.Content[i]
		entriesNode := node.Content[i+1]
		if channelNode == nil || entriesNode == nil {
			continue
		}
		channel := strings.ToLower(strings.TrimSpace(channelNode.Value))
		if channel == "" || entriesNode.Kind != yaml.SequenceNode {
			continue
		}
		entries := make([]OAuthModelAlias, 0, len(entriesNode.Content))
		for _, entryNode := range entriesNode.Content {
			if entryNode == nil || entryNode.Kind != yaml.MappingNode {
				continue
			}
			entry := parseAliasEntry(entryNode)
			if entry.Name != "" && entry.Alias != "" {
				entries = append(entries, entry)
			}
		}
		if len(entries) > 0 {
			result[channel] = entries
		}
	}
	return result
}

// parseAliasEntry parses a single alias entry node
func parseAliasEntry(node *yaml.Node) OAuthModelAlias {
	var entry OAuthModelAlias
	for i := 0; i+1 < len(node.Content); i += 2 {
		keyNode := node.Content[i]
		valNode := node.Content[i+1]
		if keyNode == nil || valNode == nil {
			continue
		}
		switch strings.ToLower(strings.TrimSpace(keyNode.Value)) {
		case "name":
			entry.Name = strings.TrimSpace(valNode.Value)
		case "alias":
			entry.Alias = strings.TrimSpace(valNode.Value)
		case "fork":
			entry.Fork = strings.ToLower(strings.TrimSpace(valNode.Value)) == "true"
		}
	}
	return entry
}

// buildOAuthModelAliasNode creates a YAML node for oauth-model-alias
func buildOAuthModelAliasNode(aliases map[string][]OAuthModelAlias) *yaml.Node {
	node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
	for channel, entries := range aliases {
		channelNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: channel}
		entriesNode := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
		for _, entry := range entries {
			entryNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
			entryNode.Content = append(entryNode.Content,
				&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "name"},
				&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: entry.Name},
				&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "alias"},
				&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: entry.Alias},
			)
			if entry.Fork {
				entryNode.Content = append(entryNode.Content,
					&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "fork"},
					&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!bool", Value: "true"},
				)
			}
			entriesNode.Content = append(entriesNode.Content, entryNode)
		}
		node.Content = append(node.Content, channelNode, entriesNode)
	}
	return node
}

// removeMapKeyByIndex removes a key-value pair from a mapping node by index
func removeMapKeyByIndex(mapNode *yaml.Node, keyIdx int) {
	if mapNode == nil || mapNode.Kind != yaml.MappingNode {
		return
	}
	if keyIdx < 0 || keyIdx+1 >= len(mapNode.Content) {
		return
	}
	mapNode.Content = append(mapNode.Content[:keyIdx], mapNode.Content[keyIdx+2:]...)
}

// writeYAMLNode writes the YAML node tree back to file
func writeYAMLNode(configFile string, root *yaml.Node) (bool, error) {
	f, err := os.Create(configFile)
	if err != nil {
		return false, err
	}
	defer f.Close()

	enc := yaml.NewEncoder(f)
	enc.SetIndent(2)
	if err := enc.Encode(root); err != nil {
		return false, err
	}
	if err := enc.Close(); err != nil {
		return false, err
	}
	return true, nil
}
