package agent

import (
	"context"
	"errors"
	"testing"

	"fiko-ai/internal/protocol"
)

// newConfirmAgent, onay akışı için hazır bir ajan kurar (fakeRunner tools_test.go'da tanımlı).
//
// NOT: a.send() olayı AKTİF TURUN emit'ine yollar (bkz. Agent.emitFor). Canlı akışta
// onay formu bir tur içinde üretildiği için tur vardır. Testte de öyle kurgulanır:
// yoksa emit nil döner ve olay sessizce düşerdi — ki bu, "form gönderilmedi" gibi
// görünür ama aslında "tur yok" demektir. Yanlış test yazmamak için tur açıyoruz.
func newConfirmAgent(t *testing.T, mode string) (*Agent, *fakeRunner, *[]protocol.Outbound) {
	t.Helper()
	r := &fakeRunner{out: map[string]any{"deleted": true, "collection": "customers"}}
	var sent []protocol.Outbound
	a := &Agent{
		emit:    func(o protocol.Outbound) { sent = append(sent, o) },
		runner:  r,
		confirm: NewPendingConfirm(),
		active:  make(map[string]*run),
	}
	if mode != "" {
		a.cfg.ConfirmFunc = func() string { return mode }
	}
	return a, r, &sent
}

// openRun, konuşma için sahte bir AKTİF TUR açar (olayların gitmesi için gerekir).
func openRun(a *Agent, convID string, sent *[]protocol.Outbound) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.active[convID] = &run{
		emit: func(o protocol.Outbound) { *sent = append(*sent, o) },
	}
}

// ---- Politika ----

func TestConfirmPolicy_DefaultIsRisky(t *testing.T) {
	a, _, _ := newConfirmAgent(t, "") // ConfirmFunc bağlanmamış
	if got := a.confirmPolicy(); got != confirmRisky {
		t.Errorf("varsayılan politika = %q, beklenen risky", got)
	}
	a2, _, _ := newConfirmAgent(t, "always")
	if got := a2.confirmPolicy(); got != confirmAlways {
		t.Errorf("politika = %q, beklenen always", got)
	}
	a3, _, _ := newConfirmAgent(t, "bozuk-deger")
	if got := a3.confirmPolicy(); got != confirmRisky {
		t.Errorf("geçersiz mod güvenli varsayılana düşmeli, gelen: %q", got)
	}
}

func TestConfirmRequired_PolicyMatrix(t *testing.T) {
	riskyForm := map[string]any{"destructive": true}
	safeForm := map[string]any{"destructive": false}
	cases := []struct {
		mode string
		form map[string]any
		miss []string
		want bool
	}{
		{"never", riskyForm, []string{"x"}, false},
		{"always", safeForm, nil, true},
		{"risky", riskyForm, nil, true},
		{"risky", safeForm, nil, false},
		{"risky", safeForm, []string{"name"}, false},
		{"missing", safeForm, nil, false},
		{"missing", safeForm, []string{"name"}, true},
	}
	for _, tc := range cases {
		a, _, _ := newConfirmAgent(t, tc.mode)
		if got := a.confirmRequired(tc.form, tc.miss); got != tc.want {
			t.Errorf("mode=%s destructive=%v missing=%v → %v, beklenen %v",
				tc.mode, tc.form["destructive"], tc.miss, got, tc.want)
		}
	}
}

// ---- Onay formu gönderimi ----

func TestHandleConfirmForm_SendsWhenRequired(t *testing.T) {
	a, _, sent := newConfirmAgent(t, "risky")
	openRun(a, "c1", sent)
	out := map[string]any{"confirm": map[string]any{
		"action": "delete", "title": "Müşteriler silinsin mi?", "destructive": true,
		"confirmLabel": "Sil", "cancelLabel": "Vazgeç",
		"payload": map[string]any{"collection": "customers", "ref": "cus_1"},
		"fields":  []any{map[string]any{"key": "name", "label": "name", "value": "Aril"}},
	}}
	id, ok := a.handleConfirmForm(Input{ConversationID: "c1"}, "tc_1", "biz.delete", out)
	if !ok || id == "" {
		t.Fatal("onay formu gönderilmeliydi")
	}
	if len(*sent) != 1 || (*sent)[0].Type != protocol.ServerToolConfirm {
		t.Fatalf("tool.confirm olayı gönderilmedi: %+v", *sent)
	}
	p, _ := (*sent)[0].Payload.(protocol.ToolConfirmPayload)
	if p.Tool != "biz.delete" || !p.Destructive || p.ConfirmLabel != "Sil" {
		t.Errorf("form içeriği eksik: %+v", p)
	}
	if len(p.Fields) != 1 || p.Fields[0].Key != "name" {
		t.Errorf("alanlar aktarılmadı: %+v", p.Fields)
	}
	if a.confirm.Len() != 1 {
		t.Error("form depoda beklemeli")
	}
}

// ---- Onay cevabı ----

func TestConfirmAnswer_RunsToolOnlyOnApproval(t *testing.T) {
	a, r, sent := newConfirmAgent(t, "risky")
	openRun(a, "c1", sent)
	out := map[string]any{"confirm": map[string]any{
		"action": "delete", "title": "Sil", "destructive": true,
		"payload": map[string]any{"collection": "customers", "ref": "cus_1"},
	}}
	cid, _ := a.handleConfirmForm(Input{ConversationID: "c1"}, "tc", "biz.delete", out)

	a.ConfirmAnswer(context.Background(), "c1", "u1",
		protocol.ToolConfirmAnswerPayload{ConfirmID: cid, Confirmed: true})

	if len(r.calls) != 1 || r.calls[0] != "biz.delete" {
		t.Fatalf("onaylanınca araç çalışmalıydı: %v", r.calls)
	}
	if r.args[0]["confirmed"] != true {
		t.Error("tool'a confirmed:true gitmeli (yoksa araç yine form döndürür)")
	}
	if len(*sent) == 0 || (*sent)[len(*sent)-1].Type != protocol.ServerToolConfirmResolved {
		t.Errorf("çözüm olayı gönderilmeliydi: %+v", *sent)
	}
}

func TestConfirmAnswer_CancelDoesNothing(t *testing.T) {
	a, r, sent := newConfirmAgent(t, "risky")
	openRun(a, "c1", sent)
	out := map[string]any{"confirm": map[string]any{
		"action": "delete", "destructive": true,
		"payload": map[string]any{"collection": "customers", "ref": "cus_1"},
	}}
	cid, _ := a.handleConfirmForm(Input{}, "tc", "biz.delete", out)

	a.ConfirmAnswer(context.Background(), "c1", "u1",
		protocol.ToolConfirmAnswerPayload{ConfirmID: cid, Confirmed: false})

	if len(r.calls) != 0 {
		t.Fatalf("vazgeçildiğinde araç ÇALIŞMAMALI: %v", r.calls)
	}
	if len(*sent) == 0 || (*sent)[len(*sent)-1].Type != protocol.ServerToolConfirmResolved {
		t.Fatal("çözüm olayı yine de gönderilmeli (kart kapanmalı)")
	}
	p, _ := (*sent)[len(*sent)-1].Payload.(protocol.ToolConfirmResolvedPayload)
	if p.Confirmed {
		t.Error("vazgeçildiğinde confirmed=false olmalı")
	}
}

func TestConfirmAnswer_UnknownIDIsSafe(t *testing.T) {
	a, r, _ := newConfirmAgent(t, "risky")
	a.ConfirmAnswer(context.Background(), "c1", "u1",
		protocol.ToolConfirmAnswerPayload{ConfirmID: "yok-boyle-bir-id", Confirmed: true})
	if len(r.calls) != 0 {
		t.Error("bilinmeyen onay kimliğiyle araç çalışmamalı")
	}
}

// Aynı onay İKİ KERE kullanılamaz: aksi hâlde çift silme olurdu.
func TestConfirmAnswer_SingleUse(t *testing.T) {
	a, r, _ := newConfirmAgent(t, "risky")
	out := map[string]any{"confirm": map[string]any{
		"action": "delete", "destructive": true,
		"payload": map[string]any{"collection": "customers", "ref": "cus_1"},
	}}
	cid, _ := a.handleConfirmForm(Input{}, "tc", "biz.delete", out)
	a.ConfirmAnswer(context.Background(), "c1", "u1",
		protocol.ToolConfirmAnswerPayload{ConfirmID: cid, Confirmed: true})
	a.ConfirmAnswer(context.Background(), "c1", "u1",
		protocol.ToolConfirmAnswerPayload{ConfirmID: cid, Confirmed: true})
	if len(r.calls) != 1 {
		t.Fatalf("onay tekrarı araç çalıştırmamalı, çağrı sayısı = %d", len(r.calls))
	}
}

// ---- Güvenlik: kullanıcı sabit alanı değiştiremez ----

func TestApplyConfirmValues_OnlyEditableFields(t *testing.T) {
	in := map[string]any{"collection": "customers", "ref": "cus_1",
		"data": map[string]any{"name": "Eski"}}
	fields := []protocol.ToolConfirmField{
		{Key: "collection", Editable: false}, // sabit: dokunulmamalı
		{Key: "ref", Editable: false},        // sabit: dokunulmamalı
		{Key: "name", Editable: true},        // düzenlenebilir
	}
	applyConfirmValues(in, fields, map[string]string{
		"collection": "projects", // SALDIRI: başka koleksiyona yönlendirme
		"ref":        "prj_9",
		"name":       "Yeni Ad",
	})
	if in["collection"] != "customers" || in["ref"] != "cus_1" {
		t.Fatalf("sabit alanlar değiştirildi (güvenlik açığı): %+v", in)
	}
	data := in["data"].(map[string]any)
	if data["name"] != "Yeni Ad" {
		t.Errorf("düzenlenebilir alan uygulanmadı: %+v", data)
	}
}

// ---- Depo yaşam döngüsü ----

func TestPendingConfirm_ExpireRemovesStale(t *testing.T) {
	p := NewPendingConfirm()
	f := protocol.ToolConfirmPayload{Tool: "biz.delete", Title: "x"}
	f.ConfirmID = p.Add(f, map[string]any{"a": 1}, "tc")
	if p.Len() != 1 {
		t.Fatalf("form kaydedilmedi: %d", p.Len())
	}
	if _, err := p.Take(f.ConfirmID); err != nil {
		t.Fatalf("form alınamadı: %v", err)
	}
	if p.Len() != 0 {
		t.Error("alınan form depodan silinmeli")
	}
	if _, err := p.Take(f.ConfirmID); !errors.Is(err, ErrNoConfirm) {
		t.Errorf("ikinci alım hata vermeli: %v", err)
	}
	if p.Expire() != 0 {
		t.Error("süresi dolmamış form silinmemeli")
	}
}

func TestPendingConfirm_CloneArgsIsolates(t *testing.T) {
	p := NewPendingConfirm()
	src := map[string]any{"collection": "customers"}
	f := protocol.ToolConfirmPayload{Tool: "biz.delete"}
	f.ConfirmID = p.Add(f, src, "tc")
	src["collection"] = "MUTASYON" // dışarıdan değiştir
	got, _ := p.Take(f.ConfirmID)
	if got.toolInput["collection"] != "customers" {
		t.Error("paylaşılan harita mutasyona uğramamalı (kopyalanmalı)")
	}
}

// ---- Politika canlı değişimi ----

func TestConfirmPolicy_ReflectsLiveChange(t *testing.T) {
	mode := "risky"
	a, _, _ := newConfirmAgent(t, "")
	a.cfg.ConfirmFunc = func() string { return mode }
	if a.confirmPolicy() != confirmRisky {
		t.Fatal("başlangıç risky olmalı")
	}
	mode = "always" // kullanıcı ayar ekranından değiştirdi
	if a.confirmPolicy() != confirmAlways {
		t.Error("ayar değişikliği anında geçerli olmalı (sunucu yeniden başlatmaya gerek yok)")
	}
}

func TestHandleConfirmForm_SkippedWhenPolicyAllows(t *testing.T) {
	// "never": form üretildi ama istemciye GÖNDERİLMEMELİ, araç doğrudan çalışır.
	a, _, sent := newConfirmAgent(t, "never")
	out := map[string]any{"confirm": map[string]any{"action": "delete", "destructive": true}}
	if _, ok := a.handleConfirmForm(Input{}, "tc", "biz.delete", out); ok {
		t.Error("never modunda form gönderilmemeli")
	}
	if len(*sent) != 0 {
		t.Errorf("olay gitmemeliydi: %+v", *sent)
	}
}

func TestHandleConfirmForm_NoFormInOutput(t *testing.T) {
	a, _, _ := newConfirmAgent(t, "risky")
	if _, ok := a.handleConfirmForm(Input{}, "tc", "biz.find", map[string]any{"count": 3}); ok {
		t.Error("form yoksa handleConfirmForm true dönmemeli")
	}
}
