package diff

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"sort"
	"strings"

	"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)

// DiffOpenAICompatibility produces human-readable change descriptions.
func DiffOpenAICompatibility(oldList, newList []config.OpenAICompatibility) []string {
	changes := make([]string, 0)
	oldMap := make(map[string]config.OpenAICompatibility, len(oldList))
	oldLabels := make(map[string]string, len(oldList))
	for idx, entry := range oldList {
		key, label := openAICompatKey(entry, idx)
		oldMap[key] = entry
		oldLabels[key] = label
	}
	newMap := make(map[string]config.OpenAICompatibility, len(newList))
	newLabels := make(map[string]string, len(newList))
	for idx, entry := range newList {
		key, label := openAICompatKey(entry, idx)
		newMap[key] = entry
		newLabels[key] = label
	}
	keySet := make(map[string]struct{}, len(oldMap)+len(newMap))
	for key := range oldMap {
		keySet[key] = struct{}{}
	}
	for key := range newMap {
		keySet[key] = struct{}{}
	}
	orderedKeys := make([]string, 0, len(keySet))
	for key := range keySet {
		orderedKeys = append(orderedKeys, key)
	}
	sort.Strings(orderedKeys)
	for _, key := range orderedKeys {
		oldEntry, oldOk := oldMap[key]
		newEntry, newOk := newMap[key]
		label := oldLabels[key]
		if label == "" {
			label = newLabels[key]
		}
		switch {
		case !oldOk:
			changes = append(changes, fmt.Sprintf("provider added: %s (api-keys=%d, models=%d)", label, countAPIKeys(newEntry), countOpenAIModels(newEntry.Models)))
		case !newOk:
			changes = append(changes, fmt.Sprintf("provider removed: %s (api-keys=%d, models=%d)", label, countAPIKeys(oldEntry), countOpenAIModels(oldEntry.Models)))
		default:
			if detail := describeOpenAICompatibilityUpdate(oldEntry, newEntry); detail != "" {
				changes = append(changes, fmt.Sprintf("provider updated: %s %s", label, detail))
			}
		}
	}
	return changes
}

func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibility) string {
	oldKeyCount := countAPIKeys(oldEntry)
	newKeyCount := countAPIKeys(newEntry)
	oldModelCount := countOpenAIModels(oldEntry.Models)
	newModelCount := countOpenAIModels(newEntry.Models)
	details := make([]string, 0, 3)
	if oldEntry.Disabled != newEntry.Disabled {
		details = append(details, fmt.Sprintf("disabled %t -> %t", oldEntry.Disabled, newEntry.Disabled))
	}
	if oldKeyCount != newKeyCount {
		details = append(details, fmt.Sprintf("api-keys %d -> %d", oldKeyCount, newKeyCount))
	}
	if oldModelCount != newModelCount {
		details = append(details, fmt.Sprintf("models %d -> %d", oldModelCount, newModelCount))
	}
	if !equalStringMap(oldEntry.Headers, newEntry.Headers) {
		details = append(details, "headers updated")
	}
	if len(details) == 0 {
		return ""
	}
	return "(" + strings.Join(details, ", ") + ")"
}

func countAPIKeys(entry config.OpenAICompatibility) int {
	count := 0
	for _, keyEntry := range entry.APIKeyEntries {
		if strings.TrimSpace(keyEntry.APIKey) != "" {
			count++
		}
	}
	return count
}

func countOpenAIModels(models []config.OpenAICompatibilityModel) int {
	count := 0
	for _, model := range models {
		name := strings.TrimSpace(model.Name)
		alias := strings.TrimSpace(model.Alias)
		if name == "" && alias == "" {
			continue
		}
		count++
	}
	return count
}

func openAICompatKey(entry config.OpenAICompatibility, index int) (string, string) {
	name := strings.TrimSpace(entry.Name)
	if name != "" {
		return "name:" + name, name
	}
	base := strings.TrimSpace(entry.BaseURL)
	if base != "" {
		return "base:" + base, base
	}
	for _, model := range entry.Models {
		alias := strings.TrimSpace(model.Alias)
		if alias == "" {
			alias = strings.TrimSpace(model.Name)
		}
		if alias != "" {
			return "alias:" + alias, alias
		}
	}
	sig := openAICompatSignature(entry)
	if sig == "" {
		return fmt.Sprintf("index:%d", index), fmt.Sprintf("entry-%d", index+1)
	}
	short := sig
	if len(short) > 8 {
		short = short[:8]
	}
	return "sig:" + sig, "compat-" + short
}

func openAICompatSignature(entry config.OpenAICompatibility) string {
	var parts []string

	if v := strings.TrimSpace(entry.Name); v != "" {
		parts = append(parts, "name="+strings.ToLower(v))
	}
	if v := strings.TrimSpace(entry.BaseURL); v != "" {
		parts = append(parts, "base="+v)
	}

	models := make([]string, 0, len(entry.Models))
	for _, model := range entry.Models {
		name := strings.TrimSpace(model.Name)
		alias := strings.TrimSpace(model.Alias)
		if name == "" && alias == "" {
			continue
		}
		models = append(models, strings.ToLower(name)+"|"+strings.ToLower(alias))
	}
	if len(models) > 0 {
		sort.Strings(models)
		parts = append(parts, "models="+strings.Join(models, ","))
	}

	if len(entry.Headers) > 0 {
		keys := make([]string, 0, len(entry.Headers))
		for k := range entry.Headers {
			if trimmed := strings.TrimSpace(k); trimmed != "" {
				keys = append(keys, strings.ToLower(trimmed))
			}
		}
		if len(keys) > 0 {
			sort.Strings(keys)
			parts = append(parts, "headers="+strings.Join(keys, ","))
		}
	}

	// Intentionally exclude API key material; only count non-empty entries.
	if count := countAPIKeys(entry); count > 0 {
		parts = append(parts, fmt.Sprintf("api_keys=%d", count))
	}

	if len(parts) == 0 {
		return ""
	}
	sum := sha256.Sum256([]byte(strings.Join(parts, "|")))
	return hex.EncodeToString(sum[:])
}
