package agent

import (
	"context"
	"errors"
	"testing"
	"time"

	"fiko-ai/internal/llm"
)

type fakeClaimWriter struct {
	got     chan ClaimRequest
	started chan struct{}
	block   bool // true: ctx iptal olana kadar bekler (kapanış testi)
}

func newFakeClaimWriter() *fakeClaimWriter {
	return &fakeClaimWriter{got: make(chan ClaimRequest, 4), started: make(chan struct{}, 4)}
}

func (f *fakeClaimWriter) Capture(ctx context.Context, req ClaimRequest) {
	f.started <- struct{}{}
	if f.block {
		<-ctx.Done()
		return
	}
	f.got <- req
}

// Tamamlanan turdan sonra yakalayıcı kullanıcının (kırpılmış) mesajıyla çağrılır; asistan cevabı karışmaz.
func TestClaims_CapturedAfterCompleteTurn(t *testing.T) {
	f := &fakeLLM{stream: memOKStream("Asistan cevabı: not aldım.")}
	a, _, conv := setup(t, f, Config{})
	w := newFakeClaimWriter()
	a.SetClaims(w)

	runText(a, conv, "  ACME müşterimiz, telefonu 0212.  ")

	select {
	case req := <-w.got:
		if req.Text != "ACME müşterimiz, telefonu 0212." {
			t.Fatalf("metin kullanıcının kırpılmış mesajı olmalı, got=%q", req.Text)
		}
		if req.UserID != "u" || req.ConversationID != conv {
			t.Fatalf("kimlikler yanlış: %+v", req)
		}
	case <-time.After(3 * time.Second):
		t.Fatal("tamamlanan turdan sonra claim yakalanmadı")
	}
}

// Yazıcı bağlı değilse (varsayılan) hiçbir şey olmaz ve tur normal biter.
func TestClaims_NilWriterIsNoop(t *testing.T) {
	f := &fakeLLM{stream: memOKStream("tamam")}
	a, _, conv := setup(t, f, Config{})
	runText(a, conv, "ACME müşterimiz.")
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	if err := a.Close(ctx); err != nil {
		t.Fatalf("Close: %v", err)
	}
}

// Üretim hatası olan turdan claim yakalanmaz.
func TestClaims_NotCapturedWhenGenerationFails(t *testing.T) {
	f := &fakeLLM{stream: func(context.Context, llm.Request, llm.DeltaFunc) (llm.Result, error) {
		return llm.Result{}, errors.New("model çöktü")
	}}
	a, _, conv := setup(t, f, Config{})
	w := newFakeClaimWriter()
	a.SetClaims(w)

	runText(a, conv, "ACME müşterimiz.")

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	if err := a.Close(ctx); err != nil {
		t.Fatalf("Close: %v", err)
	}
	if n := len(w.started); n != 0 {
		t.Fatalf("hatalı turdan claim yakalanmamalı, çağrı=%d", n)
	}
}

// Close, süren yakalamayı iptal eder ve bekler; takılmaz.
func TestClaims_CloseCancelsAndWaits(t *testing.T) {
	f := &fakeLLM{stream: memOKStream("tamam")}
	a, _, conv := setup(t, f, Config{})
	w := newFakeClaimWriter()
	w.block = true
	a.SetClaims(w)

	runText(a, conv, "ACME müşterimiz.")
	select {
	case <-w.started:
	case <-time.After(3 * time.Second):
		t.Fatal("yakalama başlamadı")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	done := make(chan error, 1)
	go func() { done <- a.Close(ctx) }()
	select {
	case err := <-done:
		if err != nil {
			t.Fatalf("Close hata: %v", err)
		}
	case <-time.After(3 * time.Second):
		t.Fatal("Close claim yakalama yüzünden takıldı")
	}
}

// Yakalayıcı çökerse tur ve süreç etkilenmez.
type panicClaimWriter struct{}

func (panicClaimWriter) Capture(context.Context, ClaimRequest) { panic("yazıcı çöktü") }

func TestClaims_PanicDoesNotBreakTurn(t *testing.T) {
	f := &fakeLLM{stream: memOKStream("tamam")}
	a, _, conv := setup(t, f, Config{})
	a.SetClaims(panicClaimWriter{})
	runText(a, conv, "ACME müşterimiz.")
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	if err := a.Close(ctx); err != nil {
		t.Fatalf("Close: %v", err)
	}
}
