package main

// Eslesme ve onay: fikoyt.com sayfasi bridge ile eslesmek veya bir izin istemek
// isterse, bu bilgisayardaki kullanici onay ekraninda karar verir.
//
//   Eslesme:  POST /esles (yalnizca https://fikoyt.com Origin'i)
//     -> onay ekrani -> "Izin ver" -> token uretilir, yalnizca SHA-256 ozeti yazilir.
//   Izin:     POST /izin-iste (token gerekli)
//     -> onay ekrani -> "Izin ver" -> ilgili izin acilir (bkz. izinistek.go).
//
// Ayni anda yalnizca bir onay penceresi acik olur. 60 saniyede yanit yoksa reddedilir.

import (
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"encoding/json"
	"errors"
	"html"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

type eslesme struct {
	Site       string `json:"site"`
	TokenOzeti string `json:"tokenOzeti"`
	Tarih      string `json:"tarih"`
}

type onayBilgi struct {
	baslik     string
	aciklama   string
	secenekli  bool     // true: "bu sefer / 1 saat / kalici" secenekleri gosterilir
	ayrinti    []string // onay ekraninda gosterilen madde listesi (ne yapilacak, ne yapilmayacak)
	kapsam     string   // iznin kapsami: hangi islem icin gecerli
	guncelleme bool     // true: bridge guncelleme onayi (Simdi / Her zaman / Daha sonra)
}

var (
	eslesMu      sync.Mutex
	bekleyenID   string
	bekleyenCh   = map[string]chan bool{}
	bekleyenBi   = map[string]onayBilgi{}
	bekleyenSure = map[string]string{} // secenekli onayda secilen sure (bkz. onay_secenek.go)
)

var errMesgul = errors.New("baska bir onay bekleniyor")

// Onay sekmesinin son acilis zamani ve yeniden acma araligi (bkz. bekleyenOnayiYenidenAc).
var sonOnayAcilis time.Time

const onayYenidenAcmaAraligi = 15 * time.Second

func eslesDosyasi() (string, error) {
	klasor, err := veriKlasoru()
	if err != nil {
		return "", err
	}
	return filepath.Join(klasor, "eslesmeler.json"), nil
}

func eslesmeleriOku() []eslesme {
	yol, err := eslesDosyasi()
	if err != nil {
		return nil
	}
	veri, err := os.ReadFile(yol)
	if err != nil {
		return nil
	}
	var liste []eslesme
	_ = json.Unmarshal(veri, &liste)
	return liste
}

func eslesmeleriYaz(liste []eslesme) error {
	yol, err := eslesDosyasi()
	if err != nil {
		return err
	}
	veri, err := json.MarshalIndent(liste, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(yol, veri, 0o600)
}

func tokenOzeti(t string) string {
	s := sha256.Sum256([]byte(t))
	return hex.EncodeToString(s[:])
}

// eslesmisTokenMi: gelen token, kayitli bir eslesmenin ozetiyle eslesiyor mu.
func eslesmisTokenMi(t string) bool {
	if t == "" {
		return false
	}
	ozet := tokenOzeti(t)
	for _, e := range eslesmeleriOku() {
		if subtle.ConstantTimeCompare([]byte(e.TokenOzeti), []byte(ozet)) == 1 {
			return true
		}
	}
	return false
}

func rastgeleHex(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}

// onayBekle: kullaniciya onay ekrani acar ve karari bekler (60 sn).
func onayBekle(baslik, aciklama string) (bool, error) {
	id, err := rastgeleHex(16)
	if err != nil {
		return false, err
	}
	eslesMu.Lock()
	if bekleyenID != "" {
		eslesMu.Unlock()
		bekleyenOnayiYenidenAc()
		return false, errMesgul
	}
	ch := make(chan bool, 1)
	bekleyenID = id
	bekleyenCh[id] = ch
	bekleyenBi[id] = onayBilgi{baslik: baslik, aciklama: aciklama}
	sonOnayAcilis = time.Now()
	eslesMu.Unlock()
	defer onayBitir(id)

	if err := tarayiciAc("http://127.0.0.1:7878/onay/" + id); err != nil {
		return false, err
	}
	select {
	case izin := <-ch:
		return izin, nil
	case <-time.After(60 * time.Second):
		return false, nil
	}
}

func onayBitir(id string) {
	eslesMu.Lock()
	defer eslesMu.Unlock()
	delete(bekleyenCh, id)
	delete(bekleyenBi, id)
	delete(bekleyenSure, id)
	if bekleyenID == id {
		bekleyenID = ""
	}
}

// esleHandler: yalnizca izinli fikoyt.com Origin'inden gelir.
func esleHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "yalnizca POST", http.StatusMethodNotAllowed)
		return
	}
	if r.Header.Get("Origin") != izinliKoken {
		http.Error(w, "yetkisiz kaynak", http.StatusForbidden)
		return
	}
	// Zaten eşleşmiş fikoyt.com: onay ekranı AÇMA, sessizce yeni token üret.
	// Ayarlar her açıldığında ya da web token'ı kaybettiğinde (POST birleştirici her istekte
	// /esles ekliyor olabilir) kullanıcı tekrar "eşleşme izni" sorulmaz. İlk eşleşmede onay zaten verildi.
	if !siteEslesmisMi(izinliKoken) {
		izin, err := onayBekle(
			"fikoyt.com bu bilgisayarla eşleşmek istiyor",
			"İzin verirseniz fikoyt.com bu bilgisayardaki FikoYT Yerel Köprü ile bağlanabilir. Erişim izinlerini istediğiniz zaman Ayarlar'dan yönetebilirsiniz.",
		)
		if errors.Is(err, errMesgul) {
			yazJSON(w, http.StatusTooManyRequests, map[string]any{"hata": "baska bir onay bekleniyor"})
			return
		}
		if err != nil {
			olayLog("onay ekrani acilamadi: %v", err)
			http.Error(w, "onay ekrani acilamadi", http.StatusInternalServerError)
			return
		}
		if !izin {
			yazJSON(w, http.StatusForbidden, map[string]any{"hata": "kullanici izin vermedi"})
			return
		}
	}

	token, err := rastgeleHex(32)
	if err != nil {
		http.Error(w, "sistem hatasi", http.StatusInternalServerError)
		return
	}
	// Ayni site icin tek kayit: varsa TokenOzeti guncellenir (eski token gecersiz olur), yoksa eklenir.
	if err := eslesmeKaydetVeyaGuncelle(izinliKoken, tokenOzeti(token)); err != nil {
		http.Error(w, "eslesme kaydedilemedi", http.StatusInternalServerError)
		return
	}
	olayLog("eslesme olusturuldu/yenilendi: %s", izinliKoken)
	yazJSON(w, http.StatusOK, map[string]any{"token": token})
}

// siteEslesmisMi: verilen site icin daha once onaylanmis bir eslesme var mi.
func siteEslesmisMi(site string) bool {
	for _, e := range eslesmeleriOku() {
		if e.Site == site {
			return true
		}
	}
	return false
}

// eslesmeKaydetVeyaGuncelle: ayni site icin tek kayit tutar. Varsa TokenOzeti'yi gunceller
// (eski token gecersiz olur), yoksa yeni kayit ekler. Boylece eslesmeler.json her /esles
// cagrisinda sismez; ayni site icin tek satir kalir.
func eslesmeKaydetVeyaGuncelle(site, ozet string) error {
	liste := eslesmeleriOku()
	for i := range liste {
		if liste[i].Site == site {
			liste[i].TokenOzeti = ozet
			liste[i].Tarih = time.Now().Format(time.RFC3339)
			return eslesmeleriYaz(liste)
		}
	}
	return eslesmeleriYaz(append(liste, eslesme{
		Site:       site,
		TokenOzeti: ozet,
		Tarih:      time.Now().Format(time.RFC3339),
	}))
}

// onayHandler: GET /onay/<id> onay ekranini gosterir.
func onayHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "yalnizca GET", http.StatusMethodNotAllowed)
		return
	}
	if !yerelIstek(w, r) {
		return
	}
	id := strings.TrimPrefix(r.URL.Path, "/onay/")
	eslesMu.Lock()
	bilgi, bekliyor := bekleyenBi[id]
	eslesMu.Unlock()

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-store")
	if bilgi.secenekli {
		w.Write([]byte(onaySecenekliSayfa(id, bekliyor, bilgi)))
		return
	}
	w.Write([]byte(onaySayfasi(id, bekliyor, bilgi)))
}

type onayKarari struct {
	Id   string `json:"id"`
	Izin bool   `json:"izin"`
	Sure string `json:"sure"` // secenekli onay icin: bu-sefer | saat | kalici
}

// onayKararHandler: ekrandaki dugmeden gelir (yalnizca yerel Origin).
func onayKararHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "yalnizca POST", http.StatusMethodNotAllowed)
		return
	}
	if !yerelIstek(w, r) {
		return
	}
	var k onayKarari
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1024)).Decode(&k); err != nil {
		http.Error(w, "gecersiz istek", http.StatusBadRequest)
		return
	}
	eslesMu.Lock()
	ch, ok := bekleyenCh[k.Id]
	if ok && k.Sure != "" {
		bekleyenSure[k.Id] = k.Sure
	}
	eslesMu.Unlock()
	if !ok {
		yazJSON(w, http.StatusGone, map[string]any{"hata": "istek zaman asimina ugradi"})
		return
	}
	select {
	case ch <- k.Izin:
	default:
	}
	yazJSON(w, http.StatusOK, map[string]any{"ok": true})
}

// onaySayfasi: iki dugmeli klasik onay ekrani.
func onaySayfasi(id string, aktif bool, bilgi onayBilgi) string {
	govde := `<p>` + html.EscapeString(bilgi.aciklama) + `</p>
<div class="dugmeler">
<button onclick="karar(false,'')">Reddet</button>
<button class="onay" onclick="karar(true,'')">İzin ver</button>
</div>`
	if !aktif {
		govde = `<p>Bu istek artık geçerli değil (süresi dolmuş ya da yanıtlanmış).</p>`
	}
	return onaySayfasiTemel(id, bilgi.baslik, govde)
}

// onaySayfasiTemel: ortak sayfa iskeleti ve karar gonderen betik.
func onaySayfasiTemel(id, baslik, govde string) string {
	return `<!doctype html><html lang="tr"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>FikoYT Yerel Köprü - Onay</title>
<style>
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f3f4f6;font-family:"Segoe UI",system-ui,sans-serif;color:#1f2937}
.kart{width:360px;max-width:92vw;padding:28px;border-radius:16px;background:#fff;box-shadow:0 4px 24px rgba(0,0,0,.08);border:1px solid rgba(0,0,0,.04);box-sizing:border-box}
.logo{width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,#2185d0,#1678c2);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:20px;margin-bottom:16px;box-shadow:0 4px 12px rgba(33,133,208,.3)}
h1{font-size:17px;font-weight:700;margin:0 0 10px;line-height:1.4}
p{color:#6b7280;font-size:14px;line-height:1.6;margin:0}
.dugmeler{display:flex;gap:10px;margin-top:22px}
.dugmeler.dikey{flex-direction:column}
button{flex:1;padding:11px;border-radius:10px;border:1px solid #d1d5db;font-size:15px;font-weight:600;cursor:pointer;background:#fff;color:#1f2937}
button:hover{background:#f9fafb}
.onay{background:#2185d0;border-color:#2185d0;color:#fff}
.onay:hover{background:#1678c2}
.ayrinti{margin:14px 0 0;padding:0 0 0 18px;color:#374151;font-size:13px;line-height:1.6}
.kapsam{margin:12px 0 0;padding:10px 12px;border-radius:8px;background:#f3f4f6;color:#374151;font-size:12.5px;line-height:1.5}
button small{display:block;font-weight:400;font-size:12px;color:#6b7280;margin-top:3px}
.onay small{color:#dbeafe}
.ozel{margin-top:8px;padding:10px;border:1px solid #e5e7eb;border-radius:10px}
.ozel label{display:block;font-size:13px;color:#374151;margin-bottom:8px}
.ozel input{display:block;width:100%;margin-top:4px;padding:8px;border:1px solid #d1d5db;border-radius:8px;box-sizing:border-box;font-size:14px}
</style></head><body>
<div class="kart">
<div class="logo">F</div>
<h1>` + html.EscapeString(baslik) + `</h1>
` + govde + `
</div>
<script>
const ID = "` + html.EscapeString(id) + `";
function ozelIzin(){
  var v = document.getElementById('ozelBitis').value;
  if(!v){ alert('Lütfen bir tarih ve saat seçin.'); return; }
  var t = Math.floor(new Date(v).getTime()/1000);
  if(!isFinite(t)){ alert('Geçersiz tarih.'); return; }
  karar(true, 'ozel:' + t);
}
async function karar(izin, sure){
  try { await fetch('/onay/karar', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: ID, izin: izin, sure: sure})}); } catch(e) {}
  document.querySelector('.kart').innerHTML = '<h1>' + (izin ? 'Onaylandı' : 'Reddedildi') + '</h1><p>Bu pencereyi kapatabilirsiniz.</p>';
  setTimeout(function(){ window.close(); }, 1200);
}
</script>
</body></html>`
}
