﻿#include "lisans_canli.hpp"

#include <atomic>
#include <algorithm>
#include <cctype>
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <ctime>
#include <map>
#include <mutex>
#include <thread>

#include "ayar_kalici.hpp"          // ayarOku/ayarYaz (URL)
#include "fiko/cekirdek/lisans_uzak.hpp"  // canliKararCoz (imzalı karar doğrulama)
#include "fiko/cekirdek/makine_kimlik.hpp"
#include "fiko/cekirdek/surum.hpp"  // SURUM_METIN
#include "fiko/core/gunluk.hpp"
#include "imgui.h"
#include "lisans_yukle.hpp"  // lisansAcikAnahtarHex, lisansOturum

#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <winhttp.h>

namespace fiko {

namespace {

// Karakter sabitleri (kaçışsız): JSON üretimi/çözümlemesi ASCII kodlarıyla.
constexpr char NL = static_cast<char>(10);
constexpr char TAB = static_cast<char>(9);
constexpr char TERS = static_cast<char>(92);  // JSON kaçış işareti
constexpr char TIRNAK = static_cast<char>(34);

constexpr const char* CANLI_URL_AYAR = "lisans_canli_url";
constexpr DWORD YENIDEN_BAGLANMA_MIN_MS = 5000;   // kopunca ilk deneme
constexpr DWORD YENIDEN_BAGLANMA_MAKS_MS = 60000; // tavana büyür
constexpr DWORD PING_ARALIGI_SN = 30;             // NAT canlı tutma

// ── Durum ────────────────────────────────────────────────────────────────────
enum class KanalDurum : u8 { Kapali = 0, Baglaniyor, Bagli, Kopuk };

std::mutex g_mtx;
std::map<std::string, CanliKarar> g_kararlar;  // seri → son imzalı karar
KanalDurum g_durum = KanalDurum::Kapali;
std::string g_hata;         // son bağlantı hatası
i64 g_sonKararZaman = 0;    // son kabul edilen kararın zamanı
int g_kararSayisi = 0;      // bu oturumda kabul edilen karar sayısı
std::string g_sunucuAdi;    // bağlı host (özet için)

// Kapanış güvenliği: iş parçacığı detached, statik nesneler (g_mtx, g_hata,
// g_durum) program kapanırında yok edilir. Thread durmazsa yarış durumu
// oluşur → 0xC0000409. Ölçüldü: "Eklenti kaldırıldı" logundan sonra fast-fail.
std::atomic<bool> g_durduruldu{false};
std::mutex g_durmus_mtx;
std::condition_variable g_durmus_cv;
bool g_durdu = true;

// "Bağlantı ZORUNLU" kilidi için: izleme ne zaman başladı + en son ne zaman
// gerçekten BAĞLI idik (0 = hiç bağlanmadık). uzakCanliZorunluIhlalMi() bunları
// kullanır — g_mtx ile korunur (diğer durum alanlarıyla aynı kilit).
i64 g_izlemeBaslangic = 0;
i64 g_sonBagliZaman = 0;

std::atomic<bool> g_isParcacigiSuruyor{false};
std::atomic<bool> g_taze{false};       // yeni karar → pompa lisansı yeniden yükler
std::atomic<bool> g_yenidenBaglanIste{false};  // "Şimdi bağlan" düğmesi

std::string urlOku() {
  std::string url = ayarOku(CANLI_URL_AYAR, "");
  std::string kirp;
  for (char c : url)
    if (!std::isspace((unsigned char)c)) kirp.push_back(c);
  if (kirp.empty())
    return "https://fikoyt.com:9000/isans.socket.io/";
  return kirp;
}

// ── URL: [http://|https://]host[:port]/yol ────────────────────────────────────
// Yeni format: Socket.IO v3 endpoint'i (engine.io v3). Örn:
// https://fikoyt.com/isans.socket.io/   (eski wss://.../lisans-ws formatı artık yok)
struct UrlParca {
  bool https = true;
  u16 port = 443;
  std::string host, yol;
};

bool urlCoz(const std::string& url, UrlParca& u) {
  std::string kalan = url;
  for (char& c : kalan)
    if (c == static_cast<char>(92)) c = '/';  // ters bölü → eğik (yanlışlıkla)
  if (kalan.rfind("https://", 0) == 0) {
    u.https = true;
    kalan.erase(0, 8);
  } else if (kalan.rfind("http://", 0) == 0) {
    u.https = false;
    kalan.erase(0, 7);
  }
  if (kalan.empty()) return false;
  std::string yetki = kalan, yol = "/";
  if (const std::size_t egik = kalan.find('/'); egik != std::string::npos) {
    yetki = kalan.substr(0, egik);
    yol = kalan.substr(egik);
  }
  if (yetki.empty()) return false;
  std::string host = yetki;
  if (!yetki.empty() && yetki.front() == '[') {  // [IPv6]:port
    const std::size_t kapa = yetki.find(']');
    if (kapa == std::string::npos) return false;
    host = yetki.substr(1, kapa - 1);
    if (kapa + 1 < yetki.size() && yetki[kapa + 1] == ':')
      u.port = (u16)std::atoi(yetki.c_str() + kapa + 2);
  } else if (const std::size_t iki = yetki.rfind(':'); iki != std::string::npos) {
    host = yetki.substr(0, iki);
    u.port = (u16)std::atoi(yetki.c_str() + iki + 1);
  }
  if (host.empty() || u.port == 0) return false;
  u.host = host;
  u.yol = yol;
  return true;
}

// ── JSON üretimi (değerler kaçışlanır — kaynak içinde kaçış dizisi YOK) ──────
void jsonDizgeEkle(std::string& j, const char* anahtar, const std::string& deger) {
  j += TIRNAK;
  j += anahtar;
  j += TIRNAK;
  j += ':';
  j += TIRNAK;
  for (char c : deger) {
    if (c == TIRNAK || c == TERS) {
      j += TERS;  // kaçış işareti
      j += c;     // ardından kendisi (" veya \)
    } else if (c == NL) {
      j += TERS;
      j += 'n';
    } else if (c == TAB) {
      j += TERS;
      j += 't';
    } else if ((unsigned char)c < 0x20) {
      // diğer kontrol karakterleri → boşluk (mesaj/bilgisayar adı temizliği)
      j += ' ';
    } else {
      j += c;
    }
  }
  j += TIRNAK;
}

void jsonSayiEkle(std::string& j, const char* anahtar, long long deger) {
  j += TIRNAK;
  j += anahtar;
  j += TIRNAK;
  j += ':';
  j += std::to_string(deger);
}

// Alt nesne/dizi açıcılar: önceki alanla araya virgül koyar (JSON geçerliliği).
void jsonNesneAc(std::string& j, const char* anahtar) {
  j += ',';
  j += TIRNAK;
  j += anahtar;
  j += TIRNAK;
  j += ':';
}

void jsonDiziAc(std::string& j, const char* anahtar) {
  jsonNesneAc(j, anahtar);
  j += '[';
}

// Merhaba mesajı: sunucu panosunda satır olarak görünecek her şey.
std::string merhabaMesajiUret() {
  const LisansOturum& o = lisansOturum();
  std::string j = "{";
  jsonDizgeEkle(j, "tip", "merhaba");
  jsonDizgeEkle(j, "seri", o.bilgi.seri);
  jsonDizgeEkle(j, "musteri", o.bilgi.musteri);
  if (const char* cn = std::getenv("COMPUTERNAME"); cn && *cn)
    jsonDizgeEkle(j, "bilgisayar", cn);
  jsonDizgeEkle(j, "surum", SURUM_METIN ? SURUM_METIN : "");
  jsonSayiEkle(j, "pid", (long long)GetCurrentProcessId());
  jsonDiziAc(j, "makine");
  bool ilk = true;
  for (const std::string& h : makineParmakIzi().hashListesi()) {
    if (!ilk) j += ',';
    ilk = false;
    j += TIRNAK;
    for (char c : h) {
      if (c == TIRNAK || c == TERS || c == NL) {
        j += TERS;
        j += 'n';
      } else {
        j += c;
      }
    }
    j += TIRNAK;
  }
  j += "]}";
  return j;
}

// ── Karar uygulama ───────────────────────────────────────────────────────────
void kararUygula(const CanliKarar& k) {
  {
    std::lock_guard lk(g_mtx);
    g_kararlar[k.kimlik] = k;
    ++g_kararSayisi;
    g_sonKararZaman = k.zaman;
  }
  gunluk::bilgi("Canlı lisans kararı: {} → {} ({})", k.kimlik,
                k.engelli ? "ENGELLI" : "izinli", k.mesaj);
  g_taze = true;  // pompa UI iş parçacığında lisansı yeniden yükler
}

// ── Socket.IO v3 transport (engine.io v3, websocket) ─────────────────────────
// engine.io v3 wire formatı:
//   - Socket.io EVENT packet: 2[/isans,eventname,{"args"}]
//   - websocket frame'i = socket.io EVENT packet (length prefix YOK)
//   - polling handshake: GET /isans.socket.io/?EIO=3&transport=polling  → sid
//   - websocket yükseltme: GET /isans.socket.io/?EIO=3&transport=websocket&sid=...
//
// Sunucu (/isans namespace'i) merhaba olayını bekler, lisans-karar ile yansır.
// Transport: yalnızca websocket.

// UTF-16 kod birimi sayısı (ASCII için byte sayısı; unicode için UTF-16 kod birimi).
size_t utf16KarakterSayisi(const std::string& s) {
  size_t kodBirimi = 0;
  for (size_t i = 0; i < s.size();) {
    unsigned char c = static_cast<unsigned char>(s[i]);
    size_t adim;
    if (c < 0x80)
      adim = 1;
    else if ((c >> 5) == 0x6)
      adim = 2;
    else if ((c >> 4) == 0xE)
      adim = 3;
    else
      adim = 4;
    i += adim;
    if (i > s.size()) break;
    // surrogate çifti (U+10000+) → UTF-16'da 2 kod birimi
    if (adim == 4 && i + 1 <= s.size()) kodBirimi += 2;
    else kodBirimi += 1;
  }
  return kodBirimi;
}

// Socket.io EVENT packet'ını engine.io v3 payload'a sar.
// event = ad, argsJson = tek nesne argüman (JSON). Nsp = /isans.
std::string socketioEventUret(const std::string& event, const std::string& argsJson) {
  std::string packet = "2[/isans,";
  packet += event;
  packet += ",";
  packet += argsJson;
  packet += "]";
  // engine.io v3: <length>:<packet>
  std::string payload;
  payload += std::to_string(utf16KarakterSayisi(packet));
  payload += ":";
  payload += packet;
  return payload;
}

// engine.io v3 payload'ını socket.io EVENT packet'ına çöz.
// Dönüş: true ise outEvent/outJson doldurulur (lisans-karar olayı).
bool engineioPayloadCoz(const std::string& payload, std::string& outEvent,
                        std::string& outJson) {
  // <length>:<packet>
  const size_t kolon = payload.find(':');
  if (kolon == std::string::npos) return false;
  // length atla (doğrulama yok — sunucudan geldi)
  const std::string packet = payload.substr(kolon + 1);
  // socket.io EVENT: 2[/isans,event,{"args"}]
  if (packet.size() < 2 || packet[0] != '2') return false;
  size_t i = 1;
  if (packet[i] != '/') return false;  // namespace yok → /
  ++i;
  // /namespace, → ',' ye kadar
  const size_t virgul = packet.find(',', i);
  if (virgul == std::string::npos) return false;
  // event adı: virgul + 1'den başlayıp son TIRNAK'tan önceye kadar değil,
  // '}' ve ']' arasından çıkar.
  // Format: 2[/isans,event,{"args"}]  → event, virgul'den sonra başlar
  const size_t eventBaslangic = virgul + 1;
  // event adı ',' veya '{' bitinceye kadar
  size_t eventBitis = eventBaslangic;
  while (eventBitis < packet.size() && packet[eventBitis] != '{' &&
         packet[eventBitis] != '}' && packet[eventBitis] != ']')
    ++eventBitis;
  outEvent = packet.substr(eventBaslangic, eventBitis - eventBaslangic);

  // args JSON: ilk '{' ile ']' arasında
  const size_t jsonBas = packet.find('{', eventBitis);
  if (jsonBas == std::string::npos) {
    outJson.clear();
    return true;
  }
  const size_t jsonBit = packet.find_last_of("}]");
  if (jsonBit == std::string::npos) return false;
  outJson = packet.substr(jsonBas, jsonBit - jsonBas + 1);
  return true;
}

// JSON sayısal alan ("anahtar":123).
bool jsonSayiAl(std::string_view j, const char* anahtar, long long& out) {
  std::string pat;
  pat += TIRNAK;
  pat += anahtar;
  pat += TIRNAK;
  const std::size_t p = j.find(pat);
  if (p == std::string_view::npos) return false;
  std::size_t i = p + pat.size();
  while (i < j.size() && (j[i] == ' ' || j[i] == TAB)) ++i;
  if (i >= j.size() || j[i] != ':') return false;
  ++i;
  while (i < j.size() && (j[i] == ' ' || j[i] == TAB)) ++i;
  bool neg = false;
  if (i < j.size() && j[i] == '-') {
    neg = true;
    ++i;
  }
  if (i >= j.size() || j[i] < '0' || j[i] > '9') return false;
  long long v = 0;
  while (i < j.size() && j[i] >= '0' && j[i] <= '9') {
    v = v * 10 + (j[i] - '0');
    ++i;
  }
  out = neg ? -v : v;
  return true;
}

// HTTP long-polling ile Socket.IO handshake'i başlat → sid + ping parametreleri.
// Dönüş: true başarılı.
bool socketioBaslat(const UrlParca& u, std::string& sid, DWORD& pingIntervalMs) {
  const std::wstring whost(u.host.begin(), u.host.end());

  HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansCanli/1.0",
                                 WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME,
                                 WINHTTP_NO_PROXY_BYPASS, 0);
  if (!oturum) return false;
  WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE);

  HINTERNET baglanti = WinHttpConnect(oturum, whost.c_str(), u.port, 0);
  if (!baglanti) {
    WinHttpCloseHandle(oturum);
    return false;
  }
  gunluk::uyari("lisans: connect OK ({}:{})", u.host, u.port);

  // GET /isans.socket.io/?EIO=3&transport=polling
  std::string yolQuery = u.yol + "?EIO=3&transport=polling";
  const std::wstring wpath(yolQuery.begin(), yolQuery.end());
  // ⚠️ WinHttpOpenRequest BAGLANTI handle'i ister (oturum degil).
  //    Oturum handle'i verilince 12018 (SECURE_FAILURE) donuyordu ve
  //    'baglanti' olusturulup bir satir sonra kapatiliyordu.
  HINTERNET istek = WinHttpOpenRequest(
      baglanti, L"GET", wpath.c_str(), nullptr, WINHTTP_NO_REFERER,
      WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0);
  if (!istek) {
    DWORD err = GetLastError();
    gunluk::uyari("lisans: OpenRequest FAIL, WinHTTP error {}", err);
    WinHttpCloseHandle(baglanti);
    WinHttpCloseHandle(oturum);
    return false;
  }
  // Aynı sertifika politikası (gizli üretici adresi) — yoksa 12018 dönüyor.
  if (u.https) {
    DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
                SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
    WinHttpSetOption(istek, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec));
  }

  DWORD kod = 0, boyut = sizeof(kod);
  if (!WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA,
                          0, 0, 0) ||
      !WinHttpReceiveResponse(istek, nullptr)) {
    DWORD err = GetLastError();
    gunluk::uyari("lisans: SendRequest/ReceiveResponse FAIL, WinHTTP error {}", err);
    WinHttpCloseHandle(istek);
    WinHttpCloseHandle(baglanti);
    WinHttpCloseHandle(oturum);
    return false;
  }
  WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                      WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX);
  if (kod != 200) {
    gunluk::uyari("lisans: polling HTTP {}", kod);
    WinHttpCloseHandle(istek);
    WinHttpCloseHandle(baglanti);
    WinHttpCloseHandle(oturum);
    return false;
  }

  // Yanıt: {"upgrades":[...],"pingInterval":25000,"pingTimeout":20000,"sid":"..."}
  std::string yanit;
  DWORD okunan = 0, toplam = 0;
  char tampon[2048];
  do {
    okunan = 0;
    if (!WinHttpReadData(istek, tampon, sizeof(tampon), &okunan) || okunan == 0) break;
    yanit.append(tampon, okunan);
    toplam += okunan;
  } while (toplam < sizeof(tampon) * 8);

  WinHttpCloseHandle(istek);
  WinHttpCloseHandle(baglanti);
  WinHttpCloseHandle(oturum);

  // sid çıkar
  const size_t sidPos = yanit.find("\"sid\"");
  if (sidPos == std::string::npos) {
    gunluk::uyari("lisans: sid bulunamadi (HTTP {}, body {} byte)", kod, yanit.size());
    return false;
  }
  size_t n = yanit.find(':', sidPos);
  if (n == std::string::npos) return false;
  ++n;
  while (n < yanit.size() && (yanit[n] == ' ' || yanit[n] == TAB)) ++n;
  if (n >= yanit.size() || yanit[n] != TIRNAK) return false;
  ++n;
  size_t bit = n;
  while (bit < yanit.size() && yanit[bit] != TIRNAK) ++bit;
  if (bit == n) return false;
  sid = yanit.substr(n, bit - n);
  if (sid.empty()) return false;

  // pingInterval çıkar (opsiyonel)
  pingIntervalMs = 25000;
  const size_t pingPos = yanit.find("\"pingInterval\"");
  if (pingPos != std::string::npos) {
    long long val = 0;
    if (jsonSayiAl(yanit, "pingInterval", val))
      pingIntervalMs = static_cast<DWORD>(val);
  }
  return true;
}

// Socket.IO ile bağlan → merhaba gönder → lisans-karar dinle.
// Transport: yalnızca websocket.
// Dönüş: ne kadar sonra yeniden bağlanma denenecek (ms).
DWORD wsOturumu(const UrlParca& u) {
  HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansCanli/1.0",
                                 WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME,
                                 WINHTTP_NO_PROXY_BYPASS, 0);
  if (!oturum) {
    std::lock_guard lk(g_mtx);
    g_hata = "WinHttpOpen başarısız";
    return YENIDEN_BAGLANMA_MIN_MS;
  }
  WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE);

  DWORD pingIntervalMs = 25000;
  std::string sid;
  gunluk::uyari("lisans: polling handshake deneniyor ({})", u.host.c_str());
  if (!socketioBaslat(u, sid, pingIntervalMs)) {
    std::lock_guard lk(g_mtx);
    g_hata = "Socket.IO handshake reddedildi";
    WinHttpCloseHandle(oturum);
    return YENIDEN_BAGLANMA_MIN_MS;
  }
  gunluk::uyari("lisans: polling OK, sid={}", sid);

  // ⚠️ Bu fonksiyondaki TÜM WinHttpOpenRequest çağrıları BAGLANTI handle'ı
  //    ister. Oturum handle'ı verilirse 12018 (SECURE_FAILURE) dönüyor.
  // ⚠️ std::wstring(...) geçici nesnesi yerine sabit tampon kullanılır.
  wchar_t wbuf[256] = {0};
  {
    const size_t n = u.host.size() < 255 ? u.host.size() : 255;
    for (size_t i = 0; i < n; ++i) wbuf[i] = (wchar_t)(unsigned char)u.host[i];
    wbuf[n] = 0;
  }
  gunluk::g_teshisAdim = "LC:WinHttpConnect";
  HINTERNET baglanti = WinHttpConnect(oturum, wbuf, (INTERNET_PORT)u.port, 0);
  gunluk::g_teshisAdim = "LC:baglanti-ok";
  if (!baglanti) {
    std::lock_guard lk(g_mtx);
    g_hata = "HTTP bağlantısı kurulamadı";
    WinHttpCloseHandle(oturum);
    return YENIDEN_BAGLANMA_MIN_MS;
  }

  // CONNECT packet'ı: 0[/isans]  (namespace'e bağlan)
  {
    const std::string connectPacket = "0[/isans]";
    // ⚠️ begin()/end() AYNI adlandırılmış string'den alınmalı. İki ayrı geçici string'in
    //    iteratörleriyle aralık kurmak UB → length_error/bad_alloc → thread'de terminate →
    //    abort → 0xC0000409 (asıl çökme kaynağı buydu).
    const std::string pollYol = u.yol + "?EIO=3&sid=" + sid + "&transport=polling";
    const std::wstring wpath(pollYol.begin(), pollYol.end());
    HINTERNET istek = WinHttpOpenRequest(
        baglanti, L"POST", wpath.c_str(), nullptr, WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0);
    if (!istek) {
      std::lock_guard lk(g_mtx);
      g_hata = "POST istek kurulamadı";
      WinHttpCloseHandle(baglanti);
      WinHttpCloseHandle(oturum);
      return YENIDEN_BAGLANMA_MIN_MS;
    }
    if (u.https) {
      DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                  SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
                  SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
      WinHttpSetOption(istek, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec));
    }
    DWORD payloadLen = static_cast<DWORD>(connectPacket.size());
    if (!WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
                            (PVOID)connectPacket.data(), payloadLen, payloadLen, 0) ||
        !WinHttpReceiveResponse(istek, nullptr)) {
      std::lock_guard lk(g_mtx);
      g_hata = "CONNECT iletilemedi";
      WinHttpCloseHandle(istek);
      WinHttpCloseHandle(baglanti);
      WinHttpCloseHandle(oturum);
      return YENIDEN_BAGLANMA_MIN_MS;
    }
    DWORD kod = 0, boyut = sizeof(kod);
    WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                        WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX);
    WinHttpCloseHandle(istek);
    if (kod != 200) {
      DWORD err = GetLastError();
      gunluk::uyari("lisans: CONNECT reddedildi (HTTP {}, WinHTTP error {})", kod, err);
      std::lock_guard lk(g_mtx);
      g_hata = "CONNECT reddedildi (HTTP " + std::to_string(kod) + ")";
      WinHttpCloseHandle(baglanti);
      WinHttpCloseHandle(oturum);
      return YENIDEN_BAGLANMA_MIN_MS;
    }
    gunluk::uyari("lisans: CONNECT OK (HTTP 200)");
  }

  // Bağlandı → durum + merhaba.
  {
    std::lock_guard lk(g_mtx);
    g_durum = KanalDurum::Bagli;
    g_hata.clear();
    g_sunucuAdi = u.host;
  }
  gunluk::bilgi("Canlı lisans kanalı bağlandı (Socket.IO): {}", u.host);

  const std::string merhabaJson = merhabaMesajiUret();
  const std::string merhabaPacket = socketioEventUret("merhaba", merhabaJson);

  // websocket'e yükseltme
  HINTERNET ws = nullptr;
  {
    const std::string wsPath = u.yol + "?EIO=3&transport=websocket&sid=" + sid;
    const std::wstring wpath(wsPath.begin(), wsPath.end());
    HINTERNET istek = WinHttpOpenRequest(
        baglanti, L"GET", wpath.c_str(), nullptr, WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0);
    if (istek) {
      // ⚠️ WebSocket yükseltmesinde de aynı sertifika politikası uygulanmalı
      //    (gizli üretici adresi). Bu adım atlanınca WinHttpSetOption
      //    ERROR_INVALID_PARAMETER (87) döndürüyordu.
      if (u.https) {
        DWORD sec = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                    SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
                    SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
        WinHttpSetOption(istek, WINHTTP_OPTION_SECURITY_FLAGS, &sec, sizeof(sec));
      }
      // ⚠️ UPGRADE_TO_WEB_SOCKET tampon ALMAZ: (nullptr, 0) verilmeli. Değer/uzunluk
      //    geçmek ERROR_INVALID_PARAMETER (87) döndürüyordu.
      if (WinHttpSetOption(istek, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0)) {
        if (WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA,
                               0, 0, 0) && WinHttpReceiveResponse(istek, nullptr)) {
          DWORD kod = 0, boyut = sizeof(kod);
          WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                              WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX);
          if (kod == 101) {
            ws = WinHttpWebSocketCompleteUpgrade(istek, 0);
            if (ws) gunluk::uyari("lisans: websocket 101 OK");
            else {
              DWORD err = GetLastError();
              gunluk::uyari("lisans: websocket complete FAIL, error {}", err);
            }
          } else {
            gunluk::uyari("lisans: websocket upgrade HTTP {}", kod);
          }
        } else {
          DWORD err = GetLastError();
          gunluk::uyari("lisans: websocket send/recv FAIL, error {}", err);
        }
      } else {
        DWORD err = GetLastError();
        gunluk::uyari("lisans: websocket setoption FAIL, error {}", err);
      }
      WinHttpCloseHandle(istek);
    }
  }
  if (!ws) {
    std::lock_guard lk(g_mtx);
    g_hata = "websocket'e yükseltilemedi";
    WinHttpCloseHandle(baglanti);
    WinHttpCloseHandle(oturum);
    return YENIDEN_BAGLANMA_MIN_MS;
  }
  // WebSocket kendi bağlantısını devraldı; HTTP bağlantı handle'ı gereksiz.
  WinHttpCloseHandle(baglanti);

  // websocket frame'leri: socket.io EVENT packet'ı (length prefix YOK)
  {
    const DWORD src = WinHttpWebSocketSend(ws, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE,
                                           (PVOID)merhabaPacket.data(),
                                           (DWORD)merhabaPacket.size());
    if (src != NO_ERROR) {
      gunluk::uyari("lisans: merhaba gonderilemedi rc={}", src);
      std::lock_guard lk(g_mtx);
      g_hata = "merhaba gönderilemedi";
    } else {
      gunluk::uyari("lisans: merhaba gonderildi ({} bayt): {}", merhabaPacket.size(),
                    merhabaPacket.substr(0, 40));
    }
  }

  std::string birikim;
  auto sonPing = std::chrono::steady_clock::now();
  for (;;) {
    // Ping: 25s'de bir engine.io ping frame'i (4)
    if (std::chrono::steady_clock::now() - sonPing >
        std::chrono::milliseconds(pingIntervalMs)) {
      sonPing = std::chrono::steady_clock::now();
      if (WinHttpWebSocketSend(ws, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)"4", 1) !=
          NO_ERROR)
        break;
    }
    char tampon[4096];
    DWORD okunan = 0;
    WINHTTP_WEB_SOCKET_BUFFER_TYPE tur = WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE;
    const DWORD rc = WinHttpWebSocketReceive(ws, tampon, sizeof(tampon), &okunan, &tur);
    if (rc != NO_ERROR) {
      gunluk::uyari("lisans: ws okuma hatasi rc={} (12030=baglanti kesildi, 12002=zaman asimi)", rc);
      std::lock_guard lk(g_mtx);
      g_hata = "bağlantı koptu (okuma)";
      break;
    }
    if (tur == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) {
      USHORT kapKod = 0;
      char kapNeden[123] = {0};
      DWORD kapNedenUz = 0;
      WinHttpWebSocketQueryCloseStatus(ws, &kapKod, kapNeden, sizeof(kapNeden) - 1, &kapNedenUz);
      gunluk::uyari("lisans: sunucu ws kapatti kod={} neden={}", (int)kapKod,
                    std::string(kapNeden, kapNedenUz < sizeof(kapNeden) ? kapNedenUz : 0));
      std::lock_guard lk(g_mtx);
      g_hata = "sunucu bağlantıyı kapattı";
      break;
    }
    gunluk::uyari("lisans: ws frame tur={} bayt={}", (int)tur, (int)okunan);
    birikim.append(tampon, okunan);
    if (tur != WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE) continue;  // parça

    // Tam frame geldi → socket.io EVENT packet'ına çöz
    const std::string frame = std::move(birikim);
    birikim.clear();
    std::string ev, json;
    if (engineioPayloadCoz(frame, ev, json) && ev == "lisans-karar") {
      CanliKarar k;
      std::string hata;
      const UzakPolitikaDurum d = canliKararCoz(json, lisansAcikAnahtarHex(), k, hata);
      if (d == UzakPolitikaDurum::Gecerli) {
        kararUygula(k);
      } else if (d == UzakPolitikaDurum::Bicim && hata.find("tip") != std::string::npos) {
        // ping/pong gibi → sessizce yok say
      } else {
        gunluk::uyari("Canlı kanal mesajı reddedildi ({}): {}", hata, json.substr(0, 120));
      }
    }
  }

  // Temizlik: ws / oturum
  WinHttpWebSocketClose(ws, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0);
  WinHttpCloseHandle(ws);
  WinHttpCloseHandle(oturum);

  {
    std::lock_guard lk(g_mtx);
    if (g_durum == KanalDurum::Bagli) {
      g_durum = KanalDurum::Kopuk;
      g_sonBagliZaman = (i64)std::time(nullptr);
    }
  }
  gunluk::bilgi("Canlı lisans kanalı kapandı (Socket.IO)");
  return YENIDEN_BAGLANMA_MIN_MS;
}

// Arka plan iş parçacığı: URL olduğu sürece bağlan/kop/yeniden bağlan.
void canliIsParcacigi() {
  DWORD beklemeMs = 0;
  for (;;) {
    if (g_durduruldu.load()) break;   // kapanış: statik nesnelere dokunma
    if (urlOku().empty()) {
      std::lock_guard lk(g_mtx);
      g_durum = KanalDurum::Kapali;
      // URL silindi → bekle; ayar tekrar dolarsa dışarıdan yeniden başlatılır.
      for (int i = 0; i < 100 && urlOku().empty() && !g_durduruldu.load(); ++i)
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
      continue;
    }
    if (g_yenidenBaglanIste.exchange(false)) beklemeMs = 0;

    UrlParca u;
    if (!urlCoz(urlOku(), u)) {
      {
        std::lock_guard lk(g_mtx);
        g_hata = "geçersiz URL";
        g_durum = KanalDurum::Kopuk;
      }
      std::this_thread::sleep_for(std::chrono::seconds(10));
      continue;
    }
    {
      std::lock_guard lk(g_mtx);
      g_durum = KanalDurum::Baglaniyor;
    }
    beklemeMs = wsOturumu(u);
    // Üstel geri çekilme: her başarısız turda büyüt, tavanla sınırla.
    beklemeMs = std::min<DWORD>(beklemeMs * 2, YENIDEN_BAGLANMA_MAKS_MS);
    for (DWORD bekledi = 0; bekledi < beklemeMs; bekledi += 250) {
      if (g_yenidenBaglanIste.load()) break;   // elle yeniden bağlanma isteği
      if (g_durduruldu.load()) break;           // kapanış
      std::this_thread::sleep_for(std::chrono::milliseconds(250));
    }
  }
  // Kapanış/sonlandırma bildir.
  {
    std::lock_guard lk(g_durmus_mtx);
    g_durdu = true;
  }
  g_durmus_cv.notify_all();
}

}  // namespace

void uzakCanliBaslat() {
  static const bool birKez = [] {
    {
      std::lock_guard lk(g_mtx);
      if (g_izlemeBaslangic == 0) g_izlemeBaslangic = (i64)std::time(nullptr);
    }
    if (urlOku().empty()) {
      std::lock_guard lk(g_mtx);
      g_durum = KanalDurum::Kapali;
      return true;  // kanal kapalı — iş parçacığı başlatma
    }
    if (g_isParcacigiSuruyor.exchange(true)) return true;
    std::thread([] {
      // Detached thread'de yakalanmayan istisna = std::terminate = süreç ölümü (0xC0000409).
      // Lisans kanalı yardımcı bir servis: hata uygulamayı düşürmemeli, yalnız loglanmalı.
      try {
        canliIsParcacigi();
      } catch (const std::exception& ex) {
        gunluk::uyari("lisans: canli kanal istisnasi yakalandi: {}", ex.what());
      } catch (...) {
        gunluk::uyari("lisans: canli kanal bilinmeyen istisna yakalandi");
      }
      g_isParcacigiSuruyor = false;
    }).detach();
    return true;
  }();
  (void)birKez;
}

void uzakCanliDurdur() {
  // ⚠️ Kapanışta bu kanalın detached iş parçacığı statik nesnelere
  //    (g_mtx, g_hata, g_durum) erişmeye devam ederse, statik yok etme
  //    sırasında yarış durumu → 0xC0000409. Kurulum (uzakCanliBaslat)
  //    sırasında çağrılmalı; sonra yeniden başlatılabilir.
  g_durduruldu = true;
  {
    std::lock_guard lk(g_durmus_mtx);
    g_durdu = true;
  }
  g_durmus_cv.notify_all();
}

bool uzakCanliKararVarMi(const std::string& seri) {
  std::lock_guard lk(g_mtx);
  return g_kararlar.count(seri) != 0;
}

bool uzakCanliEngelliMi(const std::string& seri) {
  std::lock_guard lk(g_mtx);
  const auto it = g_kararlar.find(seri);
  return it != g_kararlar.end() && it->second.engelli;
}

std::string uzakCanliMesaj(const std::string& seri) {
  std::lock_guard lk(g_mtx);
  const auto it = g_kararlar.find(seri);
  return it != g_kararlar.end() ? it->second.mesaj : std::string();
}

bool uzakCanliTazeKontrol() { return g_taze.exchange(false); }

bool uzakCanliZorunluIhlalMi() {
  const bool zorunlu = ayarOku("lisans_canli_zorunlu", "0") == "1";
  if (!zorunlu) return false;
  std::lock_guard lk(g_mtx);
  if (g_durum == KanalDurum::Bagli) return false;
  if (g_izlemeBaslangic == 0) return false;  // izleme henüz başlamadı (uzakCanliBaslat çağrılmadı)
  const i64 simdi = (i64)std::time(nullptr);
  const i64 referans = g_sonBagliZaman != 0 ? g_sonBagliZaman : g_izlemeBaslangic;
  const int esikSn = std::max(10, std::atoi(ayarOku("lisans_canli_zorunlu_gecikme_sn", "120").c_str()));
  return (simdi - referans) > esikSn;
}

std::string uzakCanliDurumOzet() {
  std::lock_guard lk(g_mtx);
  switch (g_durum) {
    case KanalDurum::Kapali:
      return "Canlı kanal: kapalı (URL ayarlanmamış)";
    case KanalDurum::Baglaniyor:
      return "Canlı kanal: bağlanıyor...";
    case KanalDurum::Bagli:
      return "Canlı kanal: BAĞLI — " + g_sunucuAdi + " · " +
             std::to_string(g_kararSayisi) + " karar";
    case KanalDurum::Kopuk:
      return "Canlı kanal: kopuk, yeniden bağlanıyor" +
             (g_hata.empty() ? std::string() : " (" + g_hata + ")");
  }
  return "Canlı kanal: ?";
}

void uzakCanliAyarBolumu() {
  ImGui::TextDisabled("%s", uzakCanliDurumOzet().c_str());

  static char buf[512] = "";
  static bool bufDolu = false;
  if (!bufDolu) {
    std::string v = urlOku();
    snprintf(buf, sizeof(buf), "%s", v.c_str());
    bufDolu = true;
  }
  ImGui::InputText("##canliurl", buf, sizeof(buf));
  if (ImGui::IsItemHovered()) {
    std::string ipucu =
        "Canlı lisans sunucusu (Socket.IO v3). Örn: https://fikoyt.com:9000/isans.socket.io/";
    ipucu += NL;
    ipucu += "Boş = canlı kanal kapalı (.pol dosyalı uzak politika bağımsız çalışır)";
    ImGui::SetTooltip("%s", ipucu.c_str());
  }
  ImGui::SameLine();
  if (ImGui::Button("Bağlan")) {
    ayarYaz(CANLI_URL_AYAR, buf);
    uzakCanliBaslat();
    g_yenidenBaglanIste = true;
  }
  ImGui::SameLine();
  if (ImGui::Button("Kanalı kapat")) ayarYaz(CANLI_URL_AYAR, "");

  static bool zorunluDolu = false;
  static bool zorunlu = false;
  static int gecikmeSn = 120;
  if (!zorunluDolu) {
    zorunlu = ayarOku("lisans_canli_zorunlu", "0") == "1";
    gecikmeSn = std::max(10, std::atoi(ayarOku("lisans_canli_zorunlu_gecikme_sn", "120").c_str()));
    zorunluDolu = true;
  }
  if (ImGui::Checkbox("Bağlantı ZORUNLU (yoksa uygulama kilitlenir)", &zorunlu))
    ayarYaz("lisans_canli_zorunlu", zorunlu ? "1" : "0");
  if (ImGui::IsItemHovered()) {
    std::string ipucu =
        "Açıksa: kanal aşağıdaki gecikme süresinden daha uzun süredir BAĞLI değilse,";
    ipucu += NL;
    ipucu += "lisans ne olursa olsun (Serbest/Kısıtlı fark etmez) uygulama görüntüleyici";
    ipucu += NL;
    ipucu += "kipine düşer: hiçbir komut çalışmaz. Sunucu tekrar ulaşılır olunca otomatik açılır.";
    ImGui::SetTooltip("%s", ipucu.c_str());
  }
  if (zorunlu) {
    ImGui::SetNextItemWidth(120.0f);
    if (ImGui::DragInt("Gecikme (sn)##canlizorunlugecikme", &gecikmeSn, 1.0f, 10, 3600))
      ayarYaz("lisans_canli_zorunlu_gecikme_sn", std::to_string(gecikmeSn));
  }
}

}  // namespace fiko
