package main

// Yerel ayar sayfasindan eklenti magazasi. Katalog fikoyt.com'dan okunur (imzali manifestler).
// Kurulum ve guncelleme, web'den kurulumla ayni yolu (eklentiKurOnayli) kullanir: PC'de onay ister,
// paketi indirir ve imzasini dogrular. Istemciden gelen sha256/imza kullanilmaz; sunucu katalogdan alinir.

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"sort"
	"strings"
	"time"
)

const magazaKatalogAdresi = "https://fikoyt.com:9000/fapiv1/fikoyt.com.FikoYT.sistem.bridge.eklenti.katalog"

type magazaManifest struct {
	Anahtar string       `json:"anahtar"`
	Surum   string       `json:"surum"`
	Sha256  string       `json:"sha256"`
	Imza    string       `json:"imza"`
	Bicim   string       `json:"bicim"`
	Acilis  bool         `json:"acilis"`
	Izinler []izinTanimi `json:"izinler,omitempty"`
}

type magazaAciklama struct {
	Ham string `json:"ham"`
}

type magazaKatalog struct {
	Durum struct {
		Hata bool `json:"hata"`
	} `json:"durum"`
	Sonuc struct {
		Eklentiler  []magazaManifest          `json:"eklentiler"`
		Aciklamalar map[string]magazaAciklama `json:"aciklamalar"`
	} `json:"sonuc"`
}

type magazaSatir struct {
	Anahtar         string       `json:"anahtar"`
	Ad              string       `json:"ad"`
	Aciklama        string       `json:"aciklama"`
	Surum           string       `json:"surum"`
	Kurulu          bool         `json:"kurulu"`
	KuruluSurum     string       `json:"kuruluSurum"`
	Guncellenebilir bool         `json:"guncellenebilir"`
	Acilis          bool         `json:"acilis"`
	Izinler         []izinTanimi `json:"izinler,omitempty"`
}

type magazaKurIstegi struct {
	Profil  string `json:"profil"`
	Anahtar string `json:"anahtar"`
	Surum   string `json:"surum"`
}

// magazaKatalogGetir: katalogu sunucudan ceker. Yonlendirmeler de yalnizca izinli adrese gidebilir.
func magazaKatalogGetir() (magazaKatalog, error) {
	var k magazaKatalog
	istemci := &http.Client{
		Timeout: 15 * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return izinliIndirmeAdresi(req.URL.String())
		},
	}
	istek, err := http.NewRequest(http.MethodPost, magazaKatalogAdresi, strings.NewReader("{}"))
	if err != nil {
		return k, err
	}
	istek.Header.Set("Content-Type", "application/json")
	yanit, err := istemci.Do(istek)
	if err != nil {
		return k, err
	}
	defer yanit.Body.Close()
	if yanit.StatusCode != http.StatusOK {
		return k, errors.New("katalog alinamadi")
	}
	veri, err := io.ReadAll(io.LimitReader(yanit.Body, 4<<20))
	if err != nil {
		return k, err
	}
	if err := json.Unmarshal(veri, &k); err != nil {
		return k, err
	}
	if k.Durum.Hata {
		return k, errors.New("katalog hatasi")
	}
	return k, nil
}

// magazaEnSonlar: her eklenti anahtari icin katalogdaki en yuksek surumu dondurur.
func magazaEnSonlar(liste []magazaManifest) map[string]magazaManifest {
	en := map[string]magazaManifest{}
	for _, m := range liste {
		if !anahtarDesen.MatchString(m.Anahtar) || !surumDesen.MatchString(m.Surum) {
			continue
		}
		mevcut, varMi := en[m.Anahtar]
		if !varMi || surumKarsilastir(m.Surum, mevcut.Surum) > 0 {
			en[m.Anahtar] = m
		}
	}
	return en
}

// magazaListeHandler: GET /ayarlar/magaza-listesi?profil=... (yalnizca yerel arayuz).
func magazaListeHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "yalnizca GET", http.StatusMethodNotAllowed)
		return
	}
	if !yerelIstek(w, r) {
		return
	}
	if !herhangiOturumVar() {
		http.Error(w, "oturum yok", http.StatusUnauthorized)
		return
	}
	profil := r.URL.Query().Get("profil")
	if !profilAnahtarDesen.MatchString(profil) || !etkinMi(profil) {
		http.Error(w, "hesap etkin degil", http.StatusConflict)
		return
	}
	kat, err := magazaKatalogGetir()
	if err != nil {
		olayLog("magaza: katalog alinamadi: %v", err)
		http.Error(w, "katalog alinamadi", http.StatusBadGateway)
		return
	}
	kurulular := map[string]kuruluEklenti{}
	kuruluMu.Lock()
	for _, k := range kuruluListeAl(profil) {
		kurulular[k.Anahtar] = k
	}
	kuruluMu.Unlock()
	en := magazaEnSonlar(kat.Sonuc.Eklentiler)
	satirlar := []magazaSatir{}
	for anahtar, m := range en {
		s := magazaSatir{Anahtar: anahtar, Ad: anahtar, Surum: m.Surum, Acilis: m.Acilis, Izinler: m.Izinler}
		if a, ok := kat.Sonuc.Aciklamalar[anahtar]; ok {
			var meta struct {
				Ad       string `json:"ad"`
				Aciklama string `json:"aciklama"`
			}
			if json.Unmarshal([]byte(a.Ham), &meta) == nil {
				if meta.Ad != "" {
					s.Ad = meta.Ad
				}
				s.Aciklama = meta.Aciklama
			}
		}
		if k, ok := kurulular[anahtar]; ok {
			s.Kurulu = true
			s.KuruluSurum = k.Surum
			s.Guncellenebilir = surumKarsilastir(m.Surum, k.Surum) > 0
		}
		satirlar = append(satirlar, s)
	}
	sort.Slice(satirlar, func(i, j int) bool { return satirlar[i].Anahtar < satirlar[j].Anahtar })
	yazJSON(w, http.StatusOK, satirlar)
}

// magazaKurHandler: POST /ayarlar/magaza-kur. Kurulum ve guncelleme PC'de onay ister.
func magazaKurHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "yalnizca POST", http.StatusMethodNotAllowed)
		return
	}
	if !yerelIstek(w, r) || !csrfDogrula(w, r) {
		return
	}
	var istek magazaKurIstegi
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1024)).Decode(&istek); err != nil {
		http.Error(w, "gecersiz istek", http.StatusBadRequest)
		return
	}
	if !profilAnahtarDesen.MatchString(istek.Profil) || !anahtarDesen.MatchString(istek.Anahtar) || !surumDesen.MatchString(istek.Surum) {
		http.Error(w, "gecersiz istek", http.StatusBadRequest)
		return
	}
	if !etkinMi(istek.Profil) {
		http.Error(w, "hesap etkin degil", http.StatusConflict)
		return
	}
	kat, err := magazaKatalogGetir()
	if err != nil {
		olayLog("magaza: katalog alinamadi: %v", err)
		http.Error(w, "katalog alinamadi", http.StatusBadGateway)
		return
	}
	var secilen *magazaManifest
	for i := range kat.Sonuc.Eklentiler {
		m := &kat.Sonuc.Eklentiler[i]
		if m.Anahtar == istek.Anahtar && m.Surum == istek.Surum {
			secilen = m
			break
		}
	}
	if secilen == nil {
		http.Error(w, "eklenti surumu bulunamadi", http.StatusNotFound)
		return
	}
	p := paketBilgisi{
		Anahtar: secilen.Anahtar, Surum: secilen.Surum, Sha256: secilen.Sha256, Imza: secilen.Imza,
		Bicim: secilen.Bicim, Acilis: secilen.Acilis, Izinler: secilen.Izinler,
	}
	url := "https://fikoyt.com:9000/eklenti/" + p.Anahtar + "/" + p.Surum + "/plugin.exe"
	kod, err := eklentiKurOnayli(istek.Profil, p, url)
	if kod == http.StatusOK {
		yazJSON(w, http.StatusOK, map[string]any{"ok": true})
		return
	}
	if errors.Is(err, errKullaniciRed) {
		http.Error(w, "kullanici kurulumu reddetti", http.StatusForbidden)
		return
	}
	if errors.Is(err, errMesgul) {
		http.Error(w, "baska bir onay bekleniyor", http.StatusTooManyRequests)
		return
	}
	if kod < 400 {
		kod = http.StatusInternalServerError
	}
	mesaj := "kurulum basarisiz"
	if err != nil {
		mesaj = err.Error()
	}
	http.Error(w, mesaj, kod)
}
