// Package lisans_kontrol — FikoCAD canlı lisans yönetimi (kill-switch).
//
// İstemci (fikocad.exe) açılışta /lisans-ws ham WebSocket'ine bağlanır,
// {"tip":"merhaba","seri":...,"musteri":...,"bilgisayar":...,"makine":[...]}
// gönderir. Yönetim paneli (/lisans-kontrol) bağlı TÜM fikocad'leri tabloda
// gösterir; Engelle/İzin ver düğmesi kararı ECDSA P-256 ile İMZALAYIP istemciye
// yollar. İstemci gömülü açık anahtarla imzayı doğrular → sahte sunucu/MITM
// karar enjekte edemez (politika .pol dosyasıyla aynı güven modeli).
//
// İmzalanan kanonik gövde (istemci canliKararKanonik ile AYNI sıra):
//
//	engelle=<1|0>
//	seri=<seri>
//	mesaj=<mesaj>
//	zaman=<unix saniye>
//
// Kararlar sistem/lisans_kararlar.json'a yazılır → sunucu yeniden başlasa da
// yeniden bağlanan istemci son kararını hello'da anında alır.
package lisans_kontrol

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/gofiber/fiber/v3"
	"github.com/gofiber/fiber/v3/middleware/adaptor"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
)

// ── Yapılandırma (ortam değişkenleri) ────────────────────────────────────────
// LISANS_OZEL_PEM   : ECDSA P-256 özel anahtar PEM (lisans anahtarıyla aynı çift!)
// LISANS_KARAR_DOSYA: karar kalıcılığı (varsayılan sistem/lisans_kararlar.json)
// LISANS_ADMIN_TOKEN: doluysa panel/API bu token ister (?token= veya X-Token)

type Istemci struct {
	Kimlik    string    `json:"kimlik"`
	Seri      string    `json:"seri"`
	Musteri   string    `json:"musteri"`
	Bilgisayar string   `json:"bilgisayar"`
	Surum     string    `json:"surum"`
	Pid       int64     `json:"pid"`
	Makine    []string  `json:"makine"`
	Ip        string    `json:"ip"`
	Baglanma  time.Time `json:"baglanma"`

	conn *websocket.Conn
	yaz  sync.Mutex
}

type Karar struct {
	Seri    string `json:"seri"`
	Engelli bool   `json:"engelli"`
	Mesaj   string `json:"mesaj"`
	Zaman   int64  `json:"zaman"`
	Imza    string `json:"imza"`
}

var (
	mu        sync.RWMutex
	istemciler = map[string]*Istemci{} // kimlik → istemci
	kararlar   = map[string]Karar{}    // seri → son karar
	ozelAnahtar *ecdsa.PrivateKey
	kararDosya = "sistem/lisans_kararlar.json"
	adminToken string
)

// ── Özel anahtar yükleme ─────────────────────────────────────────────────────

func anahtarYukle() {
	yol := os.Getenv("LISANS_OZEL_PEM")
	if yol == "" {
		yol = "sistem/lisans_ozel.pem"
	}
	if d := os.Getenv("LISANS_KARAR_DOSYA"); d != "" {
		kararDosya = d
	}
	adminToken = os.Getenv("LISANS_ADMIN_TOKEN")

	veri, err := os.ReadFile(yol)
	if err != nil {
		log.Printf("[LISANS-KONTROL] UYARI: özel anahtar okunamadı (%s): %v — karar imzalama KAPALI", yol, err)
		return
	}
	blok, _ := pem.Decode(veri)
	if blok == nil {
		log.Printf("[LISANS-KONTROL] UYARI: %s PEM değil — karar imzalama KAPALI", yol)
		return
	}
	if k, err := x509.ParseECPrivateKey(blok.Bytes); err == nil {
		ozelAnahtar = k
	} else if genel, err2 := x509.ParsePKCS8PrivateKey(blok.Bytes); err2 == nil {
		if k2, ok := genel.(*ecdsa.PrivateKey); ok {
			ozelAnahtar = k2
		}
	}
	if ozelAnahtar != nil && ozelAnahtar.Curve != elliptic.P256() {
		log.Printf("[LISANS-KONTROL] UYARI: anahtar P-256 değil — karar imzalama KAPALI")
		ozelAnahtar = nil
		return
	}
	if ozelAnahtar != nil {
		log.Printf("[LISANS-KONTROL] Özel anahtar yüklendi: %s", yol)
	}
}

// ── İmzalama: kanonik gövde → SHA256 → ECDSA → ham r||s base64 ──────────────

func kanonikKur(k Karar) string {
	e := 0
	if k.Engelli {
		e = 1
	}
	return fmt.Sprintf("engelle=%d\nseri=%s\nmesaj=%s\nzaman=%d\n", e, k.Seri, k.Mesaj, k.Zaman)
}

// DER (ASN.1) imzasını ham r||s (64 bayt) biçimine çevirir — istemcinin
// kripto::imzaDogrula'sı ham r||s bekler.
func derToRaw(der []byte) ([]byte, error) {
	if len(der) < 8 || der[0] != 0x30 {
		return nil, errors.New("bozuk DER imzası")
	}
	idx := 2
	if der[1]&0x80 != 0 {
		n := int(der[1] & 0x7f)
		if n == 0 || idx+n > len(der) {
			return nil, errors.New("bozuk DER uzunluk")
		}
		idx += n
	}
	okunanTamsayi := func() ([]byte, error) {
		if idx+2 > len(der) || der[idx] != 0x02 {
			return nil, errors.New("bozuk DER tamsayı")
		}
		uz := int(der[idx+1])
		if idx+2+uz > len(der) {
			return nil, errors.New("bozuk DER tamsayı uzunluğu")
		}
		v := der[idx+2 : idx+2+uz]
		idx += 2 + uz
		return v, nil
	}
	r, err := okunanTamsayi()
	if err != nil {
		return nil, err
	}
	s, err := okunanTamsayi()
	if err != nil {
		return nil, err
	}
	raw := make([]byte, 64)
	for i, v := range [][]byte{r, s} {
		if len(v) > 32 {
			v = v[len(v)-32:] // öndeki sıfırları at
		}
		copy(raw[i*32+(32-len(v)):], v) // sağa hizala
	}
	return raw, nil
}

func kararImzala(k *Karar) error {
	if ozelAnahtar == nil {
		return errors.New("özel anahtar yüklenmemiş (LISANS_OZEL_PEM)")
	}
	h := sha256.Sum256([]byte(kanonikKur(*k)))
	der, err := ecdsa.SignASN1(rand.Reader, ozelAnahtar, h[:])
	if err != nil {
		return err
	}
	raw, err := derToRaw(der)
	if err != nil {
		return err
	}
	k.Imza = base64.StdEncoding.EncodeToString(raw)
	return nil
}

// ── Karar kalıcılığı ─────────────────────────────────────────────────────────

func kararlariKaydet() {
	mu.RLock()
	veri, err := json.MarshalIndent(kararlar, "", "  ")
	mu.RUnlock()
	if err != nil {
		return
	}
	if err := os.MkdirAll(filepath.Dir(kararDosya), 0o755); err != nil {
		return
	}
	_ = os.WriteFile(kararDosya, veri, 0o644)
}

func kararlariYukle() {
	veri, err := os.ReadFile(kararDosya)
	if err != nil {
		return
	}
	var yuklenen map[string]Karar
	if json.Unmarshal(veri, &yuklenen) == nil {
		mu.Lock()
		kararlar = yuklenen
		mu.Unlock()
		log.Printf("[LISANS-KONTROL] %d karar diskten yüklendi", len(yuklenen))
	}
}

// ── WS uç noktası ────────────────────────────────────────────────────────────

var yukseltici = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool { return true }, // CORS serbest (imza korur)
}

func wsUcu(w http.ResponseWriter, r *http.Request) {
	conn, err := yukseltici.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	ip, _, _ := net.SplitHostPort(r.RemoteAddr)
	ist := &Istemci{
		Kimlik:   uuid.New().String(),
		Ip:       ip,
		Baglanma: time.Now(),
		conn:     conn,
	}

	defer func() {
		mu.Lock()
		delete(istemciler, ist.Kimlik)
		mu.Unlock()
		_ = conn.Close()
		log.Printf("[LISANS-KONTROL] kopdu: %s (%s)", ist.Seri, ist.Bilgisayar)
	}()

	for {
		_, mesaj, err := conn.ReadMessage()
		if err != nil {
			return
		}
		var govde map[string]interface{}
		if json.Unmarshal(mesaj, &govde) != nil {
			continue
		}
		tip, _ := govde["tip"].(string)
		switch tip {
		case "merhaba":
			ist.Seri, _ = govde["seri"].(string)
			ist.Musteri, _ = govde["musteri"].(string)
			ist.Bilgisayar, _ = govde["bilgisayar"].(string)
			ist.Surum, _ = govde["surum"].(string)
			if p, ok := govde["pid"].(float64); ok {
				ist.Pid = int64(p)
			}
			if dizi, ok := govde["makine"].([]interface{}); ok {
				ist.Makine = ist.Makine[:0]
				for _, m := range dizi {
					if s, ok := m.(string); ok {
						ist.Makine = append(ist.Makine, s)
					}
				}
			}
			mu.Lock()
			istemciler[ist.Kimlik] = ist
			mevcut, varMi := kararlar[ist.Seri]
			mu.Unlock()
			log.Printf("[LISANS-KONTROL] baglandi: %s | %s | %s | pid=%d", ist.Seri, ist.Musteri, ist.Bilgisayar, ist.Pid)
			if varMi {
				ist.gonder(mevcut) // son kararı anında ilet
			}
		case "ping":
			ist.gonderJSON(map[string]string{"tip": "pong"})
		}
	}
}

func (i *Istemci) gonderJSON(v interface{}) {
	veri, err := json.Marshal(v)
	if err != nil {
		return
	}
	i.yaz.Lock()
	defer i.yaz.Unlock()
	_ = i.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
	_ = i.conn.WriteMessage(websocket.TextMessage, veri)
}

func (i *Istemci) gonder(k Karar) { i.gonderJSON(k) }

// ── Panel (tek sayfa) ────────────────────────────────────────────────────────

const panelHTML = `<!doctype html>
<html lang="tr"><head><meta charset="utf-8">
<title>FikoCAD Canlı Lisans Kontrolü</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body{font-family:system-ui,sans-serif;background:#0e1116;color:#dbe4ee;margin:24px}
h1{font-size:20px} table{border-collapse:collapse;width:100%;margin-top:12px}
th,td{padding:8px 10px;border-bottom:1px solid #232b36;text-align:left;font-size:14px}
th{color:#8aa0b8;font-weight:600} tr.engelli td{background:#3a1418}
button{border:0;border-radius:6px;padding:6px 12px;margin:2px;cursor:pointer;font-size:13px}
.b-engel{background:#c0392b;color:#fff}.b-izin{background:#27ae60;color:#fff}
input{background:#161c25;border:1px solid #2a3442;color:#dbe4ee;border-radius:6px;padding:6px;width:220px}
.kucuk{color:#8aa0b8;font-size:12px}.durum{margin-left:auto;font-size:13px;color:#8aa0b8}
</style></head><body>
<h1>🔒 FikoCAD Canlı Lisans Kontrolü <span class="durum" id="durum"></span></h1>
<div class="kucuk">Bağlı tüm fikocad süreçleri. Kararlar ECDSA ile imzalanır — istemci doğrulamadan uygulamaz.</div>
<table><thead><tr><th>Seri</th><th>Müşteri</th><th>Bilgisayar</th><th>Sürüm</th><th>PID</th><th>IP</th><th>Bağlanma</th><th>Durum</th><th>İşlem</th></tr></thead>
<tbody id="govde"></tbody></table>
<script>
async function tazele(){
 try{
  const r=await fetch('/lisans-kontrol/api/istemciler'+location.search);
  const liste=await r.json();
  document.getElementById('durum').textContent=liste.length+' bağlı';
  const g=document.getElementById('govde');g.innerHTML='';
  for(const i of liste){
   const tr=document.createElement('tr');
   if(i.engelli)tr.className='engelli';
   tr.innerHTML='<td>'+esc(i.seri||'(lisanssız)')+'</td><td>'+esc(i.musteri||'-')+'</td>'+
    '<td>'+esc(i.bilgisayar||'-')+'</td><td>'+esc(i.surum||'-')+'</td><td>'+i.pid+'</td>'+
    '<td>'+esc(i.ip)+'</td><td>'+new Date(i.baglanma).toLocaleString('tr')+'</td>'+
    '<td>'+(i.engelli?'⛔ ENGELLİ'+(i.mesaj?'<div class=kucuk>'+esc(i.mesaj)+'</div>':''):'✅ izinli')+'</td>';
   const td=document.createElement('td');
   const be=document.createElement('button');be.className='b-engel';be.textContent='Engelle';
   be.onclick=()=>kararVer(i,true);
   const bz=document.createElement('button');bz.className='b-izin';bz.textContent='İzin ver';
   bz.onclick=()=>kararVer(i,false);
   td.append(be,bz);tr.append(td);g.append(tr);
  }
 }catch(e){document.getElementById('durum').textContent='sunucuya erişilemiyor'}
 setTimeout(tazele,2000);
}
async function kararVer(i,engelli){
 const mesaj=prompt('Kullanıcıya gösterilecek mesaj (opsiyonel):','')||'';
 await fetch('/lisans-kontrol/api/karar'+location.search,{method:'POST',
  headers:{'Content-Type':'application/json'},
  body:JSON.stringify({kimlik:i.kimlik,engelli,mesaj})});
 tazele();
}
function esc(s){const d=document.createElement('div');d.textContent=String(s);return d.innerHTML}
tazele();
</script></body></html>`

// ── Fiber kurulumu ───────────────────────────────────────────────────────────

func tokenTamam(c fiber.Ctx) bool {
	if adminToken == "" {
		return true
	}
	if c.Query("token") == adminToken {
		return true
	}
	if c.Get("X-Token") == adminToken {
		return true
	}
	return false
}

// Ayarla — main.go'dan çağrılır: lisans_kontrol.Ayarla(app)
func Ayarla(app *fiber.App) {
	anahtarYukle()
	kararlariYukle()

	// Ham WebSocket ucu (fikocad WinHTTP istemcisi buraya bağlanır).
	app.Get("/lisans-ws", adaptor.HTTPHandler(http.HandlerFunc(wsUcu)))

	// Yönetim paneli.
	app.Get("/lisans-kontrol", func(c fiber.Ctx) error {
		c.Set("Content-Type", "text/html; charset=utf-8")
		return c.SendString(panelHTML)
	})

	g := app.Group("/lisans-kontrol/api")

	// Bağlı istemciler (+ o serinin güncel kararı).
	g.Get("/istemciler", func(c fiber.Ctx) error {
		if !tokenTamam(c) {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"hata": "token"})
		}
		mu.RLock()
		liste := make([]fiber.Map, 0, len(istemciler))
		for _, i := range istemciler {
			k, varMi := kararlar[i.Seri]
			engelli := varMi && k.Engelli
			mesaj := ""
			if varMi {
				mesaj = k.Mesaj
			}
			liste = append(liste, fiber.Map{
				"kimlik": i.Kimlik, "seri": i.Seri, "musteri": i.Musteri,
				"bilgisayar": i.Bilgisayar, "surum": i.Surum, "pid": i.Pid,
				"makine": i.Makine, "ip": i.Ip, "baglanma": i.Baglanma,
				"engelli": engelli, "mesaj": mesaj,
			})
		}
		mu.RUnlock()
		return c.JSON(liste)
	})

	// Karar ver: {kimlik, engelli, mesaj} → imzala + kaydet + WS ile yolla.
	g.Post("/karar", func(c fiber.Ctx) error {
		if !tokenTamam(c) {
			return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"hata": "token"})
		}
		var govde struct {
			Kimlik  string `json:"kimlik"`
			Engelli bool   `json:"engelli"`
			Mesaj   string `json:"mesaj"`
		}
		if err := c.Bind().Body(&govde); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"hata": "gövde"})
		}
		mu.RLock()
		ist, varMi := istemciler[govde.Kimlik]
		mu.RUnlock()
		if !varMi {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"hata": "istemci bağlı değil"})
		}
		k := Karar{Seri: ist.Seri, Engelli: govde.Engelli, Mesaj: govde.Mesaj, Zaman: time.Now().Unix()}
		if err := kararImzala(&k); err != nil {
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"hata": err.Error()})
		}
		mu.Lock()
		kararlar[k.Seri] = k
		mu.Unlock()
		kararlariKaydet()
		ist.gonder(k)
		log.Printf("[LISANS-KONTROL] karar: %s → engelli=%v (%s)", k.Seri, k.Engelli, k.Mesaj)
		return c.JSON(fiber.Map{"tamam": true})
	})

	log.Printf("[LISANS-KONTROL] hazır: /lisans-ws + /lisans-kontrol")
}
