package main

import (
	"context"
	"io"
	"log/slog"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"fiko-ai/internal/agent"
	"fiko-ai/internal/claim"
	"fiko-ai/internal/llm"
	"fiko-ai/internal/user"
)

// readCountingLLM ayırıcı çağrılarını sayar ve sabit bir yanıt döndürür.
type readCountingLLM struct {
	claimFakeLLM
	reply string
	calls atomic.Int32
}

func (f *readCountingLLM) Generate(context.Context, llm.Request) (llm.Result, error) {
	f.calls.Add(1)
	return llm.Result{Content: f.reply}, nil
}

func readTestRepo(t *testing.T) *claim.MemoryRepo {
	t.Helper()
	repo := claim.NewMemoryRepo()
	now := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
	for _, c := range []claim.Claim{
		{ID: "c1", Kind: claim.KindFirma, Text: "ACME Kalıp müşterimizdir", Key: claim.NormalizeKey("ACME Kalıp"), AuthorID: "usr_fikri", AuthorName: "Fikri", CreatedAt: now, UpdatedAt: now, Status: claim.StatusActive},
		{ID: "c2", Kind: claim.KindFirma, Text: "Barış Metal tedarikçimizdir", Key: claim.NormalizeKey("Barış Metal"), AuthorID: "usr_fikri", AuthorName: "Fikri", CreatedAt: now, UpdatedAt: now, Status: claim.StatusActive},
	} {
		c := c
		if err := repo.Create(context.Background(), &c); err != nil {
			t.Fatal(err)
		}
	}
	return repo
}

func readTestLog() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) }

// Bayrak kapalıyken (varsayılan) okuyucu kurulmaz.
func TestSetupClaimReader_BayrakKapaliNil(t *testing.T) {
	cfg := claimCfg(true)
	if got := setupClaimReader(readTestRepo(t), claim.NewMemoryAuditRepo(), &readCountingLLM{}, nil, nil, cfg, readTestLog()); got != nil {
		t.Fatal("FIKO_CLAIMS_READ=false iken okuyucu nil olmalı")
	}
	cfg.Claims.Read = true
	if got := setupClaimReader(readTestRepo(t), claim.NewMemoryAuditRepo(), &readCountingLLM{}, nil, nil, cfg, readTestLog()); got == nil {
		t.Fatal("bayrak açıkken okuyucu kurulmalı")
	}
	if got := setupClaimReader(nil, claim.NewMemoryAuditRepo(), &readCountingLLM{}, nil, nil, cfg, readTestLog()); got != nil {
		t.Fatal("depo yoksa kapalı kalmalı")
	}
}

// Gerçek oturum kimliği yoksa ekip verisi okunmaz ve ayırıcı LLM'e HİÇ gidilmez.
func TestClaimReader_KimliksizOkumaz(t *testing.T) {
	p := &readCountingLLM{reply: `{"type":"liste","kind":"firma"}`}
	cfg := claimCfg(false) // üyelik kapalı
	cfg.Claims.Read = true
	rd := setupClaimReader(readTestRepo(t), claim.NewMemoryAuditRepo(), p, nil, nil, cfg, readTestLog())

	got := rd.Read(context.Background(), agent.ClaimReadRequest{UserID: "usr_fikri", ConversationID: "cv1", Text: "firmaların listesini ver"})
	if got != (agent.ClaimContext{}) {
		t.Errorf("kimliksiz okuma boş olmalıydı: %+v", got)
	}
	if p.calls.Load() != 0 {
		t.Errorf("kimliksiz okumada LLM çağrıldı (%d)", p.calls.Load())
	}
	// Paylaşılan anahtar kimliği ("local") ve olmayan hesap da okuyamaz.
	svc := newClaimTestService(t, user.User{ID: "usr_fikri", Username: "fikri", Role: user.RoleAdmin})
	cfg2 := claimCfg(true)
	cfg2.Claims.Read = true
	rd2 := setupClaimReader(readTestRepo(t), claim.NewMemoryAuditRepo(), p, nil, svc, cfg2, readTestLog())
	for _, uid := range []string{"local", "hacker", ""} {
		if got := rd2.Read(context.Background(), agent.ClaimReadRequest{UserID: uid, Text: "firmaların listesini ver"}); got != (agent.ClaimContext{}) {
			t.Errorf("%q okumamalıydı: %+v", uid, got)
		}
	}
	if p.calls.Load() != 0 {
		t.Errorf("doğrulanamayan kimliklerde LLM çağrıldı (%d)", p.calls.Load())
	}
}

// Doğrulanmış üye için liste sorusu: sunucu listesi Append'e girer, yazma yakalaması atlanır (List).
func TestClaimReader_ListeSorusu(t *testing.T) {
	svc := newClaimTestService(t, user.User{ID: "usr_ali", Username: "ali", Role: user.RoleUser})
	cfg := claimCfg(true)
	cfg.Claims.Read = true
	p := &readCountingLLM{reply: `{"type":"liste","kind":"firma"}`}
	rd := setupClaimReader(readTestRepo(t), claim.NewMemoryAuditRepo(), p, nil, svc, cfg, readTestLog())

	got := rd.Read(context.Background(), agent.ClaimReadRequest{UserID: "usr_ali", ConversationID: "cv1", Text: "firmaların listesini ver"})
	if !got.List || !strings.Contains(got.Append, "1. ACME Kalıp müşterimizdir (yazan: Fikri, 2026-10-05)") ||
		!strings.Contains(got.Append, "Toplam: 2 firma") || got.Prompt == "" {
		t.Fatalf("liste hazırlığı yanlış: %+v", got)
	}
}

// Doğrulanmış üye için bilgi sorusu: kayıtlar isteme girer, Append boştur.
func TestClaimReader_BilgiSorusu(t *testing.T) {
	svc := newClaimTestService(t, user.User{ID: "usr_ali", Username: "ali", Role: user.RoleUser})
	cfg := claimCfg(true)
	cfg.Claims.Read = true
	cfg.Ollama.EmbedModel = "e"
	repo := readTestRepo(t)
	c, _ := repo.Get(context.Background(), "c1")
	c.Embedding, c.EmbedModel = []float32{1, 0}, "e"
	if err := repo.Update(context.Background(), c); err != nil {
		t.Fatal(err)
	}
	p := &readCountingLLM{reply: `{"type":"soru"}`}
	rd := setupClaimReader(repo, claim.NewMemoryAuditRepo(), p, constEmbedder{v: []float32{1, 0}}, svc, cfg, readTestLog())

	got := rd.Read(context.Background(), agent.ClaimReadRequest{UserID: "usr_ali", Text: "ACME kim?"})
	if got.List || got.Append != "" || !strings.Contains(got.Prompt, "ACME Kalıp müşterimizdir") {
		t.Fatalf("bilgi sorusu hazırlığı yanlış: %+v", got)
	}
}

type constEmbedder struct{ v []float32 }

func (e constEmbedder) Embed(context.Context, string) ([]float32, error) { return e.v, nil }

// Rapor sorusu (Faz 9): rapor sunucuda hesaplanıp Append'e girer, yazma yakalaması atlanır (List=true).
func TestClaimReader_RaporSorusu(t *testing.T) {
	svc := newClaimTestService(t, user.User{ID: "usr_ali", Username: "ali", Role: user.RoleUser})
	cfg := claimCfg(true)
	cfg.Claims.Read = true
	repo := readTestRepo(t)
	now := time.Date(2026, 10, 5, 9, 0, 0, 0, time.UTC)
	task := claim.Claim{ID: "g1", Kind: claim.KindGorev, Text: "İşleme programı hazırlanacak", AuthorID: "usr_fikri", AuthorName: "Fikri",
		CreatedAt: now, UpdatedAt: now, Status: claim.StatusActive,
		Fields: map[string]string{"atanan": "Ahmet", "son_tarih": "2000-01-01", "durum": "acik"}} // her zaman gecikmiş
	if err := repo.Create(context.Background(), &task); err != nil {
		t.Fatal(err)
	}
	p := &readCountingLLM{reply: `{"type":"rapor","report":"gecikmis_gorev"}`}
	rd := setupClaimReader(repo, claim.NewMemoryAuditRepo(), p, nil, svc, cfg, readTestLog())

	got := rd.Read(context.Background(), agent.ClaimReadRequest{UserID: "usr_ali", ConversationID: "cv1", Text: "gecikmiş görevler hangileri?"})
	if !got.List {
		t.Error("rapor sorusu yeni bilgi taşımaz: yazma yakalaması atlanmalı (List=true)")
	}
	for _, want := range []string{"Gecikmiş görevler", "İşleme programı hazırlanacak", "atanan: Ahmet", "Toplam: 1 gecikmiş görev"} {
		if !strings.Contains(got.Append, want) {
			t.Errorf("Append'te %q yok:\n%s", want, got.Append)
		}
	}
	if got.Prompt == "" {
		t.Error("model için rapor yönergesi (Prompt) boş")
	}
	if p.calls.Load() != 1 {
		t.Errorf("LLM çağrısı = %d, beklenen 1 (yalnızca ayırıcı)", p.calls.Load())
	}
}
