package main

// Eklenti calistirici: her eklenti AYRI surec olarak calisir.
//  - Yalnizca <veriKlasoru>\eklentiler altindaki dosyalar calistirilir (yol kontrolu).
//  - Girdi tek JSON satiri (stdin), cikti tek JSON satiri (stdout).
//  - Zaman asiminda surec sonlandirilir; cikti boyutu sinirlidir.
//  - Eklenti stderr'i okunmaz ve gunluge yazilmaz (sizinti riski).

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

const (
	calistirZamanAsimi = 10 * time.Second
	calistirMaksCikti  = 1024 * 1024
)

type eklentiGirdi struct {
	Islem string          `json:"islem"`
	Veri  json.RawMessage `json:"veri,omitempty"`
	// Ayar: kullanicinin bu PC'de kaydettigi ayarlar (opak JSON; eklenti kendi anlamini bilir).
	Ayar json.RawMessage `json:"ayar,omitempty"`
}

type eklentiCikti struct {
	Tamam bool            `json:"tamam"`
	Veri  json.RawMessage `json:"veri,omitempty"`
	Hata  string          `json:"hata,omitempty"`
}

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

// eklentiYoluGecerliMi: yalnizca <veriKlasoru>\eklentiler altindaki yollar kabul edilir.
func eklentiYoluGecerliMi(yol string) error {
	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
}

// eklentiCalistir: ayri surec olarak calistirir. Yol dogrulamasi cagiranin sorumlulugundadir
// (uretimde eklentiYoluGecerliMi, testlerde test binary'si).
func eklentiCalistir(yol string, args []string, girdi eklentiGirdi, sure time.Duration) (eklentiCikti, error) {
	var bos eklentiCikti
	bosalt, err := eklentiSlotAl(yol)
	if err != nil {
		return bos, err
	}
	defer bosalt()
	ctx, iptal := context.WithTimeout(context.Background(), sure)
	defer iptal()

	veri, err := json.Marshal(girdi)
	if err != nil {
		return bos, err
	}
	komut := exec.CommandContext(ctx, yol, args...)
	komut.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
	komut.Stdin = bytes.NewReader(append(veri, '\n'))
	komut.Stderr = io.Discard

	cikis, err := komut.StdoutPipe()
	if err != nil {
		return bos, err
	}
	if err := komut.Start(); err != nil {
		return bos, err
	}
	surecIsNesnesineBagla(komut.Process.Pid)

	satir, okumaHatasi := bufio.NewReader(io.LimitReader(cikis, calistirMaksCikti)).ReadBytes('\n')
	bekleHatasi := komut.Wait()

	if ctx.Err() == context.DeadlineExceeded {
		return bos, errors.New("eklenti zaman asimina ugradi")
	}
	if bekleHatasi != nil {
		return bos, errors.New("eklenti hata ile sonlandi")
	}
	if okumaHatasi != nil && len(satir) == 0 {
		return bos, errors.New("eklenti cikti uretmedi ya da cikti cok buyuk")
	}
	var cikti eklentiCikti
	if err := json.Unmarshal(satir, &cikti); err != nil {
		return bos, errors.New("eklenti gecersiz cikti dondu")
	}
	return cikti, nil
}
