package builtin

// biz_spec.go şirket yönetimi koleksiyonlarının şemalarıdır. Alan doğrulaması, varsayılanlar,
// türetilen alanlar (fatura brüt tutarı...) ve kayıt çözümleme burada, LLM'den bağımsız yapılır:
// model yanlış tür/enum/tarih gönderse bile veri tutarlı kalır.

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

type bizField struct {
	Name     string
	Kind     string // string | email | enum | date | number
	Required bool
	Enum     []string
	Default  any // sabit varsayılan; "@today" tarih alanında bugünü verir
	MaxLen   int // metin alanları için (0 → 2000)
	Max      float64
}

type bizSpec struct {
	Name      string
	Label     string
	Prefix    string
	Fields    []bizField
	Refs      []string // ref ile çözümlenebilen alanlar (id'den başka)
	Unique    string   // büyük/küçük harf ve aksan duyarsız benzersiz alan
	DateField string   // from/to süzgeci ve sıralama için
	SortAsc   bool     // DateField artan sıralansın (görevler: en yakın termin önce)
	derive    func(rec map[string]any, existing []map[string]any, isNew bool, today string)
}

const todayDefault = "@today"

var (
	reEmail   = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)
	statusInv = []string{"draft", "sent", "paid", "cancelled"}
)

var bizSpecs = []*bizSpec{
	{
		Name: "customers", Label: "müşteriler", Prefix: "cus", Refs: []string{"name", "email"},
		Fields: []bizField{
			{Name: "name", Kind: "string", Required: true},
			{Name: "company", Kind: "string"},
			{Name: "email", Kind: "email"},
			{Name: "phone", Kind: "string", MaxLen: 40},
			{Name: "taxNo", Kind: "string", MaxLen: 20},
			{Name: "status", Kind: "enum", Enum: []string{"lead", "active", "inactive"}, Default: "active"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
	},
	{
		Name: "tasks", Label: "görevler", Prefix: "tsk", Refs: []string{"title"}, DateField: "due", SortAsc: true,
		Fields: []bizField{
			{Name: "title", Kind: "string", Required: true},
			{Name: "assignee", Kind: "string"},
			{Name: "due", Kind: "date"},
			{Name: "priority", Kind: "enum", Enum: []string{"low", "normal", "high"}, Default: "normal"},
			{Name: "status", Kind: "enum", Enum: []string{"open", "doing", "done"}, Default: "open"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
	},
	{
		Name: "invoices", Label: "faturalar", Prefix: "inv", Refs: []string{"number"}, Unique: "number", DateField: "issueDate",
		Fields: []bizField{
			{Name: "number", Kind: "string", MaxLen: 40},
			{Name: "customer", Kind: "string", Required: true},
			{Name: "issueDate", Kind: "date", Default: todayDefault},
			{Name: "dueDate", Kind: "date"},
			{Name: "net", Kind: "number", Required: true},
			{Name: "vatRate", Kind: "number", Default: float64(20), Max: 100},
			{Name: "status", Kind: "enum", Enum: statusInv, Default: "draft"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
		derive: deriveInvoice,
	},
	{
		Name: "expenses", Label: "giderler", Prefix: "exp", DateField: "date",
		Fields: []bizField{
			{Name: "description", Kind: "string", Required: true},
			{Name: "amount", Kind: "number", Required: true},
			{Name: "category", Kind: "string", MaxLen: 60},
			{Name: "date", Kind: "date", Default: todayDefault},
			{Name: "vendor", Kind: "string"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
	},
	{
		Name: "products", Label: "ürün/stok", Prefix: "prd", Refs: []string{"sku", "name"}, Unique: "sku",
		Fields: []bizField{
			{Name: "sku", Kind: "string", Required: true, MaxLen: 60},
			{Name: "name", Kind: "string", Required: true},
			{Name: "stock", Kind: "number", Default: float64(0)},
			{Name: "minStock", Kind: "number", Default: float64(0)},
			{Name: "unitPrice", Kind: "number"},
			{Name: "unit", Kind: "string", MaxLen: 20},
		},
	},
	{
		Name: "employees", Label: "personel", Prefix: "emp", Refs: []string{"name", "email"},
		Fields: []bizField{
			{Name: "name", Kind: "string", Required: true},
			{Name: "title", Kind: "string"},
			{Name: "department", Kind: "string", MaxLen: 80},
			{Name: "email", Kind: "email"},
			{Name: "phone", Kind: "string", MaxLen: 40},
			{Name: "startDate", Kind: "date"},
			{Name: "leaveBalance", Kind: "number", Default: float64(14), Max: 366},
		},
	},
	{
		Name: "notes", Label: "notlar/toplantı", Prefix: "not", Refs: []string{"title"},
		Fields: []bizField{
			{Name: "title", Kind: "string", Required: true},
			{Name: "content", Kind: "string", Required: true, MaxLen: 20000},
			{Name: "tags", Kind: "string", MaxLen: 200},
		},
	},

	// ---- PROJE YÖNETİMİ ----
	// Proje kaydı gövde alanlarının kaynağıdır; görev/risk/sprint `project` alanıyla
	// ona bağlanır. Alan adları görevler ("tasks") ile karışmasın diye projectTasks
	// adıyla ayrıldı: "tasks" zaten şirket geneli iş takibi için kullanılıyor.
	{
		Name: "projects", Label: "projeler", Prefix: "prj", Refs: []string{"code", "name"},
		Unique: "code", DateField: "startDate",
		Fields: []bizField{
			{Name: "name", Kind: "string", Required: true},
			{Name: "code", Kind: "string", MaxLen: 30},
			{Name: "description", Kind: "string", MaxLen: 5000},
			{Name: "customer", Kind: "string"},
			{Name: "manager", Kind: "string"},
			// plannedEnd + startDate → ilerleme yüzdesi türetilir.
			{Name: "startDate", Kind: "date"},
			{Name: "plannedEnd", Kind: "date"},
			{Name: "budget", Kind: "number"},
			{Name: "currency", Kind: "string", Default: "TRY", MaxLen: 8},
			{Name: "status", Kind: "enum", Enum: []string{"planning", "active", "onHold", "done", "cancelled"}, Default: "planning"},
			{Name: "priority", Kind: "enum", Enum: []string{"low", "normal", "high"}, Default: "normal"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
		derive: deriveProject,
	},
	{
		Name: "milestones", Label: "kilometre taşları", Prefix: "mls", Refs: []string{"title"},
		DateField: "due", SortAsc: true,
		Fields: []bizField{
			{Name: "title", Kind: "string", Required: true},
			{Name: "project", Kind: "string"},
			{Name: "due", Kind: "date"},
			{Name: "status", Kind: "enum", Enum: []string{"open", "done"}, Default: "open"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
	},
	{
		Name: "projectTasks", Label: "proje görevleri", Prefix: "pts", Refs: []string{"title"},
		DateField: "due", SortAsc: true,
		Fields: []bizField{
			{Name: "title", Kind: "string", Required: true},
			{Name: "project", Kind: "string"},
			{Name: "milestone", Kind: "string"},
			{Name: "assignee", Kind: "string"},
			{Name: "due", Kind: "date"},
			// gün cinsinden iş yükü: planlama/kapasite hesabı için.
			{Name: "estimate", Kind: "number"},
			{Name: "status", Kind: "enum", Enum: []string{"backlog", "todo", "doing", "done", "blocked"}, Default: "todo"},
			{Name: "priority", Kind: "enum", Enum: []string{"low", "normal", "high"}, Default: "normal"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
	},
	{
		Name: "risks", Label: "riskler", Prefix: "rsk", Refs: []string{"title"},
		Fields: []bizField{
			{Name: "title", Kind: "string", Required: true},
			{Name: "project", Kind: "string"},
			{Name: "impact", Kind: "enum", Enum: []string{"low", "medium", "high"}, Default: "medium"},
			{Name: "likelihood", Kind: "enum", Enum: []string{"low", "medium", "high"}, Default: "medium"},
			{Name: "mitigation", Kind: "string", MaxLen: 5000},
			{Name: "status", Kind: "enum", Enum: []string{"open", "mitigated", "closed"}, Default: "open"},
			{Name: "notes", Kind: "string", MaxLen: 5000},
		},
	},
	{
		Name: "sprints", Label: "sprint'lar", Prefix: "spt", Refs: []string{"name"},
		DateField: "startDate",
		Fields: []bizField{
			{Name: "name", Kind: "string", Required: true},
			{Name: "project", Kind: "string"},
			{Name: "startDate", Kind: "date"},
			{Name: "endDate", Kind: "date"},
			{Name: "goal", Kind: "string", MaxLen: 2000},
			{Name: "status", Kind: "enum", Enum: []string{"planned", "active", "done"}, Default: "planned"},
		},
	},
	{
		Name: "timeEntries", Label: "harcanan süre", Prefix: "tim", Refs: nil, DateField: "date",
		Fields: []bizField{
			{Name: "task", Kind: "string"},
			{Name: "person", Kind: "string", Required: true},
			{Name: "hours", Kind: "number", Required: true},
			{Name: "date", Kind: "date", Default: todayDefault},
			{Name: "notes", Kind: "string", MaxLen: 2000},
		},
	},
	{
		Name: "meetings", Label: "toplantılar", Prefix: "mtg", Refs: []string{"title"},
		DateField: "date",
		Fields: []bizField{
			{Name: "title", Kind: "string", Required: true},
			{Name: "project", Kind: "string"},
			{Name: "date", Kind: "date", Default: todayDefault},
			{Name: "attendees", Kind: "string", MaxLen: 2000},
			{Name: "decisions", Kind: "string", MaxLen: 5000},
			{Name: "status", Kind: "enum", Enum: []string{"planned", "done", "cancelled"}, Default: "planned"},
		},
	},
}

func specFor(v any) (*bizSpec, bool) {
	name, _ := v.(string)
	name = strings.TrimSpace(name)
	for _, sp := range bizSpecs {
		// Kıyaslama büyük/küçük harf duyarsızdır: LLM "projectTasks" yerine
		// "projecttasks" gönderebilir; şema camelCase tanımlıdır ve BOZULMAZ.
		if strings.EqualFold(sp.Name, name) {
			return sp, true
		}
	}
	return nil, false
}

func specNames() []any {
	out := make([]any, 0, len(bizSpecs))
	for _, sp := range bizSpecs {
		out = append(out, sp.Name)
	}
	return out
}

// describeSpecs LLM'in alan adlarını bilmesi için kısa şema özeti üretir (tool açıklamasına girer).
func describeSpecs() string {
	var b strings.Builder
	for _, sp := range bizSpecs {
		fmt.Fprintf(&b, "%s (%s): ", sp.Name, sp.Label)
		for i, f := range sp.Fields {
			if i > 0 {
				b.WriteString(", ")
			}
			b.WriteString(f.Name)
			if f.Required {
				b.WriteString("*")
			}
			switch f.Kind {
			case "enum":
				b.WriteString("{" + strings.Join(f.Enum, "|") + "}")
			case "date":
				b.WriteString("[tarih]")
			case "number":
				b.WriteString("[sayı]")
			}
		}
		b.WriteString(". ")
	}
	return strings.TrimSpace(b.String())
}

func (sp *bizSpec) field(name string) *bizField {
	for i := range sp.Fields {
		if sp.Fields[i].Name == name {
			return &sp.Fields[i]
		}
	}
	return nil
}

var foldReplacer = strings.NewReplacer(
	"İ", "i", "I", "i", "ı", "i", "Ç", "c", "ç", "c", "Ş", "s", "ş", "s",
	"Ğ", "g", "ğ", "g", "Ö", "o", "ö", "o", "Ü", "u", "ü", "u",
)

// fold Türkçe aksan ve büyük/küçük harf farkını yok sayan karşılaştırma anahtarıdır.
func fold(s string) string { return strings.ToLower(foldReplacer.Replace(strings.TrimSpace(s))) }

func parseBizDate(s string) (string, bool) {
	for _, layout := range []string{"2006-01-02", "2.1.2006", "2/1/2006"} {
		if t, err := time.Parse(layout, strings.TrimSpace(s)); err == nil {
			return t.Format("2006-01-02"), true
		}
	}
	return "", false
}

// coerce tek alan değerini türüne çevirir. cleared=true: değer boş (alanı temizle).
func (f *bizField) coerce(v any) (val any, cleared bool, problem string) {
	if v == nil {
		return nil, true, ""
	}
	if f.Kind == "number" {
		var n float64
		switch x := v.(type) {
		case float64:
			n = x
		case int:
			n = float64(x)
		case int64:
			n = float64(x)
		case json.Number:
			var err error
			if n, err = x.Float64(); err != nil {
				return nil, false, "sayı olmalı"
			}
		case string:
			s := strings.TrimSpace(strings.ReplaceAll(x, " ", ""))
			if s == "" {
				return nil, true, ""
			}
			// "1.500,50" (TR) ve "1500.50" (EN) biçimlerini ayırt et.
			if strings.Contains(s, ",") {
				s = strings.ReplaceAll(strings.ReplaceAll(s, ".", ""), ",", ".")
			}
			var err error
			if n, err = strconv.ParseFloat(s, 64); err != nil {
				return nil, false, "sayı olmalı"
			}
		default:
			return nil, false, "sayı olmalı"
		}
		if math.IsNaN(n) || math.IsInf(n, 0) || n < 0 {
			return nil, false, "negatif olmayan bir sayı olmalı"
		}
		if f.Max > 0 && n > f.Max {
			return nil, false, fmt.Sprintf("en çok %g olabilir", f.Max)
		}
		return round(n, 4), false, ""
	}

	var s string
	switch x := v.(type) {
	case string:
		s = x
	case float64:
		s = strconv.FormatFloat(x, 'f', -1, 64) // telefon/vergi no sayı olarak gelebilir
	default:
		return nil, false, "metin olmalı"
	}
	s = strings.TrimSpace(s)
	if s == "" {
		return nil, true, ""
	}
	switch f.Kind {
	case "enum":
		l := strings.ToLower(s)
		for _, e := range f.Enum {
			if e == l {
				return e, false, ""
			}
		}
		return nil, false, "şunlardan biri olmalı: " + strings.Join(f.Enum, " | ")
	case "email":
		if !reEmail.MatchString(s) {
			return nil, false, "geçerli bir e-posta olmalı"
		}
		return strings.ToLower(s), false, ""
	case "date":
		d, ok := parseBizDate(s)
		if !ok {
			return nil, false, "tarih YYYY-MM-DD (ya da GG.AA.YYYY) olmalı"
		}
		return d, false, ""
	default:
		max := f.MaxLen
		if max <= 0 {
			max = 2000
		}
		if utf8.RuneCountInString(s) > max {
			return nil, false, fmt.Sprintf("en çok %d karakter olabilir", max)
		}
		return s, false, ""
	}
}

// normalize gelen alanları doğrular. partial=false → ekleme (zorunlular + varsayılanlar);
// partial=true → güncelleme (yalnızca gönderilenler; nil değer = alanı temizle).
func (sp *bizSpec) normalize(in map[string]any, partial bool, today string) (map[string]any, []string) {
	out := map[string]any{}
	var errs []string
	keys := make([]string, 0, len(in))
	for k := range in {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		f := sp.field(k)
		if f == nil {
			errs = append(errs, fmt.Sprintf("%s: bilinmeyen alan", k))
			continue
		}
		val, cleared, problem := f.coerce(in[k])
		switch {
		case problem != "":
			errs = append(errs, k+": "+problem)
		case cleared && f.Required:
			errs = append(errs, k+": boş olamaz")
		case cleared:
			out[k] = nil
		default:
			out[k] = val
		}
	}
	if !partial {
		for _, f := range sp.Fields {
			if v, ok := out[f.Name]; ok && v != nil {
				continue
			}
			delete(out, f.Name)
			switch {
			case f.Default == todayDefault:
				out[f.Name] = today
			case f.Default != nil:
				out[f.Name] = f.Default
			case f.Required:
				errs = append(errs, f.Name+": zorunlu")
			}
		}
	}
	return out, errs
}

func (sp *bizSpec) checkUnique(rec map[string]any, recs []map[string]any, selfID string) error {
	if sp.Unique == "" {
		return nil
	}
	v, _ := rec[sp.Unique].(string)
	if v == "" {
		return nil
	}
	for _, r := range recs {
		if id, _ := r["id"].(string); id == selfID {
			continue
		}
		if o, _ := r[sp.Unique].(string); o != "" && fold(o) == fold(v) {
			return fmt.Errorf("%s %q zaten kayıtlı", sp.Unique, v)
		}
	}
	return nil
}

// resolve ref'i (id ya da Refs alanlarından biri) kayıt dizinine çevirir.
func (sp *bizSpec) resolve(recs []map[string]any, ref string) (int, error) {
	ref = strings.TrimSpace(ref)
	if ref == "" {
		return -1, fmt.Errorf("ref (kayıt kimliği) boş olamaz")
	}
	for i, r := range recs {
		if id, _ := r["id"].(string); id == ref {
			return i, nil
		}
	}
	f := fold(ref)
	var hits []int
	for i, r := range recs {
		for _, name := range sp.Refs {
			if s, _ := r[name].(string); s != "" && fold(s) == f {
				hits = append(hits, i)
				break
			}
		}
	}
	switch len(hits) {
	case 0:
		return -1, fmt.Errorf("%s içinde kayıt bulunamadı: %q", sp.Name, ref)
	case 1:
		return hits[0], nil
	default:
		return -1, fmt.Errorf("%q için %d kayıt eşleşti; kesin olması için id kullan", ref, len(hits))
	}
}

// deriveInvoice KDV tutarını ve brüt tutarı hesaplar; yeni faturada numarayı otomatik verir.
func deriveInvoice(rec map[string]any, existing []map[string]any, isNew bool, today string) {
	net, _ := rec["net"].(float64)
	vat := 20.0
	if v, ok := rec["vatRate"].(float64); ok {
		vat = v
	}
	vatAmount := round(net*vat/100, 2)
	rec["vatAmount"] = vatAmount
	rec["gross"] = round(net+vatAmount, 2)
	if n, _ := rec["number"].(string); n == "" && isNew {
		prefix := "FTR-" + today[:4] + "-"
		max := 0
		for _, r := range existing {
			if s, _ := r["number"].(string); strings.HasPrefix(s, prefix) {
				if seq, err := strconv.Atoi(strings.TrimPrefix(s, prefix)); err == nil && seq > max {
					max = seq
				}
			}
		}
		rec["number"] = fmt.Sprintf("%s%04d", prefix, max+1)
	}
}

// deriveProject proje planının zaman çizelgesini normalize eder ve "planGün" alanını
// türetir. Gerekçe: ilerleme yüzdesi = geçen gün / toplam plan günü. Bu değer
// KAYITTA tutulur (LLM her seferinde yeniden hesaplamaz, tek hesap kuralı).
//
// plannedEnd boşsa bittiği varsayılmaz: ilerleme hesaplanamaz, "planGün" yazılmaz.
func deriveProject(rec map[string]any, _ []map[string]any, _ bool, _ string) {
	start, _ := rec["startDate"].(string)
	end, _ := rec["plannedEnd"].(string)
	rec["planGün"] = nil
	if start == "" || end == "" || end < start {
		return
	}
	s, okS := bizTime(start)
	e, okE := bizTime(end)
	if !okS || !okE {
		return
	}
	days := int(e.Sub(s).Hours() / 24)
	if days < 0 {
		return
	}
	rec["planGün"] = float64(days)
}
