package protokol

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"strings"
	"sync"
	"testing"
	"time"
)

func TestCerceveGidisDonus(t *testing.T) {
	c, err := Cerceve(Mesaj{Id: json.RawMessage("7"), Method: "ping"})
	if err != nil {
		t.Fatal(err)
	}
	m, err := Oku(bufio.NewReader(bytes.NewReader(c)))
	if err != nil {
		t.Fatalf("oku: %v", err)
	}
	if m.Method != "ping" || string(m.Id) != "7" || m.JSONRPC != "2.0" {
		t.Fatalf("beklenmeyen mesaj: %+v", m)
	}
}

func TestOkuBoyutSiniri(t *testing.T) {
	girdi := "Content-Length: 4194305\r\n\r\n"
	_, err := Oku(bufio.NewReader(strings.NewReader(girdi)))
	if !errors.Is(err, ErrCerceve) {
		t.Fatalf("ErrCerceve bekleniyordu, %v geldi", err)
	}
}

func TestOkuBaslikYok(t *testing.T) {
	_, err := Oku(bufio.NewReader(strings.NewReader("X: y\r\n\r\n{}")))
	if !errors.Is(err, ErrCerceve) {
		t.Fatalf("ErrCerceve bekleniyordu, %v geldi", err)
	}
}

func TestOkuBozukJSON(t *testing.T) {
	girdi := "Content-Length: 3\r\n\r\n{x}"
	_, err := Oku(bufio.NewReader(strings.NewReader(girdi)))
	if !errors.Is(err, ErrCerceve) {
		t.Fatalf("ErrCerceve bekleniyordu, %v geldi", err)
	}
}

// yankiEsle: istemcinin karsisinda calisan basit bir eklenti. Her istege sonuc doner.
func yankiEsle(okuyan io.Reader, yazan io.Writer) {
	go func() {
		r := bufio.NewReader(okuyan)
		for {
			m, err := Oku(r)
			if err != nil {
				return
			}
			c, err := Cerceve(Mesaj{Id: m.Id, Result: json.RawMessage(`{"tamam":true}`)})
			if err != nil {
				return
			}
			if _, err := yazan.Write(c); err != nil {
				return
			}
		}
	}()
}

func TestCagirYanitEslesir(t *testing.T) {
	// Istemci -> peer: istemci yazar, peer okur.
	peerOku, istYaz := io.Pipe()
	// Peer -> istemci: peer yazar, istemci okur.
	istOku, peerYaz := io.Pipe()
	i := Yeni(istOku, istYaz, nil)
	defer i.Kapat()
	yankiEsle(peerOku, peerYaz)

	ctx, iptal := context.WithTimeout(context.Background(), 2*time.Second)
	defer iptal()
	sonuc, err := i.Cagir(ctx, "ping", nil)
	if err != nil {
		t.Fatalf("cagir: %v", err)
	}
	if string(sonuc) != `{"tamam":true}` {
		t.Fatalf("beklenmeyen sonuc: %s", sonuc)
	}
}

func TestCagirZamanAsimi(t *testing.T) {
	_, istYaz := io.Pipe() // kimse okumuyor
	istOku, _ := io.Pipe() // kimse yazmiyor
	i := Yeni(istOku, istYaz, nil)
	defer i.Kapat()

	ctx, iptal := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer iptal()
	_, err := i.Cagir(ctx, "ping", nil)
	if !errors.Is(err, ErrZamanAsimi) {
		t.Fatalf("ErrZamanAsimi bekleniyordu, %v geldi", err)
	}
}

func TestBekleyenSiniriDolunca(t *testing.T) {
	// Karsi taraf hic okumaz ve yazmaz: istekler cevapsiz bekler.
	_, istYaz := io.Pipe()
	istOku, _ := io.Pipe()
	i := Yeni(istOku, istYaz, nil)

	ctx, iptal := context.WithTimeout(context.Background(), 5*time.Second)
	defer iptal()

	var wg sync.WaitGroup
	hatalar := make(chan error, MaksBekleyen)
	for n := 0; n < MaksBekleyen; n++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_, err := i.Cagir(ctx, "ara", nil)
			hatalar <- err
		}()
	}
	// Hepsinin kaydini bekle.
	for tur := 0; tur < 400; tur++ {
		i.mu.Lock()
		n := len(i.bekleyen)
		i.mu.Unlock()
		if n == MaksBekleyen {
			break
		}
		time.Sleep(5 * time.Millisecond)
	}

	baslangic := time.Now()
	_, err := i.Cagir(ctx, "ara", nil)
	if !errors.Is(err, ErrMesgul) {
		t.Fatalf("ErrMesgul bekleniyordu, %v geldi", err)
	}
	if time.Since(baslangic) > 100*time.Millisecond {
		t.Fatalf("sinir dolunca hemen donmeliydi: %v", time.Since(baslangic))
	}

	// Baglanti kapaninca bekleyen istekler de ErrKapali ile bitmeli.
	i.Kapat()
	wg.Wait()
	close(hatalar)
	for err := range hatalar {
		if !errors.Is(err, ErrKapali) {
			t.Fatalf("bekleyen istek ErrKapali bekleniyordu, %v geldi", err)
		}
	}
}

func TestGelenIstekIsleyiciyeGider(t *testing.T) {
	istOku, peerYaz := io.Pipe()
	gelen := make(chan Mesaj, 1)
	i := Yeni(istOku, io.Discard, func(m Mesaj) { gelen <- m })
	defer i.Kapat()

	c, err := Cerceve(Mesaj{Method: "olay", Params: json.RawMessage(`{"x":1}`)})
	if err != nil {
		t.Fatal(err)
	}
	go func() { _, _ = peerYaz.Write(c) }()

	select {
	case m := <-gelen:
		if m.Method != "olay" {
			t.Fatalf("beklenmeyen metot: %s", m.Method)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("isleyici cagrilmadi")
	}
}

func TestPeerKapaninca_BekleyenleriBitirir(t *testing.T) {
	_, istYaz := io.Pipe()
	istOku, peerYaz := io.Pipe()
	i := Yeni(istOku, istYaz, nil)

	sonuc := make(chan error, 1)
	go func() {
		_, err := i.Cagir(context.Background(), "uzun", nil)
		sonuc <- err
	}()
	time.Sleep(20 * time.Millisecond)
	_ = peerYaz.Close()

	select {
	case err := <-sonuc:
		if !errors.Is(err, ErrKapali) {
			t.Fatalf("ErrKapali bekleniyordu, %v geldi", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("baglanti kapaninca bekleyen istek bitmedi")
	}
}
