package main

// Eklenti surecleri ile sokete baglanti: eklentinin olaylari sunucuya iletilir; sunucudan
// gelen baslat/durdur/ayar komutlari islenir. Baslatma her seferinde PC'de onay ister.
// Ayarlar bridge icin opak bir JSON nesnesidir: saklanir ve eklentiye iletilir, anlamina bakilmaz.

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"regexp"
)

// surecYonetici: bu bilgisayardaki tum eklenti surecleri.
var surecYonetici = yeniSurecYoneticisi(surecOlayYonlendir)

type surecIstegi struct {
	Id      string          `json:"id"`
	Islem   string          `json:"islem"`
	Anahtar string          `json:"anahtar"`
	Veri    json.RawMessage `json:"veri,omitempty"`
}

const (
	surecOlayBoyutLimit = 8 * 1024
	surecAyarBoyutLimit = 1024
)

// eklentiOlayIlet: eklentinin urettigi bir JSON satirini sunucuya iletir.
// (var: testlerde taklibi icin; uretimde asagidaki gercek uygulama kullanilir.)
var eklentiOlayIlet = func(anahtar string, satir []byte) {
	if !json.Valid(satir) || len(satir) > surecOlayBoyutLimit {
		return
	}
	sokAktifMu.Lock()
	b := sokAktif
	hazir := sokHazirOldu
	sokAktifMu.Unlock()
	if b == nil || !hazir {
		return
	}
	govde, err := json.Marshal(map[string]any{"anahtar": anahtar, "veri": json.RawMessage(satir)})
	if err != nil {
		return
	}
	_ = b.yaz(`42["FikoYT.bridge.eklenti.olay",` + string(govde) + `]`)
}

func surecSonucGonder(id, islem, anahtar string, tamam bool, hata string) {
	sokAktifMu.Lock()
	b := sokAktif
	hazir := sokHazirOldu
	sokAktifMu.Unlock()
	if b == nil || !hazir {
		return
	}
	govde, err := json.Marshal(map[string]any{
		"id": id, "islem": islem, "anahtar": anahtar, "tamam": tamam, "hata": hata,
	})
	if err != nil {
		return
	}
	_ = b.yaz(`42["FikoYT.bridge.surec.sonuc",` + string(govde) + `]`)
}

// ayarDosyasi: bir eklentinin son kaydedilen ayarlarinin dosyasi.
func ayarDosyasi(anahtar string) (string, error) {
	klasor, err := veriKlasoru()
	if err != nil {
		return "", err
	}
	dizin := filepath.Join(klasor, "eklenti-ayar")
	if err := os.MkdirAll(dizin, 0o700); err != nil {
		return "", err
	}
	return filepath.Join(dizin, anahtar+".json"), nil
}

// ayarKaydet: ayari dosyaya yazar. Parola DPAPI ile sifrelenir; dosyada duz metin parola bulunmaz.
func ayarKaydet(anahtar string, veri json.RawMessage) error {
	yol, err := ayarDosyasi(anahtar)
	if err != nil {
		return err
	}
	var m map[string]any
	if err := json.Unmarshal(veri, &m); err != nil || m == nil {
		return errors.New("geçersiz ayar")
	}
	if p, ok := m["parola"].(string); ok {
		delete(m, "parola")
		delete(m, "parolaSifreli")
		if p != "" {
			s, err := dpapiSifrele([]byte(p))
			if err != nil {
				return err
			}
			m["parolaSifreli"] = base64.StdEncoding.EncodeToString(s)
		}
	}
	out, err := json.Marshal(m)
	if err != nil {
		return err
	}
	return os.WriteFile(yol, out, 0o600)
}

// kayitliAyar: son kaydedilen ayar; yoksa nil. Sifreli parola bellekte cozulur (eklentiye iletmek icin).
func kayitliAyar(anahtar string) json.RawMessage {
	yol, err := ayarDosyasi(anahtar)
	if err != nil {
		return nil
	}
	veri, err := os.ReadFile(yol)
	if err != nil {
		return nil
	}
	var m map[string]any
	if err := json.Unmarshal(veri, &m); err != nil || m == nil {
		return json.RawMessage(veri)
	}
	s, sifreliVarMi := m["parolaSifreli"].(string)
	if !sifreliVarMi {
		return json.RawMessage(veri)
	}
	delete(m, "parolaSifreli")
	if ham, err := base64.StdEncoding.DecodeString(s); err == nil {
		if p, err := dpapiCoz(ham); err == nil {
			m["parola"] = string(p)
		} else {
			olayLog("eklenti parolası çözülemedi (%s); yeniden girilmeli", anahtar)
		}
	}
	out, err := json.Marshal(m)
	if err != nil {
		return nil
	}
	return out
}

// surecHostDesen: ana bilgisayar adi ya da IPv4; sema/yol/port icermez.
var surecHostDesen = regexp.MustCompile(`^[A-Za-z0-9.-]{1,253}$`)

// ayarBirlestir: web'den gelen ayari dogrular ve kayitli ayarla birlestirir.
// Parola alani gelmezse eski parola korunur. Kullanici adi bossa parola silinir (kimlik dogrulama kapanir).
func ayarBirlestir(anahtar string, veri json.RawMessage) (json.RawMessage, error) {
	var yeni map[string]any
	if err := json.Unmarshal(veri, &yeni); err != nil || yeni == nil {
		return nil, errors.New("geçersiz ayar")
	}
	if p, varMi := yeni["port"]; varMi {
		f, ok := p.(float64)
		if !ok || f < 1 || f > 65535 || f != float64(int(f)) {
			return nil, errors.New("geçersiz port")
		}
	}
	if g, varMi := yeni["indirmeSuresiDk"]; varMi {
		f, ok := g.(float64)
		if !ok || f < 1 || f > 1440 || f != float64(int(f)) {
			return nil, errors.New("geçersiz indirme süresi")
		}
	}
	if g, varMi := yeni["indirmeMaksMb"]; varMi {
		f, ok := g.(float64)
		if !ok || f < 1 || f > 4096 || f != float64(int(f)) {
			return nil, errors.New("geçersiz en büyük dosya boyutu")
		}
	}
	if h, varMi := yeni["host"]; varMi {
		s, ok := h.(string)
		if !ok || (s != "" && !surecHostDesen.MatchString(s)) {
			return nil, errors.New("geçersiz sunucu adresi")
		}
	}
	kullanici, _ := yeni["kullanici"].(string)
	if len(kullanici) > 100 {
		return nil, errors.New("kullanıcı adı çok uzun")
	}
	eski := map[string]any{}
	if ham := kayitliAyar(anahtar); ham != nil {
		_ = json.Unmarshal(ham, &eski)
	}
	if _, varMi := yeni["parola"]; !varMi {
		if p, eskiVarMi := eski["parola"]; eskiVarMi {
			yeni["parola"] = p
		}
	}
	if parola, _ := yeni["parola"].(string); len(parola) > 200 {
		return nil, errors.New("parola çok uzun")
	}
	if kullanici == "" {
		delete(yeni, "parola")
	}
	out, err := json.Marshal(yeni)
	if err != nil || len(out) > surecAyarBoyutLimit*4 {
		return nil, errors.New("geçersiz ayar")
	}
	return out, nil
}

// ayarGoster: web ve sunucuya giden ayar. Parola ham olarak gitmez; yalnizca "parolaVar" bilgisi gider.
func ayarGoster(ham json.RawMessage) json.RawMessage {
	if len(ham) == 0 {
		return nil
	}
	var m map[string]any
	if err := json.Unmarshal(ham, &m); err != nil || m == nil {
		return nil
	}
	_, varMi := m["parola"]
	delete(m, "parola")
	m["parolaVar"] = varMi
	out, err := json.Marshal(m)
	if err != nil {
		return nil
	}
	return out
}

// surecIstegiIsle: sunucudan gelen tek bir surec komutunu uygular ve sonucunu bildirir.
func surecIstegiIsle(ham json.RawMessage) {
	var istek surecIstegi
	if err := json.Unmarshal(ham, &istek); err != nil || istek.Id == "" {
		return
	}
	bitir := func(tamam bool, hata string) {
		surecSonucGonder(istek.Id, istek.Islem, istek.Anahtar, tamam, hata)
	}
	if !anahtarDesen.MatchString(istek.Anahtar) {
		bitir(false, "geçersiz eklenti")
		return
	}
	switch istek.Islem {
	case "baslat":
		if ekSurecCalisiyor(istek.Anahtar) {
			bitir(false, "eklenti zaten çalışıyor")
			return
		}
		if !kuruluVarMi(istek.Anahtar) || !kuruluEtkinMi(istek.Anahtar) {
			bitir(false, "eklenti kurulu ve etkin değil")
			return
		}
		yol, err := kuruluEklentiYolu(istek.Anahtar)
		if err != nil {
			bitir(false, "eklenti dosyası bulunamadı")
			return
		}
		// Surekli calisma her baslatmada PC'de onaylanir (varsayilan: izin yok).
		onay, err := pcOnayi("Eklenti arka planda başlatılsın mı: "+istek.Anahtar,
			"Eklenti bu bilgisayarda arka planda çalışır ve olay bildirebilir. İstediğiniz zaman durdurabilirsiniz.")
		if err != nil || !onay {
			bitir(false, onayHataMetni(err, onay))
			return
		}
		// Protokol 2 (ekOturum) denenir; el sikismasi basarisizsa eski satir JSON surecine duser.
		if err := ekSurecBaslat(istek.Anahtar, yol); err != nil {
			bitir(false, "başlatılamadı")
			return
		}
		eklentiDurumGonder()
		// Kayitli ayar varsa surec hazir olunca iletilir.
		if ayar := kayitliAyar(istek.Anahtar); ayar != nil {
			_ = ekSurecAyar(istek.Anahtar, ayar)
		}
		olayLog("eklenti surec olarak baslatildi: %s", istek.Anahtar)
		bitir(true, "")
	case "durdur":
		if err := ekSurecDurdur(istek.Anahtar); err != nil {
			if errors.Is(err, errSurecYok) {
				bitir(false, "eklenti çalışmıyor")
				return
			}
			bitir(false, "durdurulamadı")
			return
		}
		olayLog("eklenti surec olarak durduruldu: %s", istek.Anahtar)
		bitir(true, "")
	case "tara":
		// Calisan eklentiden guncel surucu listesini ister; yanit olay olarak web'e gider.
		if !ekSurecCalisiyor(istek.Anahtar) {
			bitir(false, "eklenti çalışmıyor")
			return
		}
		ctx, iptal := context.WithTimeout(context.Background(), ekCagriZamanAsimi)
		err := ekSurecTara(ctx, istek.Anahtar, nil, kayitliAyar(istek.Anahtar))
		iptal()
		if err != nil {
			bitir(false, "taranamadı")
			return
		}
		bitir(true, "")
	case "ayar":
		if len(istek.Veri) == 0 || len(istek.Veri) > surecAyarBoyutLimit || !json.Valid(istek.Veri) || istek.Veri[0] != '{' {
			bitir(false, "geçersiz ayar")
			return
		}
		if !kuruluVarMi(istek.Anahtar) {
			bitir(false, "eklenti kurulu değil")
			return
		}
		olayLog("ayar isteği alındı: %s (%d bayt)", istek.Anahtar, len(istek.Veri))
		yeni, err := ayarBirlestir(istek.Anahtar, istek.Veri)
		if err != nil {
			olayLog("ayar reddedildi: %s: %v", istek.Anahtar, err)
			bitir(false, err.Error())
			return
		}
		if err := ayarKaydet(istek.Anahtar, yeni); err != nil {
			bitir(false, "ayar kaydedilemedi")
			return
		}
		if ekSurecCalisiyor(istek.Anahtar) {
			if err := ekSurecAyar(istek.Anahtar, yeni); err != nil {
				bitir(false, "ayar eklentiye iletilemedi")
				return
			}
		}
		eklentiDurumGonder()
		bitir(true, "")
	default:
		bitir(false, "bilinmeyen işlem")
	}
}

// eslesmeKaldirildi, pcOnayi, onayHataMetni: bu dosyada tanimli olanlar baska dosyalarda.
