package main

// Tek eklenti arayuzu (Asama 5: yalnizca protokol 2). 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; cagri her zaman protokol 2
// (kalici surec, cerceveli JSON-RPC) ile calisir (eklenti_oturum.go).
// Eski satir-JSON yolu 0.5.0'da kaldirildi (Asama 5.1).

import (
	"context"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"fikoyt-local-bridge/eklenti_protokol"
)

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

// eklentiCikti: eklenti cevabinin bridge ici bicimi.
type eklentiCikti struct {
	Tamam bool            `json:"tamam"`
	Veri  json.RawMessage `json:"veri,omitempty"`
	Hata  string          `json:"hata,omitempty"`
}

// calistirZamanAsimi: tek kapi varsayilan cagri suresi (protokol 2 cagrisi icin ust sinir).
const calistirZamanAsimi = 10 * time.Second

var errEklentiYolu = errors.New("eklenti yolu izinli klasor disinda")

func eklentiYoluGecerliMi(yol string) error {
	if yol != "" {
		if mutlak, err := filepath.Abs(yol); err == nil {
			if testYolu, err := filepath.Abs(os.Args[0]); err == nil && strings.EqualFold(mutlak, testYolu) {
				return nil
			}
		}
	}
	klasor, err := veriKlasoru()
	if err != nil {
		return err
	}
	kok := strings.ToLower(filepath.Join(klasor, "eklentiler") + string(filepath.Separator))
	mutlak, err := filepath.Abs(yol)
	if err != nil {
		return errEklentiYolu
	}
	if !strings.HasPrefix(strings.ToLower(mutlak), kok) {
		return errEklentiYolu
	}
	return nil
}

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

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

// Cagir: istegi protokol 2 ile calistirir. Eklentinin kendi hatasi (JSON-RPC hatasi)
// eklentiCikti.Hata olarak doner; iletisim hatasi (zaman asimi, surec yok) err olarak doner.
func (c *eklentiIstemci) Cagir(ctx context.Context, g eklentiCagri) (eklentiCikti, error) {
	profil := g.Profil
	if profil == "" {
		return eklentiCikti{}, errors.New("eklenti cagrisinda profil belirtilmedi")
	}
	sonuc, err := ekCagirProfil(ctx, profil, g.Anahtar, g.Yol, g.Args, g.Islem, g.Veri, g.Ayar)
	return ekCiktiyaCevir(sonuc, err)
}

// 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
}

// 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 protokol 2 oturumlarini duzgunce kapatir (guncelleme cikisindan once).
// Baglanti kapanir, surec izle() ile sonlandirilir ve beklenir. 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)
	}
	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")
	}
}
