package tool

import (
	"encoding/json"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"unicode/utf8"
)

var (
	// Ad noktalı olabilir: "catia.create_plane", "fiko.echo". LLM'in tool adı olarak
	// kullanabileceği güvenli biçim (küçük harf, rakam, _ ve tek nokta ayırıcı).
	nameRe    = regexp.MustCompile(`^[a-z][a-z0-9_]{0,63}(\.[a-z][a-z0-9_]{0,63}){0,3}$`)
	versionRe = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
)

// Normalize girdiyi kanonik biçime getirir: kırpar, izinleri küçük harfe indirip tekilleştirir,
// etiketleri sıralar, varsayılanları uygular ve listeleri nil yerine boş yapar
// (API'de `null` yerine `[]` dönsün). Doğrulamadan ÖNCE çağrılır.
func Normalize(t *Tool) {
	t.Name = strings.TrimSpace(t.Name)
	t.Title = strings.TrimSpace(t.Title)
	t.Description = strings.TrimSpace(t.Description)
	t.Version = strings.TrimSpace(t.Version)
	if t.Version == "" {
		t.Version = DefaultVersion
	}
	if t.Status == "" {
		t.Status = StatusEnabled
	}
	if t.Kind == "" {
		t.Kind = KindCustom
	}
	if t.UI == "" {
		t.UI = UIAuto
	}
	if t.Risk == "" {
		t.Risk = RiskLow
	}
	if t.TimeoutMs == 0 {
		t.TimeoutMs = DefaultTimeoutMs
	}
	t.Permissions = dedupe(t.Permissions, true)
	t.Tags = dedupe(t.Tags, true)
	if t.Permissions == nil {
		t.Permissions = []string{}
	}
	if t.Tags == nil {
		t.Tags = []string{}
	}
}

// dedupe kırpar, boşları atar, küçük harfe çevirir ve tekilleştirir; izinler
// kanonik sıraya (read, write, execute, delete, system) göre sıralanır,
// izin dışı adlar (etiketler) alfabetik kalır.
func dedupe(in []string, lower bool) []string {
	out := make([]string, 0, len(in))
	seen := make(map[string]bool, len(in))
	for _, v := range in {
		v = strings.TrimSpace(v)
		if lower {
			v = strings.ToLower(v)
		}
		if v == "" || seen[v] {
			continue
		}
		seen[v] = true
		out = append(out, v)
	}
	if lower {
		sortNames(out)
	}
	return out
}

func sortNames(names []string) {
	order := map[string]int{"read": 0, "write": 1, "execute": 2, "delete": 3, "system": 4}
	sort.SliceStable(names, func(i, j int) bool {
		oi, oki := order[names[i]]
		oj, okj := order[names[j]]
		switch {
		case !oki && !okj:
			return names[i] < names[j] // izin dışı adlar alfabetik
		case !oki:
			return false
		case !okj:
			return true
		}
		return oi < oj
	})
}

// Validate tool tanımını doğrular ve TÜM hataları döndürür (arayüz hepsini bir seferde gösterir).
// Tool önce Normalize edilmiş olmalıdır.
func Validate(t *Tool) []FieldError {
	var errs []FieldError
	add := func(field, msg string, args ...any) {
		errs = append(errs, FieldError{Field: field, Message: fmt.Sprintf(msg, args...)})
	}

	if !nameRe.MatchString(t.Name) {
		add("name", "küçük harfle başlamalı; noktayla ayrılmış bölümlerden oluşmalı (ör. catia.create_plane)")
	}
	if utf8.RuneCountInString(t.Title) > MaxTitleRunes {
		add("title", "en fazla %d karakter olabilir", MaxTitleRunes)
	}
	switch {
	case t.Description == "":
		add("description", "boş olamaz (LLM tool listesinde görünür)")
	case utf8.RuneCountInString(t.Description) > MaxDescriptionRunes:
		add("description", "en fazla %d karakter olabilir", MaxDescriptionRunes)
	}
	if !versionRe.MatchString(t.Version) {
		add("version", "MAJOR.MINOR.PATCH biçiminde olmalı (ör. 1.0.0)")
	}
	if !t.Status.Valid() {
		add("status", "enabled veya disabled olmalı")
	}
	if !t.Kind.Valid() {
		add("kind", "builtin, custom veya http olmalı")
	}
	if !t.UI.Valid() {
		labels := make([]string, 0, len(AllUI))
		for _, u := range AllUI {
			labels = append(labels, string(u))
		}
		add("ui", "şunlardan biri olmalı: "+strings.Join(labels, ", "))
	}
	if !t.Risk.Valid() {
		add("risk", "low, medium, high veya critical olmalı")
	}

	for _, f := range []struct {
		field  string
		schema map[string]any
	}{{"inputSchema", t.InputSchema}, {"outputSchema", t.OutputSchema}} {
		if f.schema == nil {
			continue
		}
		if ty, _ := f.schema["type"].(string); ty != "object" {
			add(f.field, `JSON Schema'nın "type" alanı "object" olmalı`)
		}
		if n := jsonLen(f.schema); n > MaxSchemaRunes {
			add(f.field, "şema çok büyük (en fazla %d bayt)", MaxSchemaRunes)
		}
	}

	if len(t.Permissions) > MaxPermissions {
		add("permissions", "en fazla %d izin olabilir", MaxPermissions)
	}
	for i, p := range t.Permissions {
		if _, err := ParsePermission(p); err != nil {
			add(fmt.Sprintf("permissions[%d]", i), "bilinmeyen izin %q (read, write, execute, delete, system)", p)
		}
	}

	switch {
	case t.TimeoutMs < 0:
		add("timeoutMs", "negatif olamaz")
	case t.TimeoutMs > MaxTimeoutMs:
		add("timeoutMs", "en fazla %d ms olabilir", MaxTimeoutMs)
	}
	if t.Retry.MaxAttempts < 0 || t.Retry.MaxAttempts > MaxRetryAttempts {
		add("retry.maxAttempts", "0 ile %d arasında olmalı", MaxRetryAttempts)
	}
	if t.Retry.BackoffMs < 0 {
		add("retry.backoffMs", "negatif olamaz")
	}

	if len(t.Tags) > MaxTags {
		add("tags", "en fazla %d etiket olabilir", MaxTags)
	}
	for i, tag := range t.Tags {
		if utf8.RuneCountInString(tag) > MaxTagRunes {
			add(fmt.Sprintf("tags[%d]", i), "en fazla %d karakter olabilir", MaxTagRunes)
		}
	}
	if n := jsonLen(t.Config); n > MaxConfigRunes {
		add("config", "çok büyük (en fazla %d bayt)", MaxConfigRunes)
	}
	return errs
}

// jsonLen bir değerin JSON uzunluğunu yaklaşık ölçer (sınırları aşan tanımı erken reddetmek için).
func jsonLen(v any) int {
	b, err := json.Marshal(v)
	if err != nil {
		return MaxConfigRunes + 1 // serileştirilemeyen config zaten sorunludur
	}
	return len(b)
}
