// Package protokol: bridge ile eklenti arasindaki Content-Length cerceveli JSON-RPC 2.0 protokolu.
//
// Cerceve bicimi (LSP benzeri):
//
//	Content-Length: N\r\n
//	\r\n
//	{...N bayt JSON...}
//
// Istemci tek okuyucu ve tek yazici goroutine kullanir. Okuyucu cevaplari "id" ile bekleyen
// isteklere eslestirir; yazici kuyrugu sinirlidir. Istek basina zaman asimi context ile verilir.
package protokol

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"
)

const (
	// MaksMesaj: tek bir JSON govdesinin ust siniri.
	MaksMesaj = 4 * 1024 * 1024
	// MaksBekleyen: bir istemcide ayni anda bekleyen istek sayisi.
	MaksBekleyen = 16
	// MaksYaziKuyrugu: giden cerceve kuyrugu uzunlugu.
	MaksYaziKuyrugu = 32
	// KodMesgul: sinir dolunca donen JSON-RPC hata kodu.
	KodMesgul = -32001

	maksBaslikSatiri = 1024
)

var (
	ErrMesgul     = errors.New("protokol: bekleyen istek siniri dolu")
	ErrKapali     = errors.New("protokol: baglanti kapali")
	ErrZamanAsimi = errors.New("protokol: zaman asimi")
	ErrCerceve    = errors.New("protokol: gecersiz cerceve")
)

// Hata: JSON-RPC hata nesnesi.
type Hata struct {
	Kod   int    `json:"code"`
	Mesaj string `json:"message"`
}

func (h *Hata) Error() string {
	return fmt.Sprintf("rpc %d: %s", h.Kod, h.Mesaj)
}

// Mesaj: JSON-RPC 2.0 zarfi. Istek (Method+Id), bildirim (Method), cevap (Id+Result/Error).
type Mesaj struct {
	JSONRPC string          `json:"jsonrpc"`
	Id      json.RawMessage `json:"id,omitempty"`
	Method  string          `json:"method,omitempty"`
	Params  json.RawMessage `json:"params,omitempty"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *Hata           `json:"error,omitempty"`
}

// Cerceve: mesajı Content-Length basligiyla birlikte baytlara cevirir.
func Cerceve(m Mesaj) ([]byte, error) {
	m.JSONRPC = "2.0"
	govde, err := json.Marshal(m)
	if err != nil {
		return nil, err
	}
	if len(govde) > MaksMesaj {
		return nil, ErrCerceve
	}
	baslik := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(govde))
	return append([]byte(baslik), govde...), nil
}

// Oku: bir cerceve okur. Boyut siniri asilirsa govde okunmadan ErrCerceve doner; bu durumda
// akis senkron degildir ve baglanti kapatilmalidir.
func Oku(r *bufio.Reader) (Mesaj, error) {
	var m Mesaj
	uzunluk := -1
	for {
		satir, err := r.ReadString('\n')
		if err != nil {
			return m, err
		}
		if len(satir) > maksBaslikSatiri {
			return m, ErrCerceve
		}
		satir = strings.TrimRight(satir, "\r\n")
		if satir == "" {
			break
		}
		ad, deger, ok := strings.Cut(satir, ":")
		if !ok {
			continue
		}
		if strings.EqualFold(strings.TrimSpace(ad), "Content-Length") {
			v, err := strconv.Atoi(strings.TrimSpace(deger))
			if err != nil || v < 0 {
				return m, ErrCerceve
			}
			uzunluk = v
		}
	}
	if uzunluk < 0 || uzunluk > MaksMesaj {
		return m, ErrCerceve
	}
	govde := make([]byte, uzunluk)
	if _, err := io.ReadFull(r, govde); err != nil {
		return m, err
	}
	if err := json.Unmarshal(govde, &m); err != nil {
		return m, ErrCerceve
	}
	return m, nil
}

type yanit struct {
	Sonuc json.RawMessage
	Hata  error
}

// Istemci: iki yonlu JSON-RPC baglantisi.
type Istemci struct {
	w        io.Writer
	r        *bufio.Reader
	yazici   chan []byte
	cikis    chan struct{}
	isleyici func(Mesaj)

	mu        sync.Mutex
	sonrakiId int64
	bekleyen  map[int64]chan yanit
	kapali    bool
}

// Yeni: r'den okuyup w'ye yazan bir istemci baslatir. isleyici, eslesmeyen gelen istek ve
// bildirimleri (ornegin eklentinin olay ya da onay istegi) alir; nil olabilir. isleyici
// okuyucu goroutine'inde calisir, bu yuzden hizli donmelidir.
func Yeni(r io.Reader, w io.Writer, isleyici func(Mesaj)) *Istemci {
	i := &Istemci{
		w:        w,
		r:        bufio.NewReader(r),
		yazici:   make(chan []byte, MaksYaziKuyrugu),
		cikis:    make(chan struct{}),
		isleyici: isleyici,
		bekleyen: map[int64]chan yanit{},
	}
	go i.yazDongusu()
	go i.okuDongusu()
	return i
}

func (i *Istemci) yazDongusu() {
	for {
		select {
		case b := <-i.yazici:
			if _, err := i.w.Write(b); err != nil {
				i.Kapat()
				return
			}
		case <-i.cikis:
			return
		}
	}
}

func (i *Istemci) okuDongusu() {
	for {
		m, err := Oku(i.r)
		if err != nil {
			i.Kapat()
			return
		}
		if m.Method == "" && len(m.Id) > 0 {
			var id int64
			if json.Unmarshal(m.Id, &id) != nil {
				continue
			}
			i.mu.Lock()
			ch, ok := i.bekleyen[id]
			if ok {
				delete(i.bekleyen, id)
			}
			i.mu.Unlock()
			if ok {
				y := yanit{Sonuc: m.Result}
				if m.Error != nil {
					y.Hata = m.Error
				}
				ch <- y
			}
			continue
		}
		if i.isleyici != nil {
			i.isleyici(m)
		}
	}
}

// Kapat: baglantiyi kapatir ve bekleyen tum istekleri ErrKapali ile bitirir. Birden fazla cagrilabilir.
func (i *Istemci) Kapat() {
	i.mu.Lock()
	defer i.mu.Unlock()
	if i.kapali {
		return
	}
	i.kapali = true
	close(i.cikis)
	for id, ch := range i.bekleyen {
		ch <- yanit{Hata: ErrKapali}
		delete(i.bekleyen, id)
	}
}

func (i *Istemci) bekleyenSil(id int64) {
	i.mu.Lock()
	delete(i.bekleyen, id)
	i.mu.Unlock()
}

func paramsJSON(params any) (json.RawMessage, error) {
	if params == nil {
		return nil, nil
	}
	return json.Marshal(params)
}

// Cagir: bir istek gonderir ve cevabi bekler. Bekleyen sinir doluysa hemen ErrMesgul doner.
// ctx bitince ErrZamanAsimi doner.
func (i *Istemci) Cagir(ctx context.Context, metot string, params any) (json.RawMessage, error) {
	pb, err := paramsJSON(params)
	if err != nil {
		return nil, err
	}
	i.mu.Lock()
	if i.kapali {
		i.mu.Unlock()
		return nil, ErrKapali
	}
	if len(i.bekleyen) >= MaksBekleyen {
		i.mu.Unlock()
		return nil, ErrMesgul
	}
	i.sonrakiId++
	id := i.sonrakiId
	ch := make(chan yanit, 1)
	i.bekleyen[id] = ch
	i.mu.Unlock()

	cerc, err := Cerceve(Mesaj{Id: json.RawMessage(strconv.FormatInt(id, 10)), Method: metot, Params: pb})
	if err != nil {
		i.bekleyenSil(id)
		return nil, err
	}
	select {
	case i.yazici <- cerc:
	case <-ctx.Done():
		i.bekleyenSil(id)
		return nil, ErrZamanAsimi
	case <-i.cikis:
		i.bekleyenSil(id)
		return nil, ErrKapali
	}
	select {
	case y := <-ch:
		return y.Sonuc, y.Hata
	case <-ctx.Done():
		i.bekleyenSil(id)
		return nil, ErrZamanAsimi
	}
}

// Bildir: cevap beklemeden bildirim (id'siz istek) gonderir. Kuyruk doluysa ErrMesgul doner.
func (i *Istemci) Bildir(metot string, params any) error {
	pb, err := paramsJSON(params)
	if err != nil {
		return err
	}
	cerc, err := Cerceve(Mesaj{Method: metot, Params: pb})
	if err != nil {
		return err
	}
	i.mu.Lock()
	kapali := i.kapali
	i.mu.Unlock()
	if kapali {
		return ErrKapali
	}
	select {
	case i.yazici <- cerc:
		return nil
	default:
		return ErrMesgul
	}
}
