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"
)

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, eklentinin ait oldugu hesabin sunucu
// soketine iletir. Baska hesabin soketine gitmez.
// (var: testlerde taklibi icin; uretimde asagidaki gercek uygulama kullanilir.)
var eklentiOlayIlet = func(profil, anahtar string, satir []byte) {
	if !json.Valid(satir) || len(satir) > surecOlayBoyutLimit {
		return
	}
	b, hazir := sokProfildeVeHazir(profil)
	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(profil, id, islem, anahtar string, tamam bool, hata string) {
	b, hazir := sokProfildeVeHazir(profil)
	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) + `]`)
}

// ayarDosyasiProfil: verilen profilin bir eklenti icin ayar dosyasi.
func ayarDosyasiProfil(profil, anahtar string) (string, error) {
	klasor, err := profilKlasorP(profil)
	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 verilen profilin dosyasina yazar. Parola DPAPI ile sifrelenir; dosyada duz metin parola bulunmaz.
func ayarKaydet(profil, anahtar string, veri json.RawMessage) error {
	yol, err := ayarDosyasiProfil(profil, 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)
}

// kayitliAyarProfil: verilen profilin son kaydedilen ayari; yoksa nil.
func kayitliAyarProfil(profil, anahtar string) json.RawMessage {
	yol, err := ayarDosyasiProfil(profil, 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(profil, 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")
		}
	}
	for _, s := range []struct {
		ad       string
		min, max float64
	}{{"kucukMaksDeneme", 1, 200}, {"kucukSureSn", 1, 30}, {"kucukToplamKb", 50, 500}} {
		if v, varMi := yeni[s.ad]; varMi {
			f, ok := v.(float64)
			if !ok || f < s.min || f > s.max || f != float64(int(f)) {
				return nil, errors.New("gecersiz " + s.ad)
			}
		}
	}
	kullanici, _ := yeni["kullanici"].(string)
	if len(kullanici) > 100 {
		return nil, errors.New("kullanıcı adı çok uzun")
	}
	eski := map[string]any{}
	if ham := kayitliAyarProfil(profil, 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(profil string, ham json.RawMessage) {
	var istek surecIstegi
	if err := json.Unmarshal(ham, &istek); err != nil || istek.Id == "" {
		return
	}
	bitir := func(tamam bool, hata string) {
		surecSonucGonder(profil, 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 ekSurecCalisiyorProfil(profil, istek.Anahtar) {
			bitir(false, "eklenti zaten çalışıyor")
			return
		}
		if !kuruluVarMiProfil(profil, istek.Anahtar) || !kuruluEtkinMiProfil(profil, istek.Anahtar) {
			bitir(false, "eklenti kurulu ve etkin değil")
			return
		}
		yol, err := kuruluEklentiYoluProfil(profil, istek.Anahtar)
		if err != nil {
			bitir(false, "eklenti dosyası bulunamadı")
			return
		}
		// Surekli calisma her baslatmada PC'de onaylanir (varsayilan: izin yok).
		onay, err := pcOnayi(hesapBaslik(profil, "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; ile baslatilir (yalnizca protokol 2).
		if err := ekSurecBaslatProfil(profil, istek.Anahtar, yol); err != nil {
			bitir(false, "başlatılamadı")
			return
		}
		eklentiDurumGonderProfil(profil)
		// Kayitli ayar varsa surec hazir olunca iletilir.
		if ayar := kayitliAyarProfil(profil, istek.Anahtar); ayar != nil {
			_ = ekSurecAyarProfil(profil, istek.Anahtar, ayar)
		}
		olayLog("eklenti surec olarak baslatildi: %s", istek.Anahtar)
		bitir(true, "")
	case "durdur":
		if err := ekEklentiDurdurProfil(profil, istek.Anahtar); err != nil {
			if errors.Is(err, errEklentiYok) {
				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 !ekSurecCalisiyorProfil(profil, istek.Anahtar) {
			bitir(false, "eklenti çalışmıyor")
			return
		}
		ctx, iptal := context.WithTimeout(context.Background(), ekCagriZamanAsimi)
		err := ekSurecTaraProfil(ctx, profil, istek.Anahtar, nil, kayitliAyarProfil(profil, 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 !kuruluVarMiProfil(profil, 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(profil, istek.Anahtar, istek.Veri)
		if err != nil {
			olayLog("ayar reddedildi: %s: %v", istek.Anahtar, err)
			bitir(false, err.Error())
			return
		}
		if err := ayarKaydet(profil, istek.Anahtar, yeni); err != nil {
			bitir(false, "ayar kaydedilemedi")
			return
		}
		if ekSurecCalisiyorProfil(profil, istek.Anahtar) {
			if err := ekSurecAyarProfil(profil, istek.Anahtar, yeni); err != nil {
				bitir(false, "ayar eklentiye iletilemedi")
				return
			}
		}
		eklentiDurumGonderProfil(profil)
		bitir(true, "")
	default:
		bitir(false, "bilinmeyen işlem")
	}
}

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