package agent

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

	"fiko-ai/internal/conversation"
	"fiko-ai/internal/ctxmgr"
	"fiko-ai/internal/llm"
	"fiko-ai/internal/protocol"
)

// ---- test yardımcıları ----

type streamFn func(ctx context.Context, req llm.Request, on llm.DeltaFunc) (llm.Result, error)

type fakeLLM struct {
	stream streamFn
	mu     sync.Mutex
	reqs   []llm.Request
}

func (f *fakeLLM) Name() string { return "fake" }
func (f *fakeLLM) Generate(context.Context, llm.Request) (llm.Result, error) {
	return llm.Result{}, errors.New("kullanılmıyor")
}
func (f *fakeLLM) ListModels(context.Context) ([]llm.ModelInfo, error) { return nil, nil }
func (f *fakeLLM) GenerateStream(ctx context.Context, req llm.Request, on llm.DeltaFunc) (llm.Result, error) {
	f.mu.Lock()
	f.reqs = append(f.reqs, req)
	f.mu.Unlock()
	return f.stream(ctx, req, on)
}
func (f *fakeLLM) requests() []llm.Request {
	f.mu.Lock()
	defer f.mu.Unlock()
	return append([]llm.Request(nil), f.reqs...)
}

type recorder struct {
	mu  sync.Mutex
	evs []protocol.Outbound
}

func (r *recorder) emit(o protocol.Outbound) {
	r.mu.Lock()
	r.evs = append(r.evs, o)
	r.mu.Unlock()
}

func (r *recorder) all() []protocol.Outbound {
	r.mu.Lock()
	defer r.mu.Unlock()
	return append([]protocol.Outbound(nil), r.evs...)
}

func (r *recorder) types() []protocol.ServerType {
	var out []protocol.ServerType
	for _, e := range r.all() {
		out = append(out, e.Type)
	}
	return out
}

func (r *recorder) states() []protocol.State {
	var out []protocol.State
	for _, e := range r.all() {
		if e.Type == protocol.ServerConversationState {
			out = append(out, e.Payload.(protocol.StatePayload).State)
		}
	}
	return out
}

func (r *recorder) find(t protocol.ServerType) (protocol.Outbound, bool) {
	for _, e := range r.all() {
		if e.Type == t {
			return e, true
		}
	}
	return protocol.Outbound{}, false
}

func setup(t *testing.T, f *fakeLLM, cfg Config) (*Agent, *conversation.Service, string) {
	t.Helper()
	svc := conversation.NewService(conversation.NewMemoryRepo())
	c, err := svc.Create(context.Background(), "u")
	if err != nil {
		t.Fatal(err)
	}
	log := slog.New(slog.NewTextHandler(io.Discard, nil))
	return New(svc, f, nil, nil, log, cfg), svc, c.ID
}

func input(conv, text string) Input {
	return Input{UserID: "u", ConversationID: conv, RequestID: "req1", Payload: protocol.TextMessagePayload{Text: text}}
}

func runText(a *Agent, conv, text string) *recorder {
	rec := &recorder{}
	a.HandleText(context.Background(), input(conv, text), rec.emit)
	return rec
}

func wait(t *testing.T, ch <-chan struct{}) {
	t.Helper()
	select {
	case <-ch:
	case <-time.After(3 * time.Second):
		t.Fatal("zaman aşımı: beklenen olay gelmedi")
	}
}

func equalTypes(a, b []protocol.ServerType) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// blocking: bir delta yollar, started'ı kapatır ve iptal/serbest bırakma bekler.
func blocking(started chan struct{}, release <-chan struct{}, partial string) streamFn {
	return func(ctx context.Context, _ llm.Request, on llm.DeltaFunc) (llm.Result, error) {
		_ = on(partial)
		close(started)
		select {
		case <-release:
			return llm.Result{Model: "m", Content: partial}, nil
		case <-ctx.Done():
			return llm.Result{Model: "m", Content: partial}, ctx.Err()
		}
	}
}

// ---- testler ----

func TestHandleText_SuccessEventOrderAndPersistence(t *testing.T) {
	f := &fakeLLM{stream: func(_ context.Context, _ llm.Request, on llm.DeltaFunc) (llm.Result, error) {
		_ = on("Mer")
		_ = on("haba")
		return llm.Result{Model: "m1", Content: "Merhaba", PromptTokens: 3, CompletionTokens: 2}, nil
	}}
	a, svc, conv := setup(t, f, Config{})
	rec := runText(a, conv, "selam")

	want := []protocol.ServerType{
		protocol.ServerConversationUpdated, protocol.ServerConversationState, protocol.ServerAssistantThinking,
		protocol.ServerConversationState, protocol.ServerAssistantTextDelta, protocol.ServerAssistantTextDelta,
		protocol.ServerAssistantTextFinal, protocol.ServerConversationState,
	}
	if got := rec.types(); !equalTypes(got, want) {
		t.Fatalf("olay sırası hatalı:\n got=%v\nwant=%v", got, want)
	}
	states := rec.states()
	if len(states) != 3 || states[0] != protocol.StateThinking || states[1] != protocol.StateSpeaking || states[2] != protocol.StateIdle {
		t.Fatalf("durumlar: %v", states)
	}
	for _, e := range rec.all() {
		if e.ReplyTo != "req1" || e.ConversationID != conv {
			t.Fatalf("replyTo/conversationId eksik: %+v", e)
		}
	}
	fin, _ := rec.find(protocol.ServerAssistantTextFinal)
	fp := fin.Payload.(protocol.TextFinalPayload)
	if fp.Status != "complete" || fp.Content != "Merhaba" || fp.PromptTokens != 3 {
		t.Fatalf("final: %+v", fp)
	}
	th, _ := rec.find(protocol.ServerAssistantThinking)
	if th.Payload.(protocol.ThinkingPayload).MessageID != fp.MessageID {
		t.Fatal("thinking ve final aynı messageId'yi taşımalı")
	}

	hist, _ := svc.Messages(context.Background(), "u", conv, 10)
	if len(hist) != 2 || hist[1].Role != conversation.RoleAssistant || hist[1].Content != "Merhaba" ||
		hist[1].Status != conversation.StatusComplete || hist[1].ID != fp.MessageID {
		t.Fatalf("geçmiş: %+v", hist)
	}
	if a.Busy(conv) {
		t.Fatal("üretim bitince konuşma meşgul görünmemeli")
	}
}

func TestHandleText_CancelSavesPartialAsInterrupted(t *testing.T) {
	started := make(chan struct{})
	f := &fakeLLM{stream: blocking(started, nil, "yarım")}
	a, svc, conv := setup(t, f, Config{})

	done := make(chan struct{})
	rec := &recorder{}
	go func() { a.HandleText(context.Background(), input(conv, "uzun bir iş"), rec.emit); close(done) }()

	wait(t, started)
	if !a.Cancel(conv, "user") {
		t.Fatal("aktif üretim iptal edilebilmeli")
	}
	wait(t, done)

	fin, _ := rec.find(protocol.ServerAssistantTextFinal)
	if fin.Payload.(protocol.TextFinalPayload).Status != "interrupted" {
		t.Fatalf("final durumu: %+v", fin.Payload)
	}
	intr, ok := rec.find(protocol.ServerAssistantInterrupted)
	if !ok {
		t.Fatal("assistant.interrupted gelmeli")
	}
	ip := intr.Payload.(protocol.InterruptedPayload)
	if ip.Partial != "yarım" || ip.Reason != "user" {
		t.Fatalf("interrupted payload: %+v", ip)
	}
	st := rec.states()
	if st[len(st)-2] != protocol.StateInterrupted || st[len(st)-1] != protocol.StateIdle {
		t.Fatalf("son durumlar INTERRUPTED→IDLE olmalı: %v", st)
	}
	hist, _ := svc.Messages(context.Background(), "u", conv, 10)
	if len(hist) != 2 || hist[1].Content != "yarım" || hist[1].Status != conversation.StatusInterrupted {
		t.Fatalf("kısmi içerik kaydedilmeli: %+v", hist)
	}
	if a.Cancel(conv, "user") {
		t.Fatal("üretim bittikten sonra Cancel false dönmeli")
	}
}

func TestHandleText_OneActiveGenerationPerConversation(t *testing.T) {
	started, release := make(chan struct{}), make(chan struct{})
	f := &fakeLLM{stream: blocking(started, release, "ok")}
	a, _, conv := setup(t, f, Config{})

	done := make(chan struct{})
	go func() { runText(a, conv, "birinci"); close(done) }()
	wait(t, started)

	if !a.Busy(conv) {
		t.Fatal("Busy true olmalı")
	}
	rec := runText(a, conv, "ikinci")
	evs := rec.all()
	if len(evs) != 1 || evs[0].Type != protocol.ServerError {
		t.Fatalf("tek bir error olayı bekleniyordu: %v", rec.types())
	}
	ep := evs[0].Payload.(protocol.ErrorPayload)
	if ep.Code != protocol.ErrCodeBusy || !ep.Retryable {
		t.Fatalf("busy hatası: %+v", ep)
	}
	close(release)
	wait(t, done)
	if len(f.requests()) != 1 {
		t.Fatalf("LLM yalnızca bir kez çağrılmalı, çağrı sayısı %d", len(f.requests()))
	}
}

func TestHandleText_LLMErrorKeepsPartialAndExcludesItFromNextContext(t *testing.T) {
	call := 0
	f := &fakeLLM{stream: func(_ context.Context, _ llm.Request, on llm.DeltaFunc) (llm.Result, error) {
		call++
		if call == 1 {
			_ = on("kısmi")
			return llm.Result{Model: "m", Content: "kısmi"}, errors.New("bağlantı koptu")
		}
		return llm.Result{Model: "m", Content: "tamam"}, nil
	}}
	a, svc, conv := setup(t, f, Config{})

	rec := runText(a, conv, "birinci")
	fin, _ := rec.find(protocol.ServerAssistantTextFinal)
	if fin.Payload.(protocol.TextFinalPayload).Status != "error" {
		t.Fatalf("final durumu error olmalı: %+v", fin.Payload)
	}
	ev, ok := rec.find(protocol.ServerError)
	if !ok || ev.Payload.(protocol.ErrorPayload).Code != protocol.ErrCodeLLM {
		t.Fatalf("llm_error bekleniyordu: %v", rec.types())
	}
	st := rec.states()
	if st[len(st)-2] != protocol.StateError || st[len(st)-1] != protocol.StateIdle {
		t.Fatalf("son durumlar ERROR→IDLE olmalı: %v", st)
	}
	hist, _ := svc.Messages(context.Background(), "u", conv, 10)
	if len(hist) != 2 || hist[1].Status != conversation.StatusError || hist[1].Content != "kısmi" {
		t.Fatalf("kısmi içerik error durumuyla kaydedilmeli: %+v", hist)
	}

	runText(a, conv, "ikinci")
	for _, m := range f.requests()[1].Messages {
		if strings.Contains(m.Content, "kısmi") {
			t.Fatalf("hatalı asistan mesajı bağlama girmemeli: %+v", m)
		}
	}
	msgs := f.requests()[1].Messages
	if last := msgs[len(msgs)-1]; last.Role != llm.RoleUser || last.Content != "ikinci" {
		t.Fatalf("son mesaj kullanıcının yeni mesajı olmalı: %+v", last)
	}
}

func TestHandleText_EmptyResponseAndEmptyErrorAreNotSaved(t *testing.T) {
	cases := map[string]llm.Result{"bos_yanit": {Content: "  "}, "hata_ve_bos": {}}
	for name, res := range cases {
		t.Run(name, func(t *testing.T) {
			var err error
			if name == "hata_ve_bos" {
				err = errors.New("x")
			}
			f := &fakeLLM{stream: func(context.Context, llm.Request, llm.DeltaFunc) (llm.Result, error) { return res, err }}
			a, svc, conv := setup(t, f, Config{})
			rec := runText(a, conv, "merhaba")
			ev, ok := rec.find(protocol.ServerError)
			if !ok || ev.Payload.(protocol.ErrorPayload).Code != protocol.ErrCodeLLM {
				t.Fatalf("llm_error bekleniyordu: %v", rec.types())
			}
			if hist, _ := svc.Messages(context.Background(), "u", conv, 10); len(hist) != 1 {
				t.Fatalf("yalnızca kullanıcı mesajı kayıtlı olmalı: %+v", hist)
			}
		})
	}
}

func TestHandleText_Validation(t *testing.T) {
	f := &fakeLLM{stream: func(context.Context, llm.Request, llm.DeltaFunc) (llm.Result, error) {
		return llm.Result{Content: "x"}, nil
	}}
	a, _, conv := setup(t, f, Config{MaxInputRunes: 5})
	cases := []struct{ name, conv, text, code string }{
		{"konusma_yok", "", "selam", protocol.ErrCodeBadRequest},
		{"bos_mesaj", conv, " \n ", protocol.ErrCodeBadRequest},
		{"bilinmeyen_konusma", "conv_yok", "selam", protocol.ErrCodeBadRequest},
		{"cok_uzun", conv, "altıncı", protocol.ErrCodeTooLarge},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			evs := runText(a, c.conv, c.text).all()
			if len(evs) != 1 || evs[0].Type != protocol.ServerError || evs[0].Payload.(protocol.ErrorPayload).Code != c.code {
				t.Fatalf("tek error(%s) bekleniyordu: %+v", c.code, evs)
			}
		})
	}
	if n := len(f.requests()); n != 0 {
		t.Fatalf("doğrulama hatasında LLM çağrılmamalı, çağrı sayısı %d", n)
	}
}

func TestHandleText_ParentContextCancelIsDisconnect(t *testing.T) {
	started := make(chan struct{})
	f := &fakeLLM{stream: blocking(started, nil, "yarım")}
	a, svc, conv := setup(t, f, Config{})

	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan struct{})
	rec := &recorder{}
	go func() { a.HandleText(ctx, input(conv, "iş"), rec.emit); close(done) }()
	wait(t, started)
	cancel()
	wait(t, done)

	intr, ok := rec.find(protocol.ServerAssistantInterrupted)
	if !ok || intr.Payload.(protocol.InterruptedPayload).Reason != "disconnect" {
		t.Fatalf("sebep disconnect olmalı: %+v", intr.Payload)
	}
	// bağlam iptal olsa bile kayıt tamamlanmalı
	if hist, _ := svc.Messages(context.Background(), "u", conv, 10); len(hist) != 2 || hist[1].Status != conversation.StatusInterrupted {
		t.Fatalf("kısmi içerik kaydedilmeli: %+v", hist)
	}
}

func TestClose_CancelsActiveWaitsAndRejectsNew(t *testing.T) {
	started := make(chan struct{})
	f := &fakeLLM{stream: blocking(started, nil, "yarım")}
	a, _, conv := setup(t, f, Config{})

	done := make(chan struct{})
	rec := &recorder{}
	go func() { a.HandleText(context.Background(), input(conv, "iş"), rec.emit); close(done) }()
	wait(t, started)

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	if err := a.Close(ctx); err != nil {
		t.Fatalf("Close: %v", err)
	}
	wait(t, done)
	if intr, ok := rec.find(protocol.ServerAssistantInterrupted); !ok || intr.Payload.(protocol.InterruptedPayload).Reason != "shutdown" {
		t.Fatalf("sebep shutdown olmalı: %+v", intr.Payload)
	}

	evs := runText(a, conv, "kapandıktan sonra").all()
	if len(evs) != 1 || evs[0].Payload.(protocol.ErrorPayload).Code != protocol.ErrCodeBusy {
		t.Fatalf("kapalı ajan yeni üretimi reddetmeli: %+v", evs)
	}
}

func TestHandleText_GenerationTimeout(t *testing.T) {
	f := &fakeLLM{stream: func(ctx context.Context, _ llm.Request, on llm.DeltaFunc) (llm.Result, error) {
		_ = on("x")
		<-ctx.Done()
		return llm.Result{Content: "x"}, ctx.Err()
	}}
	a, _, conv := setup(t, f, Config{GenerationTimeout: 30 * time.Millisecond})
	rec := runText(a, conv, "iş")
	ev, ok := rec.find(protocol.ServerError)
	if !ok || !strings.Contains(ev.Payload.(protocol.ErrorPayload).Message, "süresi aşıldı") {
		t.Fatalf("zaman aşımı hatası bekleniyordu: %v", rec.types())
	}
	fin, _ := rec.find(protocol.ServerAssistantTextFinal)
	if fin.Payload.(protocol.TextFinalPayload).Status != "error" {
		t.Fatalf("final durumu error olmalı: %+v", fin.Payload)
	}
}

// ---- Phase 2: bağlam yöneticisi entegrasyonu ----

// seedLong konuşmaya n adet (kullanıcı, asistan) çifti ekler; her mesaj ≈ 400 token.
func seedLong(t *testing.T, svc *conversation.Service, conv string, n int) {
	t.Helper()
	body := strings.Repeat("uzun içerik ", 100) // 1200 karakter
	ctx := context.Background()
	for i := 0; i < n; i++ {
		if _, _, err := svc.AddUserMessage(ctx, "u", conv, body); err != nil {
			t.Fatal(err)
		}
		if _, err := svc.AddAssistantMessage(ctx, conv, conversation.AssistantMessage{
			ID: "msg_seed_" + string(rune('a'+i)), Content: body, Status: conversation.StatusComplete,
		}); err != nil {
			t.Fatal(err)
		}
	}
}

func okLLM() *fakeLLM {
	return &fakeLLM{stream: func(_ context.Context, _ llm.Request, on llm.DeltaFunc) (llm.Result, error) {
		_ = on("tamam")
		return llm.Result{Model: "m", Content: "tamam"}, nil
	}}
}

func TestHandleText_ContextIsFittedToWindow(t *testing.T) {
	f := okLLM()
	a, svc, conv := setup(t, f, Config{ContextWindow: 2048, MaxHistory: 100})
	seedLong(t, svc, conv, 10) // 20 mesaj ≈ 8000 token → pencereye sığmaz
	runText(a, conv, "yeni soru")

	reqs := f.requests()
	if len(reqs) != 1 {
		t.Fatalf("1 istek beklenirdi: %d", len(reqs))
	}
	msgs := reqs[0].Messages
	if len(msgs) >= 22 {
		t.Fatalf("geçmiş kırpılmalıydı, mesaj sayısı=%d", len(msgs))
	}
	if last := msgs[len(msgs)-1]; last.Role != llm.RoleUser || last.Content != "yeni soru" {
		t.Fatalf("son mesaj yeni soru olmalı: %+v", last.Content)
	}
	if msgs[0].Role != llm.RoleSystem || !strings.Contains(msgs[0].Content, "Bağlam notu") {
		t.Fatal("sistem istemi başta kalmalı ve kırpma notunu taşımalı")
	}
	if len(msgs) > 2 && msgs[1].Role != llm.RoleUser {
		t.Fatalf("geçmiş user ile başlamalı: %s", msgs[1].Role)
	}
	total := 0
	for _, m := range msgs {
		total += ctxmgr.EstimateTokens(m.Content) + 4
	}
	if total > 2048 {
		t.Fatalf("tahmini toplam pencereyi aşıyor: %d > 2048", total)
	}
}

func TestHandleText_SettingsContextSizeWidensWindow(t *testing.T) {
	f := okLLM()
	// Ayarlardaki ContextSize (OptionsFunc) varsayılan pencereyi ezer: büyük pencerede hiçbir şey atılmaz.
	a, svc, conv := setup(t, f, Config{
		ContextWindow: 2048, MaxHistory: 100,
		OptionsFunc: func() llm.Options { return llm.Options{ContextSize: 32768} },
	})
	seedLong(t, svc, conv, 10)
	runText(a, conv, "yeni soru")

	msgs := f.requests()[0].Messages
	// system + 20 geçmiş + yeni soru
	if len(msgs) != 22 {
		t.Fatalf("büyük pencerede kırpma olmamalı: mesaj sayısı=%d", len(msgs))
	}
	if strings.Contains(msgs[0].Content, "Bağlam notu") {
		t.Fatal("kırpma yokken bağlam notu eklenmemeli")
	}
}

func TestHandleText_ShortHistoryIsUntouched(t *testing.T) {
	f := okLLM()
	a, _, conv := setup(t, f, Config{})
	runText(a, conv, "merhaba")
	msgs := f.requests()[0].Messages
	if len(msgs) != 2 || msgs[1].Content != "merhaba" || strings.Contains(msgs[0].Content, "Bağlam notu") {
		t.Fatalf("kısa konuşma olduğu gibi gitmeli: %d mesaj", len(msgs))
	}
}

// ---- model seçimi (regresyon: "hangi modeli seçsem hep muse-glimmer" hatası) ----

// REGRESYON: Ayar ekranından seçilen model üretime UYGULANMALIDIR.
// İstemci payload'da model göndermez (protokol: "boşsa sunucu varsayılanı");
// bu yüzden ajanın ayarlardaki modele düşmesi gerekir. Düşmezse sağlayıcının
// .env'deki varsayılanı (muse-glimmer:30b) kullanılır ve seçim etkisiz kalır.
func TestHandleText_UsesModelFromSettings(t *testing.T) {
	f := okLLM()
	a, _, conv := setup(t, f, Config{ModelFunc: func() string { return "qwen3:14b" }})
	runText(a, conv, "merhaba")

	reqs := f.requests()
	if len(reqs) != 1 {
		t.Fatalf("1 istek beklenirdi: %d", len(reqs))
	}
	if reqs[0].Model != "qwen3:14b" {
		t.Fatalf("ayar modeli kullanılmalıydı, gönderilen: %q", reqs[0].Model)
	}
}

// Ayar üretim anında okunmalı: mesajlar arasında model değiştirilirse BİR SONRAKİ
// mesaj yeni modeli kullanır (uygulama yeniden başlatmaya gerek kalmaz).
func TestHandleText_ModelChangeTakesEffectNextMessage(t *testing.T) {
	f := okLLM()
	model := "ilk:30b"
	a, _, conv := setup(t, f, Config{ModelFunc: func() string { return model }})

	runText(a, conv, "birinci")
	model = "ikinci:8b"
	runText(a, conv, "ikinci")

	reqs := f.requests()
	if len(reqs) != 2 {
		t.Fatalf("2 istek beklenirdi: %d", len(reqs))
	}
	if reqs[0].Model != "ilk:30b" || reqs[1].Model != "ikinci:8b" {
		t.Fatalf("model değişikliği ikinci mesaja yansımadı: %q, %q", reqs[0].Model, reqs[1].Model)
	}
}

// İstemci payload'da model AÇIKÇA verirse o kazanır (ayarı geçersiz kılma yetkisi).
func TestHandleText_ExplicitPayloadModelWins(t *testing.T) {
	f := okLLM()
	a, _, conv := setup(t, f, Config{ModelFunc: func() string { return "ayar:30b" }})

	in := input(conv, "merhaba")
	in.Payload.Model = "istek:8b"
	rec := &recorder{}
	a.HandleText(context.Background(), in, rec.emit)

	if got := f.requests()[0].Model; got != "istek:8b" {
		t.Fatalf("payload'daki model öncelikli olmalıydı, gönderilen: %q", got)
	}
}

// ModelFunc yoksa (bağlanmamış kurulum) istek modeli BOŞ gider: sağlayıcının kendi
// varsayılanını kullanır. Bu, Ollama'nın beklediği sözleşmedir (bkz. llm.Provider).
func TestResolveModel_Fallbacks(t *testing.T) {
	a, _, _ := setup(t, okLLM(), Config{})
	if got := a.resolveModel(""); got != "" {
		t.Fatalf("ModelFunc yokken boş dönmeli, döndü: %q", got)
	}
	if got := a.resolveModel("  "); got != "" {
		t.Fatalf("yalnızca boşluk modeli boş sayılmalı, döndü: %q", got)
	}

	withFunc, _, _ := setup(t, okLLM(), Config{ModelFunc: func() string { return " ayar:30b " }})
	if got := withFunc.resolveModel(""); got != "ayar:30b" {
		t.Fatalf("ayar modeli kırpılmış dönmeliydi, döndü: %q", got)
	}
	if got := withFunc.resolveModel("istek:8b"); got != "istek:8b" {
		t.Fatalf("payload modeli öncelikli olmalıydı, döndü: %q", got)
	}
}

// ModelFunc PANİKLERSE üretim durmaz: boş döner, sağlayıcı varsayılanına düşer.
// (llmOptions ile aynı politika: ayar okunamazsa sohbet kesintiye uğramaz.)
func TestResolveModel_PanicFallsBackToProviderDefault(t *testing.T) {
	a, _, _ := setup(t, okLLM(), Config{ModelFunc: func() string { panic("ayar patladı") }})
	if got := a.resolveModel(""); got != "" {
		t.Fatalf("panik sonrası boş dönmeli, döndü: %q", got)
	}
	if got := a.resolveModel("istek:8b"); got != "istek:8b" {
		t.Fatalf("panik olsa bile payload modeli korunmalıydı, döndü: %q", got)
	}
}
