package claim

import (
	"context"
	"fmt"
	"os"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"fiko-ai/internal/llm"
	"fiko-ai/internal/llm/ollama"
)

// countingLLM gerçek sağlayıcıyı sarar ve çıkarım/karar çağrılarını sayar.
type countingLLM struct {
	llm.Provider
	calls atomic.Int64
}

func (c *countingLLM) Generate(ctx context.Context, r llm.Request) (llm.Result, error) {
	c.calls.Add(1)
	return c.Provider.Generate(ctx, r)
}

func (c *countingLLM) GenerateStream(ctx context.Context, r llm.Request, d llm.DeltaFunc) (llm.Result, error) {
	c.calls.Add(1)
	return c.Provider.GenerateStream(ctx, r, d)
}

func e2eEnv(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}

// TestE2E_GercekOllama_140Firma gerçek Ollama (çıkarım + embedding) ile Service.Write'ı uçtan uca dener.
// Yavaş ve model kalitesine bağlıdır; yalnızca CLAIM_E2E_OLLAMA=1 iken çalışır.
//
//	CLAIM_E2E_OLLAMA=1 CLAIM_E2E_MODEL=gemma4:latest CLAIM_E2E_EMBED=qwen3-embedding go test -run E2E -v -timeout 30m ./internal/claim
func TestE2E_GercekOllama_140Firma(t *testing.T) {
	if os.Getenv("CLAIM_E2E_OLLAMA") != "1" {
		t.Skip("CLAIM_E2E_OLLAMA=1 değil; gerçek Ollama denemesi atlandı")
	}
	model := e2eEnv("CLAIM_E2E_MODEL", "gemma4:latest")
	embed := e2eEnv("CLAIM_E2E_EMBED", "qwen3-embedding")
	base := e2eEnv("OLLAMA_BASE_URL", "http://localhost:11434")

	prov := &countingLLM{Provider: ollama.New(base, model, 10*time.Minute).WithEmbedModel(embed)}
	embedder, ok := prov.Provider.(llm.Embedder)
	if !ok {
		t.Fatal("ollama istemcisi llm.Embedder değil")
	}
	repo, audit := NewMemoryRepo(), NewMemoryAuditRepo()
	svc := NewService(repo, audit, prov, embedder, Config{Model: model, EmbedModel: embed}, nil)
	defer svc.Close()
	actor := Actor{ID: "usr_fikri", Name: "Fikri", Role: RolePatron}

	msg, names := e2e140Message()

	ctx, cancel := context.WithTimeout(context.Background(), 25*time.Minute)
	defer cancel()

	t0 := time.Now()
	res, err := svc.Write(ctx, actor, msg)
	if err != nil {
		t.Fatalf("Write: %v", err)
	}
	n, _ := repo.Count(ctx, Filter{})
	t.Logf("1. yapıştırma: %s | süre=%s | LLM çağrısı=%d | depoda=%d | parça=%d/%d başarısız | tekrar=%d | benzer=%d | eksik=%d",
		res.Summary(), time.Since(t0).Round(time.Second), prov.calls.Load(), n, res.FailedChunks, res.Chunks,
		res.Duplicates, len(res.Similar), res.Incomplete)

	if res.FailedChunks > 0 {
		t.Errorf("%d parça işlenemedi", res.FailedChunks)
	}
	if n < 133 { // %95 tolerans: küçük modelin 1-2 satırı atlaması
		t.Errorf("140 firmadan yalnızca %d kayıt yazıldı", n)
	}
	missing := 0
	all, _ := repo.List(ctx, Filter{}, 1000, 0)
	have := strings.ToLower(joinTexts(all))
	for _, nm := range names {
		if !strings.Contains(have, strings.ToLower(fmt.Sprintf("(%s)", nm[strings.LastIndex(nm, "(")+1:len(nm)-1]))) {
			missing++
		}
	}
	t.Logf("kayıtlarda numarası bulunamayan firma sayısı: %d", missing)

	// Aynı metin yeniden yapıştırılınca yeni kayıt ve (neredeyse) karar çağrısı olmamalı.
	before := prov.calls.Load()
	t1 := time.Now()
	res2, err := svc.Write(ctx, actor, msg)
	if err != nil {
		t.Fatalf("2. Write: %v", err)
	}
	n2, _ := repo.Count(ctx, Filter{})
	t.Logf("2. yapıştırma: %s | süre=%s | ek LLM çağrısı=%d | depoda=%d", res2.Summary(), time.Since(t1).Round(time.Second), prov.calls.Load()-before, n2)
	if n2 > n+5 {
		t.Errorf("aynı metin tekrar yapıştırılınca kayıt sayısı %d → %d (çoğaldı)", n, n2)
	}

	// Çelişki: aynı firma için iki farklı telefon.
	if _, err := svc.Write(ctx, actor, "Akın Metal Sanayi A.Ş. (001) firmasının telefonu artık 0216 555 00 11."); err != nil {
		t.Fatalf("güncelleme yazması: %v", err)
	}
	n3, _ := repo.Count(ctx, Filter{})
	t.Logf("güncelleme sonrası depoda=%d (önce %d)", n3, n2)
}

func joinTexts(cs []Claim) string {
	var b strings.Builder
	for _, c := range cs {
		b.WriteString(c.Text)
		b.WriteByte('\n')
	}
	return b.String()
}

// e2e140Message 140 benzersiz firmalık dökümü ve firma adlarını üretir.
func e2e140Message() (string, []string) {
	// 140 firma: her satırda benzersiz ad.
	sektor := []string{"Metal", "Plastik", "Kalıp", "Otomasyon", "Lojistik", "Makine", "Elektrik", "Boya", "Ambalaj", "Hidrolik"}
	ek := []string{"Sanayi", "Ticaret", "Mühendislik", "Üretim", "Teknik", "Savunma", "Endüstri"}
	var b strings.Builder
	b.WriteString("Müşteri firmalarımızın listesi:\n")
	names := make([]string, 0, 140)
	for i := 0; i < 140; i++ {
		n := fmt.Sprintf("%s %s %s A.Ş.", []string{"Akın", "Barış", "Çelik", "Demir", "Efe", "Gür", "Han", "Işık", "Kaya", "Mert", "Özkan", "Polat", "Sezer", "Tunç"}[i%14], sektor[(i/14)%10], ek[(i/3)%7])
		n = fmt.Sprintf("%s (%03d)", n, i+1) // adı benzersiz kıl
		names = append(names, n)
		fmt.Fprintf(&b, "%d. %s - müşteri, tel: 0212 %03d %02d %02d\n", i+1, n, 100+i, i%90, (i*7)%90)
	}
	msg := b.String()
	return msg, names
}
