package builtin

// datetime.go — tarih/zaman araçları.
//
// Neden? Proje planlamada "teslim tarihi ne zaman", "kaç gün kaldı", "iş günü
// hesabı sürekli gerekir. Bu hesaplar elle ve hata yapılır (özellikle iş günü:
// hafta sonu, resmi tatil). LLM tarih aritmetiğinde güvenilir DEĞİLDİR —
// yanlış sayı üretip rapora yazmak ciddi risktir. Burada hesap koddur.

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"fiko-ai/internal/tool"
)

// dateDefinitions tarih araçlarını üretir.
func dateDefinitions(o Options) []Def {
	return []Def{
		{
			Enabled: true,
			Tool: tool.Tool{
				Name:        "date.diff",
				Title:       "Tarih farkı",
				Description: "İki tarih arasındaki farkı gün, saat, hafta ve ay olarak hesaplar (iş günü seçeneğiyle hafta sonları hariç).",
				Tags:        []string{"date"},
				Risk:        tool.RiskLow,
				Permissions: []string{"execute"},
				InputSchema: obj("type", "object",
					"properties", obj(
						"from", str("Başlangıç tarihi (YYYY-MM-DD)"),
						"to", str("Bitiş tarihi (YYYY-MM-DD); boşsa bugün"),
						"timezone", str("Saat dilimi (örn. Europe/Istanbul); boşsa UTC"),
					),
					"required", []any{"from"},
				),
				OutputSchema: obj("type", "object", "properties", obj(
					"days", obj("type", "integer"),
					"workdays", obj("type", "integer"),
				)),
				UI:        tool.UIForm,
				TimeoutMs: 5_000,
			},
			Handler: dateDiffHandler(o),
		},
		{
			Enabled: true,
			Tool: tool.Tool{
				Name:        "date.add",
				Title:       "Tarihe ekle",
				Description: "Tarihe gün/hafta/ay/yıl ekler veya çıkarır (negatif değer).",
				Tags:        []string{"date"},
				Risk:        tool.RiskLow,
				Permissions: []string{"execute"},
				InputSchema: obj("type", "object",
					"properties", obj(
						"date", str("Tarih (YYYY-MM-DD)"),
						"amount", obj("type", "integer", "description", "Eklenecek miktar (negatif = çıkar)"),
						"unit", str("day | week | month | year"),
						"timezone", str("Saat dilimi; boşsa UTC"),
					),
					"required", []any{"date", "amount", "unit"},
				),
				OutputSchema: obj("type", "object", "properties", obj(
					"date", str("Hesaplanan tarih"),
				)),
				UI:        tool.UIForm,
				TimeoutMs: 5_000,
			},
			Handler: dateAddHandler(o),
		},
		{
			Enabled: true,
			Tool: tool.Tool{
				Name:        "date.workdays",
				Title:       "İş günü hesapla",
				Description: "İki tarih arasındaki iş günü sayısını (hafta sonları hariç) ve resmi tatil listesini döner.",
				Tags:        []string{"date"},
				Risk:        tool.RiskLow,
				Permissions: []string{"execute"},
				InputSchema: obj("type", "object",
					"properties", obj(
						"from", str("Başlangıç tarihi (YYYY-MM-DD)"),
						"to", str("Bitiş tarihi (YYYY-MM-DD)"),
						"holidays", obj("type", "array", "description", "Resmi tatiller (YYYY-MM-DD)", "items", obj("type", "string")),
					),
					"required", []any{"from", "to"},
				),
				OutputSchema: obj("type", "object", "properties", obj(
					"workdays", obj("type", "integer"),
				)),
				UI:        tool.UIMetrics,
				TimeoutMs: 5_000,
			},
			Handler: dateWorkdaysHandler(o),
		},
	}
}

// loadLocation saat dilimini çözer. Geçersiz dilim HATA verir (sessizce UTC'ye
// düşmek yanlış tarih hesabına yol açar).
func loadLocation(tz string) (*time.Location, error) {
	tz = strings.TrimSpace(tz)
	if tz == "" {
		return time.UTC, nil
	}
	loc, err := time.LoadLocation(tz)
	if err != nil {
		return nil, Fail("date", "geçersiz saat dilimi: %q", tz)
	}
	return loc, nil
}

// parseDate YYYY-MM-DD kabul eder (gün ayrımı sabit; saat dilimi uygulanmaz).
func parseDate(t *tool.Tool, s string) (time.Time, error) {
	s = strings.TrimSpace(s)
	d, err := time.ParseInLocation("2006-01-02", s, time.UTC)
	if err != nil {
		return time.Time{}, Fail(t.Name, "tarih YYYY-MM-DD olmalı: %q", s)
	}
	return d, nil
}
func dateDiffHandler(o Options) tool.Handler {
	return func(_ context.Context, t *tool.Tool, in map[string]any) (map[string]any, error) {
		loc, err := loadLocation(strOr(in["timezone"]))
		if err != nil {
			return nil, err
		}
		from, err := parseDate(t, strOr(in["from"]))
		if err != nil {
			return nil, err
		}
		toRaw := strings.TrimSpace(strOr(in["to"]))
		var to time.Time
		if toRaw == "" {
			to = o.clock().In(loc)
			to = time.Date(to.Year(), to.Month(), to.Day(), 0, 0, 0, 0, time.UTC)
		} else {
			if to, err = parseDate(t, toRaw); err != nil {
				return nil, err
			}
		}
		if to.Before(from) {
			from, to = to, from
		}
		days := int(to.Sub(from).Hours() / 24)
		return map[string]any{
			"from":     from.Format("2006-01-02"),
			"to":       to.Format("2006-01-02"),
			"days":     days,
			"weeks":    days / 7,
			"workdays": countWorkdays(from, to, nil),
			"months":   monthsBetween(from, to),
		}, nil
	}
}

func dateAddHandler(_ Options) tool.Handler {
	return func(_ context.Context, t *tool.Tool, in map[string]any) (map[string]any, error) {
		if _, err := loadLocation(strOr(in["timezone"])); err != nil {
			return nil, err
		}
		base, err := parseDate(t, strOr(in["date"]))
		if err != nil {
			return nil, err
		}
		amount, _ := toInt(in["amount"])
		var out time.Time
		switch strings.ToLower(strOr(in["unit"])) {
		case "day", "gun", "gün", "g":
			out = base.AddDate(0, 0, amount)
		case "week", "hafta", "h":
			out = base.AddDate(0, 0, 7*amount)
		case "month", "ay", "a":
			out = addMonths(base, amount)
		case "year", "yil", "yıl", "sene":
			out = addYears(base, amount)
		default:
			return nil, Fail(t.Name, "unit day|week|month|year olmalı")
		}
		return map[string]any{
			"date":    out.Format("2006-01-02"),
			"weekday": turkishWeekday(out.Weekday()),
		}, nil
	}
}

func dateWorkdaysHandler(_ Options) tool.Handler {
	return func(_ context.Context, t *tool.Tool, in map[string]any) (map[string]any, error) {
		from, err := parseDate(t, strOr(in["from"]))
		if err != nil {
			return nil, err
		}
		to, err := parseDate(t, strOr(in["to"]))
		if err != nil {
			return nil, err
		}
		if to.Before(from) {
			from, to = to, from
		}
		var holidaySet map[string]bool
		if raw, ok := in["holidays"].([]any); ok && len(raw) > 0 {
			holidaySet = map[string]bool{}
			for _, h := range raw {
				if s, ok := h.(string); ok {
					holidaySet[strings.TrimSpace(s)] = true
				}
			}
		}
		n := countWorkdays(from, to, holidaySet)
		return map[string]any{
			"workdays": n,
			"caldays":  int(to.Sub(from).Hours()/24) + 1,
			"excluded": holidayList(holidaySet),
			"readable": fmt.Sprintf("%s ile %s arası %d iş günü", from.Format("2006-01-02"), to.Format("2006-01-02"), n),
		}, nil
	}
}

// countWorkdays iki tarih arasındaki iş günlerini sayar (Cumartesi/Pazar hariç).
// Tatil kümesi verilirse o günler de düşülür.
func countWorkdays(from, to time.Time, holidays map[string]bool) int {
	n := 0
	for d := from; !d.After(to); d = d.AddDate(0, 0, 1) {
		if d.Weekday() == time.Saturday || d.Weekday() == time.Sunday {
			continue
		}
		if holidays[d.Format("2006-01-02")] {
			continue
		}
		n++
	}
	return n
}

func holidayList(set map[string]bool) string {
	if len(set) == 0 {
		return ""
	}
	out := make([]string, 0, len(set))
	for d := range set {
		out = append(out, d)
	}
	sort.Strings(out)
	return strings.Join(out, ", ")
}

// monthsBetween, iki tarih arasındaki TAM ay farkını verir (gün kısmı yok sayılır).
func monthsBetween(from, to time.Time) int {
	y := to.Year() - from.Year()
	m := int(to.Month()) - int(from.Month())
	if to.Day() < from.Day() {
		m--
	}
	return y*12 + m
}

// turkishWeekday, haftanın gününü Türkçe adıyla döner.
func turkishWeekday(d time.Weekday) string {
	names := []string{"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"}
	if int(d) < len(names) {
		return names[int(d)]
	}
	return ""
}

func strOr(v any) string {
	s, _ := v.(string)
	return s
}

// addYears, yıl eklerken GÜNÜ korur.
//
// time.AddDate(1,0,0) 29 Şubat'ı 1 Mart'a taşır (Go normalleştirmesi):
// 2024-02-29 + 1 yıl = 2025-03-01. Oysa "bir yıl sonra" 28 Şubat 2025'tir.
// Şubat günü hedef ayda yoksa son geçerli güne kırpılır.
func addYears(d time.Time, n int) time.Time {
	return clampDay(d.Year()+n, int(d.Month()), d.Day())
}

// addMonths, ay eklerken de günü korur (31 Ocak + 1 ay = 28 Şubat).
func addMonths(d time.Time, n int) time.Time {
	total := d.Year()*12 + int(d.Month()) - 1 + n
	return clampDay(total/12, total%12+1, d.Day())
}

// clampDay, verilen yıl/ay/günü kurar; gün o ayda yoksa son güne indirir.
func clampDay(year, month, day int) time.Time {
	last := daysInMonth(year, time.Month(month))
	if day > last {
		day = last
	}
	return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
}

func daysInMonth(year int, m time.Month) int {
	return time.Date(year, m+1, 0, 0, 0, 0, 0, time.UTC).Day()
}
