package skill

import (
	"fmt"
	"regexp"
	"strings"
	"unicode/utf8"
)

// Sınırlar: kötü niyetli/yanlışlıkla devasa skill kaydı prompt'u ve depoyu şişirmesin.
const (
	MaxDescriptionRunes = 2000
	MaxParameters       = 50
	MaxExamples         = 20
	MaxPermissions      = 20
	MaxTags             = 20
	MaxTagRunes         = 32
	MaxExampleRunes     = 500
	DefaultVersion      = "1.0.0"
)

var (
	// Ad: LLM'in tool adı olarak da kullanabileceği güvenli biçim (snake_case).
	nameRe    = regexp.MustCompile(`^[a-z][a-z0-9_]{1,63}$`)
	paramRe   = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]{0,63}$`)
	versionRe = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
	permRe    = regexp.MustCompile(`^[a-z][a-z0-9_.:*-]{0,63}$`)
)

// FieldError tek bir alanın doğrulama hatasıdır; arayüz alanı işaretleyebilsin diye yapısaldır.
type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

func (e FieldError) Error() string { return e.Field + ": " + e.Message }

// ValidationError bir ya da daha fazla alan hatasını taşır. Service.Register/Update bunu döndürür.
type ValidationError struct{ Errors []FieldError }

func (e *ValidationError) Error() string {
	parts := make([]string, len(e.Errors))
	for i, fe := range e.Errors {
		parts[i] = fe.Error()
	}
	return "skill geçersiz — " + strings.Join(parts, "; ")
}

// Normalize girdiyi kanonik biçime getirir: boşlukları kırpar, etiketleri küçük harfe çevirip tekilleştirir,
// varsayılanları uygular ve listeleri nil yerine boş yapar (API'de `null` yerine `[]` dönsün).
// Doğrulamadan ÖNCE çağrılır; kayıt ve doğrulama aynı kanonik veriyi görür.
func Normalize(s *Skill) {
	s.Name = strings.TrimSpace(s.Name)
	s.Description = strings.TrimSpace(s.Description)
	s.Version = strings.TrimSpace(s.Version)
	if s.Version == "" {
		s.Version = DefaultVersion
	}
	if s.Status == "" {
		s.Status = StatusDraft
	}
	if s.ExecutorType == "" {
		s.ExecutorType = ExecutorTool
	}
	for i := range s.Parameters {
		s.Parameters[i].Name = strings.TrimSpace(s.Parameters[i].Name)
		s.Parameters[i].Description = strings.TrimSpace(s.Parameters[i].Description)
	}
	for i := range s.Examples {
		s.Examples[i].Input = strings.TrimSpace(s.Examples[i].Input)
	}
	s.Tags = dedupe(s.Tags, true)
	s.Permissions = dedupe(s.Permissions, false)
	if s.Parameters == nil {
		s.Parameters = []Parameter{}
	}
	if s.Examples == nil {
		s.Examples = []Example{}
	}
}

// dedupe kırpar, boşları atar, (istenirse) küçük harfe çevirir ve ilk görülme sırasını koruyarak tekilleştirir.
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)
	}
	return out
}

// Validate skill'i doğrular ve TÜM hataları döndürür (arayüz hepsini bir seferde gösterebilsin).
// Skill önce Normalize edilmiş olmalıdır. Taslak (draft) skill'de executor tanımı zorunlu değildir:
// yarım iş kaydedilebilsin; etkinleştirirken (active) tamamlanması gerekir.
func Validate(s *Skill) []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(s.Name) {
		add("name", "küçük harfle başlamalı; yalnızca küçük harf, rakam ve _ içermeli; 2-64 karakter olmalı")
	}
	switch {
	case s.Description == "":
		add("description", "boş olamaz (vektör aramanın temelidir)")
	case utf8.RuneCountInString(s.Description) > MaxDescriptionRunes:
		add("description", "en fazla %d karakter olabilir", MaxDescriptionRunes)
	}
	if !versionRe.MatchString(s.Version) {
		add("version", "MAJOR.MINOR.PATCH biçiminde olmalı (ör. 1.0.0)")
	}
	if !s.Status.Valid() {
		add("status", "draft, active veya disabled olmalı")
	}

	validateParameters(s.Parameters, add)

	for _, f := range []struct {
		name   string
		schema map[string]any
	}{{"inputSchema", s.InputSchema}, {"outputSchema", s.OutputSchema}} {
		if f.schema != nil {
			if t, _ := f.schema["type"].(string); t != "object" {
				add(f.name, `JSON Schema'nın "type" alanı "object" olmalı`)
			}
		}
	}

	if !s.ExecutorType.Valid() {
		add("executorType", "tool veya llm olmalı")
	} else if s.Status != StatusDraft {
		key := "tool"
		if s.ExecutorType == ExecutorLLM {
			key = "prompt"
		}
		if v, _ := s.ExecutorConfig[key].(string); strings.TrimSpace(v) == "" {
			add("executorConfig", `%q yürütücüsü için "%s" alanı zorunlu (taslakta zorunlu değil)`, s.ExecutorType, key)
		}
	}

	if len(s.Examples) > MaxExamples {
		add("examples", "en fazla %d örnek olabilir", MaxExamples)
	}
	for i, e := range s.Examples {
		if e.Input == "" || utf8.RuneCountInString(e.Input) > MaxExampleRunes {
			add(fmt.Sprintf("examples[%d].input", i), "boş olamaz ve en fazla %d karakter olabilir", MaxExampleRunes)
		}
	}

	if len(s.Permissions) > MaxPermissions {
		add("permissions", "en fazla %d izin olabilir", MaxPermissions)
	}
	for i, p := range s.Permissions {
		if !permRe.MatchString(p) {
			add(fmt.Sprintf("permissions[%d]", i), "küçük harfle başlamalı; boşluk içermemeli (ör. catia.write)")
		}
	}

	if len(s.Tags) > MaxTags {
		add("tags", "en fazla %d etiket olabilir", MaxTags)
	}
	for i, t := range s.Tags {
		if utf8.RuneCountInString(t) > MaxTagRunes {
			add(fmt.Sprintf("tags[%d]", i), "en fazla %d karakter olabilir", MaxTagRunes)
		}
	}
	return errs
}

func validateParameters(params []Parameter, add func(field, msg string, args ...any)) {
	if len(params) > MaxParameters {
		add("parameters", "en fazla %d parametre olabilir", MaxParameters)
		return
	}
	seen := make(map[string]bool, len(params))
	for i, p := range params {
		field := fmt.Sprintf("parameters[%d]", i)
		switch {
		case !paramRe.MatchString(p.Name):
			add(field+".name", "harf veya _ ile başlamalı; yalnızca harf, rakam ve _ içermeli")
		case seen[p.Name]:
			add(field+".name", "%q birden fazla kez tanımlanmış", p.Name)
		}
		seen[p.Name] = true
		if !p.Type.Valid() {
			add(field+".type", "string, number, integer, boolean, array veya object olmalı")
		}
		if utf8.RuneCountInString(p.Description) > 500 {
			add(field+".description", "en fazla 500 karakter olabilir")
		}
	}
}
