package builtin

import (
	"context"
	"strings"
	"testing"
	"time"
)

func testOptions(t *testing.T) Options {
	t.Helper()
	day, err := time.Parse("2006-01-02", "2026-03-10")
	if err != nil {
		t.Fatal(err)
	}
	return Options{BizEnabled: true, Clock: func() time.Time { return day }}
}

// runDef verilen tool tanımını uygulama üzerinde çalıştırır.
func runDef(t *testing.T, defs []Def, name string, in map[string]any) (map[string]any, error) {
	t.Helper()
	for _, d := range defs {
		if d.Tool.Name != name {
			continue
		}
		tl := d.Tool.Clone()
		return d.Handler(context.Background(), &tl, in)
	}
	t.Fatalf("tool bulunamadı: %s", name)
	return nil, nil
}

func mustRun(t *testing.T, defs []Def, name string, in map[string]any) map[string]any {
	t.Helper()
	out, err := runDef(t, defs, name, in)
	if err != nil {
		t.Fatalf("%s: %v", name, err)
	}
	return out
}

// mustRunConfirmed, onay gerektiren bir aracı "kullanıcı Onayla'ya bastı" diye çalıştırır.
//
// Neden? Yazma/silme tool'ları onay alınmadan çalışmaz (bkz. confirm.go) ve kullanıcı
// onayı OLAY KATMANINDAN (agent) eklenir. Bizim katmanımızda bunu `confirmed:true`
// ile taklit ediyoruz: testler asıl iş mantığını (kayıt yazma, doğrulama) sınar,
// onay akışının kendisi ayrı testlerle (confirm_test.go) kapsanır.
func mustRunConfirmed(t *testing.T, defs []Def, name string, in map[string]any) map[string]any {
	t.Helper()
	if in == nil {
		in = map[string]any{}
	}
	in["confirmed"] = true
	return mustRun(t, defs, name, in)
}

// Proje eklenir, türetilen planGün hesaplanır ve görev projeye bağlanır.
func TestProjectLifecycle(t *testing.T) {
	defs, err := bizDefinitions(testOptions(t))
	if err != nil {
		t.Fatal(err)
	}
	// Yeni koleksiyonlar tanınmalı (LLM adı farklı yazabilir: specFor duyarsızdır).
	for _, name := range []string{"projects", "projectTasks", "risks", "milestones", "sprints", "timeEntries", "meetings"} {
		if _, ok := specFor(name); !ok {
			t.Fatalf("%q şeması tanınmadı", name)
		}
		if _, ok := specFor(strings.ToLower(name)); !ok {
			t.Fatalf("%q küçük harfle tanınmadı", name)
		}
	}

	prj := mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projects",
		"data": map[string]any{
			"name": "Yeni Ürün", "code": "PRJ-1", "status": "active",
			"startDate": "2026-03-01", "plannedEnd": "2026-03-31", "budget": 100_000,
		},
	})
	rec := prj["record"].(map[string]any)
	id, _ := rec["id"].(string)
	if id == "" {
		t.Fatalf("proje kimliği yok: %+v", rec)
	}
	// planGün türetilmiş alan: 1→31 Mart = 30 gün.
	if g, _ := rec["planGün"].(float64); g != 30 {
		t.Errorf("planGün = %v, 30 bekleniyordu", g)
	}
	if rec["status"] != "active" || rec["currency"] != "TRY" {
		t.Errorf("varsayılanlar uygulanmadı: %+v", rec)
	}

	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projectTasks",
		"data":       map[string]any{"title": "Tasarım", "project": id, "estimate": 3, "status": "doing"},
	})
	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projectTasks",
		"data":       map[string]any{"title": "Test", "project": id, "estimate": 2, "status": "done"},
	})
	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "risks",
		"data":       map[string]any{"title": "Gecikme riski", "project": id, "impact": "high", "likelihood": "medium"},
	})

	// Pano: ilerleme tahmin ağırlıklı (2/(3+2) = %40).
	board := mustRun(t, defs, "biz.project", map[string]any{"ref": "PRJ-1"})
	prog := board["progress"].(map[string]any)
	if p, _ := prog["percent"].(float64); p != 40 {
		t.Errorf("ilerleme = %v%%, 40 bekleniyordu", p)
	}
	if prog["tasksTotal"].(int) != 2 {
		t.Errorf("görev sayısı: %v", prog["tasksTotal"])
	}
	if st, _ := prog["byStatus"].(map[string]int); st["done"] != 1 || st["doing"] != 1 {
		t.Errorf("faz dağılımı: %v", st)
	}
	// Risk skoru: high(3) × medium(2) = 6.
	risks := board["risks"].([]any)
	if len(risks) != 1 || risks[0].(map[string]any)["score"].(float64) != 6 {
		t.Errorf("risk skoru: %v", risks)
	}

	// Proje adıyla da bulunabilmeli (kimlik bilmek zorunlu değil).
	if mustRun(t, defs, "biz.project", map[string]any{"ref": "Yeni Ürün"})["project"] == nil {
		t.Error("proje adıyla çözümlenemedi")
	}
	// Bilinmeyen proje anlamlı hata verir (çökme değil).
	if _, err := runDef(t, defs, "biz.project", map[string]any{"ref": "yok"}); err == nil {
		t.Error("bilinmeyen proje hata vermeliydi")
	}
}

// Geciken görev "overdue", bu hafta biten "dueThisWeek" listesine girer.
func TestProjectOverdueDetection(t *testing.T) {
	defs, err := bizDefinitions(testOptions(t))
	if err != nil {
		t.Fatal(err)
	}
	prj := mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projects", "data": map[string]any{"name": "P", "startDate": "2026-03-01", "plannedEnd": "2026-06-01"},
	})
	id, _ := prj["record"].(map[string]any)["id"].(string)

	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projectTasks",
		"data":       map[string]any{"title": "Gecikmiş iş", "project": id, "due": "2026-03-05"},
	})
	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projectTasks",
		"data":       map[string]any{"title": "Yaklaşan iş", "project": id, "due": "2026-03-13"},
	})

	board := mustRun(t, defs, "biz.project", map[string]any{"ref": id})
	if got := len(board["overdue"].([]any)); got != 1 {
		t.Errorf("geciken görev sayısı: %d (1 bekleniyordu)", got)
	}
	if got := len(board["dueThisWeek"].([]any)); got != 1 {
		t.Errorf("bu hafta bitecek görev: %d (1 bekleniyordu)", got)
	}
}

// Planlama: kapasiteye göre gün hesabı ve hedef tarih yeterlilik analizi.
func TestPlanCalculatesSchedule(t *testing.T) {
	defs, err := bizDefinitions(testOptions(t))
	if err != nil {
		t.Fatal(err)
	}

	// 10 günlük iş, günde 1 kişi-gün → 10 gün sürer.
	plan := mustRun(t, defs, "biz.plan", map[string]any{
		"startDate": "2026-03-10", "capacity": 1,
		"tasks": []any{
			map[string]any{"title": "A", "estimate": 4},
			map[string]any{"title": "B", "estimate": 6},
		},
	})
	if plan["neededDays"].(int) != 10 {
		t.Errorf("gerekli gün: %v (10 bekleniyordu)", plan["neededDays"])
	}
	if plan["finishDate"] != "2026-03-20" {
		t.Errorf("bitiş: %v (2026-03-20 bekleniyordu)", plan["finishDate"])
	}
	if len(plan["schedule"].([]any)) != 2 {
		t.Errorf("görev bazlı takvim eksik: %v", plan["schedule"])
	}

	// Hedef 5 gün sonra → yetişmez, açık öneri verilir.
	tight := mustRun(t, defs, "biz.plan", map[string]any{
		"startDate": "2026-03-10", "capacity": 1, "deadline": "2026-03-15",
		"tasks": []any{map[string]any{"title": "A", "estimate": 10}},
	})
	if tight["feasible"] != false {
		t.Error("5 günde 10 günlük iş yapılamaz, feasible=false olmalı")
	}
	if tight["shortfallDays"].(int) != 5 {
		t.Errorf("açık: %v gün (5 bekleniyordu)", tight["shortfallDays"])
	}
	if s, _ := tight["advice"].(string); !strings.Contains(s, "gecikme") {
		t.Errorf("öneri gecikme uyarısı içermeli: %q", s)
	}

	// Hedef yeterliyse feasible=true.
	ok := mustRun(t, defs, "biz.plan", map[string]any{
		"startDate": "2026-03-10", "capacity": 2, "deadline": "2026-03-20",
		"tasks": []any{map[string]any{"title": "A", "estimate": 10}},
	})
	if ok["feasible"] != true {
		t.Errorf("günde 2 kapasiteyle 10 günlük iş 5 günde biter: %v", ok)
	}
}

// Alt kayıtlar yalnızca kendi projesine aittir (projeler birbirine karışmaz).
func TestProjectIsolation(t *testing.T) {
	defs, err := bizDefinitions(testOptions(t))
	if err != nil {
		t.Fatal(err)
	}
	a := mustRunConfirmed(t, defs, "biz.add", map[string]any{"collection": "projects", "data": map[string]any{"name": "A"}})
	b := mustRunConfirmed(t, defs, "biz.add", map[string]any{"collection": "projects", "data": map[string]any{"name": "B"}})
	idA := a["record"].(map[string]any)["id"].(string)
	idB := b["record"].(map[string]any)["id"].(string)

	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projectTasks", "data": map[string]any{"title": "A işi", "project": idA, "estimate": 2},
	})
	mustRunConfirmed(t, defs, "biz.add", map[string]any{
		"collection": "projectTasks", "data": map[string]any{"title": "B işi", "project": idB, "estimate": 8},
	})

	board := mustRun(t, defs, "biz.project", map[string]any{"ref": idA})
	if n := board["progress"].(map[string]any)["tasksTotal"].(int); n != 1 {
		t.Errorf("A projesinde %d görev görünüyor (1 bekleniyordu)", n)
	}
}
