package sdk

import (
	"bufio"
	"encoding/json"
	"io"
	"testing"
)

// baslat: bir eklentiyi bellek ici borularla calistirir; istemci tarafi cerceve yazar/okur.
func baslat(t *testing.T, e *Eklenti) (gonder func(Mesaj), al func() Mesaj) {
	t.Helper()
	pin, cw := io.Pipe()
	cr, pout := io.Pipe()
	go func() { _ = e.Calistir(pin, pout) }()
	r := bufio.NewReader(cr)
	gonder = func(m Mesaj) {
		b, err := cerceve(m)
		if err != nil {
			t.Fatal(err)
		}
		if _, err := cw.Write(b); err != nil {
			t.Fatal(err)
		}
	}
	al = func() Mesaj {
		m, err := oku(r)
		if err != nil {
			t.Fatal(err)
		}
		return m
	}
	t.Cleanup(func() { cw.Close(); cr.Close() })
	return gonder, al
}

func TestElSikisVePing(t *testing.T) {
	e := Yeni("test")
	gonder, al := baslat(t, e)
	gonder(Mesaj{Id: json.RawMessage("1"), Method: YontemMerhaba})
	m := al()
	var s struct {
		Protokol int    `json:"protokol"`
		Eklenti  string `json:"eklenti"`
	}
	if err := json.Unmarshal(m.Result, &s); err != nil || s.Protokol != ProtokolSurumu || s.Eklenti != "test" {
		t.Fatalf("el sikisma yanit hatali: %s", m.Result)
	}
	gonder(Mesaj{Id: json.RawMessage("2"), Method: YontemPing})
	if m := al(); string(m.Id) != "2" || m.Error != nil {
		t.Fatalf("ping cevabi hatali: %+v", m)
	}
}

// Havuz doluyken (islemci bloklu) ping yine cevaplanmali; bridge ping zaman asimi yemez.
func TestPingDolDavuzdaCevaplanir(t *testing.T) {
	e := Yeni("test").Havuz(1)
	serbest := make(chan struct{})
	e.Kayit("blok", func(c *Cagri) (any, error) {
		<-serbest
		return map[string]any{}, nil
	})
	gonder, al := baslat(t, e)
	gonder(Mesaj{Id: json.RawMessage("2"), Method: "blok"})
	gonder(Mesaj{Id: json.RawMessage("3"), Method: YontemPing})
	if m := al(); string(m.Id) != "3" {
		t.Fatalf("ping once cevaplanmaliydi, gelen id: %s", m.Id)
	}
	close(serbest)
	if m := al(); string(m.Id) != "2" {
		t.Fatalf("blok islemi sonra cevaplanmaliydi, gelen id: %s", m.Id)
	}
}

// onay.iste: bridge'e istek gider, bridge cevap verir, islemci karari alir.
func TestOnayIsteGidisDonus(t *testing.T) {
	e := Yeni("test")
	e.Kayit("onay", func(c *Cagri) (any, error) {
		izin, err := c.OnayIste("demo")
		return map[string]bool{"izin": izin}, err
	})
	gonder, al := baslat(t, e)
	gonder(Mesaj{Id: json.RawMessage("5"), Method: "onay"})

	istek := al()
	if istek.Method != YontemOnayIste {
		t.Fatalf("onay.iste bekleniyordu, gelen: %s", istek.Method)
	}
	gonder(Mesaj{Id: istek.Id, Result: json.RawMessage(`{"izin":true}`)})

	son := al()
	var s struct {
		Izin bool `json:"izin"`
	}
	if string(son.Id) != "5" || json.Unmarshal(son.Result, &s) != nil || !s.Izin {
		t.Fatalf("onay sonucu hatali: id=%s sonuc=%s", son.Id, son.Result)
	}
}

// Parca bildirimleri sirayla gider; sonuc ayri bir cevap olarak gelir.
func TestParcaSirasi(t *testing.T) {
	e := Yeni("test")
	e.Kayit("parca", func(c *Cagri) (any, error) {
		for n, p := range []string{"a", "b"} {
			if err := c.ParcaGonder("p1", n, 2, p); err != nil {
				return nil, err
			}
		}
		return map[string]string{"parcaId": "p1"}, nil
	})
	gonder, al := baslat(t, e)
	gonder(Mesaj{Id: json.RawMessage("4"), Method: "parca"})
	for n := 0; n < 2; n++ {
		m := al()
		var p struct {
			Id   string `json:"id"`
			Sira int    `json:"sira"`
		}
		if m.Method != YontemParca || json.Unmarshal(m.Params, &p) != nil || p.Sira != n || p.Id != "p1" {
			t.Fatalf("parca %d hatali: %+v", n, m)
		}
	}
	if m := al(); string(m.Id) != "4" {
		t.Fatalf("son cevap id 4 olmaliydi: %s", m.Id)
	}
}

func TestAyarVeBilinmeyenIslem(t *testing.T) {
	e := Yeni("test")
	e.Kayit("oku", func(c *Cagri) (any, error) { return string(c.Ayar), nil })
	gonder, al := baslat(t, e)
	gonder(Mesaj{Method: YontemAyar, Params: json.RawMessage(`{"k":1}`)})
	gonder(Mesaj{Id: json.RawMessage("7"), Method: "oku"})
	m := al()
	var s string
	if json.Unmarshal(m.Result, &s) != nil || s != `{"k":1}` {
		t.Fatalf("ayar okunamadi: %s", m.Result)
	}
	gonder(Mesaj{Id: json.RawMessage("8"), Method: "yok"})
	if m := al(); m.Error == nil || m.Error.Kod != KodIslemYok {
		t.Fatalf("bilinmeyen islem hata dondurmeliydi: %+v", m)
	}
}
