package main

// Tek eklenti arayuzu (Asama 2.11). Bridge'in eklentiye yaptigi TUM cagrilar buradan gecer:
//   - web (HTTP):     everything_eklenti.go, eklenti_cagir.go
//   - sunucu (socket): eklenti_socket.go ("ara" ve "calistir"), dosya_calistir.go
// Cagiran yalnizca (anahtar, yol, islem, veri, ayar) verir; hangi protokolun kullanilacagi burada karar verilir:
//   - Protokol 2 (kalici surec, cerceveli JSON-RPC): once denenir (eklenti_oturum.go).
//   - Merhaba'ya cevap vermeyen eski satir-JSON eklentiler: eski yola dusulur (tek seferlik surec ya da
//     everything'in eski kalici sureci). Eski yol 0.5.0'da kaldirilacak (Asama 5); o zamana kadar tek kapi budur.
// Eski mi yeni mi karari eklenti yolu (surum klasoru dahil) basina bellekte tutulur; yeni surum yeniden denenir.

import (
	"context"
	"encoding/json"
	"errors"
	"sync"
	"time"

	"fikoyt-local-bridge/eklenti_protokol"
)

// eklentiCagri: tek bir eklenti isteginin tum girdileri.
type eklentiCagri struct {
	Anahtar string
	Yol     string
	Args    []string // yalnizca testlerde kullanilir; uretimde nil
	Islem   string
	Veri    json.RawMessage
	Ayar    json.RawMessage
}

// eklentiIstemci: eklentilere acilan tek kapi. Durumu paket duzeyindeki tablolarda tutulur.
type eklentiIstemci struct{}

// eklentiIstemcisi: bridge'in tek istemcisi.
var eklentiIstemcisi = &eklentiIstemci{}

// ekEskiGecici: el sikisma zaman asimi gibi belirsiz hatalarda eski isaretinin gecerlilik suresi.
// Net bir uyumsuzluk (surum uyusmadi ya da surec cikti) icin isaret surec omru boyunca kalir.
var ekEskiGecici = 2 * time.Minute

var (
	ekEskiMu sync.Mutex
	// yol -> isaretin bitisi (sifir deger: kalici)
	ekEskiYollar = map[string]time.Time{}
)

func ekEskiMi(yol string) bool {
	ekEskiMu.Lock()
	defer ekEskiMu.Unlock()
	bitis, ok := ekEskiYollar[yol]
	if !ok {
		return false
	}
	if !bitis.IsZero() && time.Now().After(bitis) {
		delete(ekEskiYollar, yol)
		return false
	}
	return true
}

func ekEskiIsaretle(yol string, kalici bool) {
	ekEskiMu.Lock()
	defer ekEskiMu.Unlock()
	if kalici {
		ekEskiYollar[yol] = time.Time{}
		return
	}
	ekEskiYollar[yol] = time.Now().Add(ekEskiGecici)
}

// Cagir: istegi uygun protokolle calistirir. Eklentinin kendi hatasi (JSON-RPC hatasi ya da eski yolda
// tamam=false) eklentiCikti.Hata olarak doner; iletisim hatasi (zaman asimi, surec yok) err olarak doner.
func (c *eklentiIstemci) Cagir(ctx context.Context, g eklentiCagri) (eklentiCikti, error) {
	if !ekEskiMi(g.Yol) {
		sonuc, err := ekCagir(ctx, g.Anahtar, g.Yol, g.Args, g.Islem, g.Veri, g.Ayar)
		if !errors.Is(err, errEklentiElSikis) {
			return ekCiktiyaCevir(sonuc, err)
		}
		kalici := errors.Is(err, protokol.ErrProtokolUyumsuz) || errors.Is(err, protokol.ErrKapali)
		olayLog("eklenti protokol 2 ile el sikismadi, eski yola dusuluyor: %s (kalici=%v)", g.Anahtar, kalici)
		ekEskiIsaretle(g.Yol, kalici)
	}
	return eskiCagir(ctx, g)
}

// eklentiCagirVarsayilan: varsayilan zaman asimiyla (calistirZamanAsimi) Cagir.
func eklentiCagirVarsayilan(g eklentiCagri) (eklentiCikti, error) {
	ctx, iptal := context.WithTimeout(context.Background(), calistirZamanAsimi)
	defer iptal()
	return eklentiIstemcisi.Cagir(ctx, g)
}

// ekCiktiyaCevir: protokol 2 sonucunu mevcut eklentiCikti biciminde sunar.
func ekCiktiyaCevir(sonuc json.RawMessage, err error) (eklentiCikti, error) {
	var rpcHata *protokol.Hata
	if errors.As(err, &rpcHata) {
		return eklentiCikti{Tamam: false, Hata: rpcHata.Mesaj}, nil
	}
	if err != nil {
		return eklentiCikti{}, err
	}
	return eklentiCikti{Tamam: true, Veri: sonuc}, nil
}

// eskiCagir: eski satir-JSON yolu. Everything "ara" icin eski kalici surec, digerleri tek seferlik surec.
func eskiCagir(ctx context.Context, g eklentiCagri) (eklentiCikti, error) {
	sure := calistirZamanAsimi
	if d, ok := ctx.Deadline(); ok {
		sure = time.Until(d)
		if sure <= 0 {
			return eklentiCikti{}, protokol.ErrZamanAsimi
		}
	}
	if g.Islem == "ara" && len(g.Args) == 0 {
		return eklentiAra(g.Anahtar, g.Yol, g.Veri, g.Ayar)
	}
	return eklentiCalistir(g.Yol, g.Args, eklentiGirdi{Islem: g.Islem, Veri: g.Veri, Ayar: g.Ayar}, sure)
}

// eklentiParcaliMi: sonuc, buyuk sonucun parcaId isaretcisi mi. Parcalar sunucuya iletilir ve orada birlestirilir;
// bu yuzden yalnizca socket yolu bu sonucu sunucuya aynen verebilir. HTTP yolu parcali sonucu kabul edemez.
func eklentiParcaliMi(veri json.RawMessage) bool {
	var p struct {
		ParcaId string `json:"parcaId"`
	}
	return json.Unmarshal(veri, &p) == nil && p.ParcaId != ""
}

// ekKapatZamanAsimi: eklentileriKapat'in toplam bekleme ust siniri.
var ekKapatZamanAsimi = 3 * time.Second

// eklentileriKapat: calisan tum eklenti surec ve oturumlarini duzgunce kapatir (guncelleme cikisindan once).
// Protokol 2 oturumlari: baglanti kapanir, surec izle() ile sonlandirilir ve beklenir.
// Eski kalici surecler: stdin kapatilir, surec sonlandirilir. Toplam bekleme ekKapatZamanAsimi ile sinirlidir.
func eklentileriKapat() {
	ekMu.Lock()
	oturumlar := make([]*ekOturum, 0, len(ekOturumlar))
	for _, o := range ekOturumlar {
		oturumlar = append(oturumlar, o)
	}
	ekMu.Unlock()

	var wg sync.WaitGroup
	for _, o := range oturumlar {
		o.istemci.Kapat()
		wg.Add(1)
		go func(o *ekOturum) {
			defer wg.Done()
			<-o.kapali
		}(o)
	}
	for _, anahtar := range surecYonetici.Anahtarlar() {
		wg.Add(1)
		go func(a string) {
			defer wg.Done()
			_ = surecYonetici.Durdur(a)
		}(anahtar)
	}
	bitti := make(chan struct{})
	go func() {
		wg.Wait()
		close(bitti)
	}()
	select {
	case <-bitti:
	case <-time.After(ekKapatZamanAsimi):
		olayLog("eklentileri kapatma zaman asimina ugradi; kalanlar is nesnesiyle sonlanacak")
	}
}
