package api

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"testing"
	"time"

	"fiko-ai/internal/agent"
	"fiko-ai/internal/api/ws"
	"fiko-ai/internal/conversation"
	"fiko-ai/internal/event"
	"fiko-ai/internal/pdks"
	"fiko-ai/internal/prompt"
	"fiko-ai/internal/settings"
	"fiko-ai/internal/tool"
	"fiko-ai/internal/user"
)

// pdks_test.go, PDKS uclarini sinar.
//
// Kapsam: (1) yonetici tam akis (bolum -> personel -> vardiya -> giris/cikis ->
// rapor), (2) UYE devam kaydi TUTAMAZ (guvenlik), (3) durum denetimi cift
// giris 409 doner. (2) bu dosyanin en onemli testidir: devam kaydi yazan bir
// sistemde calisan kendi saatini uydurabiliyorsa veri butunlugu yoktur.

type pdksListBody[T any] struct {
	Items []T `json:"items"`
	Count int `json:"count"`
}

// pdksServer, PDKS BAGLI bir sunucu kurar.
func pdksServer(t *testing.T) *Server {
	t.Helper()
	log := slog.New(slog.NewTextHandler(io.Discard, nil))
	svc := conversation.NewService(conversation.NewMemoryRepo())
	bus := event.NewMemoryBus(nil, 16)
	ag := agent.New(svc, nil, prompt.Default(), bus, log, agent.Config{})
	hub := ws.NewHub(ag, log, ws.Options{PingInterval: time.Hour, PongWait: 2 * time.Hour, WriteWait: time.Second})
	users, err := user.NewService(user.NewMemoryStore(), user.ServiceOptions{BcryptCost: 4})
	if err != nil {
		t.Fatal(err)
	}
	repo := pdks.NewMemoryRepo()
	now := time.Date(2026, 10, 5, 18, 0, 0, 0, time.Local)
	repo.SetNow(func() time.Time { return now })
	pdksSvc := pdks.New(pdks.Options{Repo: repo, Now: func() time.Time { return now }, Logger: log})

	s := New(context.Background(), Config{
		Env: "test", Version: "test",
		UsersEnabled: true, DefaultUserID: "local",
	}, Deps{
		Conversations: svc, Hub: hub, Bus: bus, Users: users,
		Grants:   tool.NewGrantStore(tool.DefaultPolicy()),
		Settings: settings.New(settings.Values{Model: "m"}, log, bus),
		Pdks:     pdksSvc,
	}, log)
	t.Cleanup(func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		_ = s.Shutdown(ctx)
	})
	return s
}

// TestPdksAPI_YoneticiTamAkis uctan uca akisi dener.
func TestPdksAPI_YoneticiTamAkis(t *testing.T) {
	s := pdksServer(t)
	admin := registerUser(t, s, "patron")

	// 1) Bolum olustur.
	resp, raw := do(t, s, http.MethodPost, "/api/pdks/departments",
		map[string]any{"name": "Muhasebe"}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST /pdks/departments: %d %s", resp.StatusCode, raw)
	}
	dep := decode[pdks.Department](t, raw)
	if dep.ID == "" || dep.Name != "Muhasebe" {
		t.Fatalf("bolum bozuk: %+v", dep)
	}

	// 2) Personel olustur.
	resp, raw = do(t, s, http.MethodPost, "/api/pdks/employees",
		map[string]any{"name": "Ali Veli", "departmentId": dep.ID}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST /pdks/employees: %d %s", resp.StatusCode, raw)
	}
	emp := decode[pdks.Employee](t, raw)
	if emp.DepartmentID != dep.ID {
		t.Fatalf("personel bolumu bagli degil: %+v", emp)
	}

	// 3) Vardiya olustur (gec kalma hesabini etkiler).
	resp, raw = do(t, s, http.MethodPost, "/api/pdks/shifts",
		map[string]any{"name": "Normal", "start": "09:00", "end": "18:00", "breakMin": 60},
		bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST /pdks/shifts: %d %s", resp.StatusCode, raw)
	}

	// 4) Giriş / çıkış kaydı.
	at := func(h int) string {
		return fmt.Sprintf("2026-10-05T%02d:00:00", h)
	}
	resp, raw = do(t, s, http.MethodPost, "/api/pdks/attendance",
		map[string]any{"empId": emp.ID, "type": "in", "at": at(9)}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST attendance (in): %d %s", resp.StatusCode, raw)
	}
	resp, raw = do(t, s, http.MethodPost, "/api/pdks/attendance",
		map[string]any{"empId": emp.ID, "type": "out", "at": at(18)}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST attendance (out): %d %s", resp.StatusCode, raw)
	}

	// 5) Gunluk rapor: 09-18 = 540 dk, present.
	resp, raw = do(t, s, http.MethodGet,
		"/api/pdks/reports/daily?day=2026-10-05", nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("GET reports/daily: %d %s", resp.StatusCode, raw)
	}
	daily := decode[pdksListBody[pdks.Daily]](t, raw)
	if daily.Count != 1 {
		t.Fatalf("gunluk rapor: %d satir beklenir, %d", 1, daily.Count)
	}
	got := daily.Items[0].Day
	if got.WorkMin != 540 {
		t.Errorf("WorkMin = %d, beklenen 540", got.WorkMin)
	}
	if got.Status != pdks.StatusPresent {
		t.Errorf("Status = %q, beklenen present", got.Status)
	}

	// 6) Aralik raporu.
	resp, raw = do(t, s, http.MethodGet,
		"/api/pdks/reports/range?from=2026-10-05&to=2026-10-07", nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("GET reports/range: %d %s", resp.StatusCode, raw)
	}
	rep := decode[pdks.Report](t, raw)
	if rep.Days != 3 || rep.WorkMin != 540 {
		t.Errorf("rapor: days=%d workMin=%d, beklenen 3/540", rep.Days, rep.WorkMin)
	}

	// 7) Gun detayi (olaylarla birlikte) — duzeltme ekrani bunu kullanir.
	resp, raw = do(t, s, http.MethodGet,
		"/api/pdks/reports/day?empId="+emp.ID+"&day=2026-10-05", nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("GET reports/day: %d %s", resp.StatusCode, raw)
	}
}

// TestPdksAPI_UyeDevamKaydiTutamaz GUVENLIK: calisan kendi saatini uyduramaz.
// TestPdksAPI_UyeDevamKaydiTutamaz GUVENLIK: calisan kendi saatini uyduramaz.
func TestPdksAPI_UyeDevamKaydiTutamaz(t *testing.T) {
	s := pdksServer(t)
	admin := registerUser(t, s, "patron")
	registerUser(t, s, "yardimci")
	uye := registerUser(t, s, "personel")

	// Yonetici bir personel kaydi olustursun.
	resp, raw := do(t, s, http.MethodPost, "/api/pdks/employees",
		map[string]any{"name": "Ali"}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST employees: %d %s", resp.StatusCode, raw)
	}
	emp := decode[pdks.Employee](t, raw)

	body := map[string]any{"empId": emp.ID, "type": "in"}

	// UYE yazamaz: adminOnly 403 veya 404 doner (yokluk sizdirmamak icin).
	resp, raw = do(t, s, http.MethodPost, "/api/pdks/attendance", body, bearer(uye.Token))
	if resp.StatusCode != http.StatusForbidden && resp.StatusCode != http.StatusNotFound {
		t.Fatalf("GUVENLIK IHLALI: uye devam kaydi yazdi (HTTP %d): %s", resp.StatusCode, raw)
	}

	// Dogrula: yonetici gorunumunde hic kayit YOK (uye yazamadmis olmali).
	resp, raw = do(t, s, http.MethodGet, "/api/pdks/attendance?empId="+emp.ID, nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("GET attendance: %d %s", resp.StatusCode, raw)
	}
	if decode[pdksListBody[pdks.Attendance]](t, raw).Count != 0 {
		t.Fatal("uye devam kaydi yazmis olmali")
	}
}

// TestPdksAPI_CiftGirisEngellenir durum denetimi: ayni gun iki giris 409 doner.
//
// Neden onemli? Model iki kez arac cagirirsa ikinci kayit YAZILMAZ; hata
// modele doner ve tekrar denemek zorunda kalir. Sessizce yazilirsa gunluk
// sure yanlis hesaplanir ve hata kullaniciya kadar hic ulasmaz.
func TestPdksAPI_CiftGirisEngellenir(t *testing.T) {
	s := pdksServer(t)
	admin := registerUser(t, s, "patron")
	resp, raw := do(t, s, http.MethodPost, "/api/pdks/employees",
		map[string]any{"name": "Ali"}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST employees: %d %s", resp.StatusCode, raw)
	}
	emp := decode[pdks.Employee](t, raw)

	punch := func(ty string) int {
		r, _ := do(t, s, http.MethodPost, "/api/pdks/attendance",
			map[string]any{"empId": emp.ID, "type": ty}, bearer(admin.Token))
		return r.StatusCode
	}

	if c := punch("in"); c != http.StatusCreated {
		t.Fatalf("ilk giris: %d", c)
	}
	if c := punch("in"); c != http.StatusConflict {
		t.Fatalf("cift giris 409 beklenir, %d", c)
	}

	// Ciktiktan sonra yeniden giris yapilabilir (durum yeniden acilir).
	if c := punch("out"); c != http.StatusCreated {
		t.Fatalf("cikis: %d", c)
	}
	if c := punch("in"); c != http.StatusCreated {
		t.Fatalf("tekrar giris: %d", c)
	}
}

// TestPdksAPI_IzinGunuLeaveSayilir onayli izin gunu raporda "leave" olur.
func TestPdksAPI_IzinGunuLeaveSayilir(t *testing.T) {
	s := pdksServer(t)
	admin := registerUser(t, s, "patron")
	resp, raw := do(t, s, http.MethodPost, "/api/pdks/employees",
		map[string]any{"name": "Ali"}, bearer(admin.Token))
	emp := decode[pdks.Employee](t, raw)

	resp, raw = do(t, s, http.MethodPost, "/api/pdks/leaves",
		map[string]any{"empId": emp.ID, "kind": "annual",
			"from": "2026-10-05", "to": "2026-10-07",
			"approved": true}, bearer(admin.Token))
	if resp.StatusCode != http.StatusCreated {
		t.Fatalf("POST leaves: %d %s", resp.StatusCode, raw)
	}

	day := "/api/pdks/reports/day?empId=" + emp.ID + "&day=2026-10-05"
	resp, raw = do(t, s, http.MethodGet, day, nil, bearer(admin.Token))
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("GET reports/day: %d %s", resp.StatusCode, raw)
	}
}
