/**
 * Copyright (C) 2026 Ralf Burger
 * ralf@RalfBurger.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#pragma once
#include <WiFiServer.h>
#include <WiFiClient.h>
#include <pgmspace.h>
#include "pid_registry.h"

#define HTTP_PORT 80
WiFiServer httpServer(HTTP_PORT);

extern bool apMode;
extern bool wifiOk;

#if defined(DEVICE_BRIDGE)
  #include <NimBLEDevice.h>
  extern NimBLEClient* pClient;
  // DTC-Funktionen (definiert in obd_bridge.ino)
  extern void readDTCs();
  extern void clearDTCs();
  // Akku-Info (definiert in obd_bridge.ino)
  extern bool  batGetValid();
  extern float batGetVolt();
  extern int   batGetPct();
#elif defined(DEVICE_RECEIVER)
  extern RxStats rx;
#endif

// ---- PROGMEM strings ----------------------------------------
static const char HTTP_HDR[] PROGMEM =
  "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n"
  "Cache-Control: no-cache\r\nConnection: close\r\n\r\n";

static const char HTML_404[] PROGMEM =
  "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n"
  "Connection: close\r\n\r\n404 - nur GET / verfuegbar";

static const char HTML_CSS[] PROGMEM =
  "<style>"
  "*{box-sizing:border-box;margin:0;padding:0}"
  "body{font-family:'Segoe UI',system-ui,sans-serif;background:#f0f2f5;"
       "color:#0d1520;font-size:14px}"
  "nav{background:#0d1520;color:#fff;padding:.6rem 1.2rem;"
      "display:flex;justify-content:space-between;align-items:center;"
      "flex-wrap:wrap;gap:.4rem}"
  "nav .lg{font-family:monospace;font-weight:700;font-size:.9rem}"
  "nav .mt{font-family:monospace;font-size:.65rem;color:#8aaac0}"
  ".wr{max-width:980px;margin:0 auto;padding:1rem}"
  ".sb{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));"
      "gap:1px;background:#c8d0dc;border:1px solid #c8d0dc;margin-bottom:1rem}"
  ".sc{background:#fff;padding:.6rem .8rem;text-align:center}"
  ".sl{font-size:.56rem;color:#556;text-transform:uppercase;"
      "letter-spacing:.07em;margin-bottom:.15rem}"
  ".sv{font-size:1.2rem;font-weight:700;line-height:1}"
  ".su{font-size:.52rem;color:#778;margin-top:.1rem}"
  ".ok{color:#007a3d}.wa{color:#b85c00}.er{color:#cc0022}.di{color:#bbb}"
  "h2{font-family:monospace;font-size:.7rem;color:#445;"
     "letter-spacing:.12em;text-transform:uppercase;"
     "margin:.7rem 0 .3rem;padding-bottom:.25rem;"
     "border-bottom:2px solid #0055cc}"
  ".pg{display:grid;grid-template-columns:repeat(auto-fill,minmax(185px,1fr));"
      "gap:1px;background:#c8d0dc;border:1px solid #c8d0dc;margin-bottom:.8rem}"
  ".pc{background:#fff;padding:.45rem .7rem;"
      "display:flex;align-items:center;gap:.5rem}"
  ".pc.st{background:#f8f8f8}"
  ".pb{font-family:monospace;font-size:.54rem;background:#eef1f5;color:#556;"
      "padding:.15em .45em;flex-shrink:0;min-width:3rem;text-align:center}"
  ".mo{background:#ddeeff;color:#0055cc}"
  ".kr{background:#ddfff0;color:#007a3d}"
  ".ab{background:#fff5dd;color:#b85c00}"
  ".zu{background:#ffdddd;color:#cc0022}"
  ".fe{background:#ffe0e0;color:#cc0022}"
  ".in{background:#f0f0f0;color:#667}"
  ".pn{flex:1;font-size:.72rem;color:#334}"
  ".pv{font-family:monospace;font-weight:700;font-size:.85rem;white-space:nowrap}"
  ".ac{color:#0055cc}.ia{color:#ccc}"
  ".pu{font-family:monospace;font-size:.58rem;color:#889;margin-left:.15rem}"
  "footer{text-align:center;font-family:monospace;font-size:.58rem;"
         "color:#889;padding:.7rem;border-top:1px solid #ddd;margin-top:.4rem}"
  "@media(max-width:480px){.sb{grid-template-columns:repeat(2,1fr)}}"
  ".rf{display:flex;align-items:center;gap:.4rem;font-family:monospace;font-size:.65rem}"
  "#rfl{color:#4a90d9;cursor:default}"
  "#rfs{font-family:monospace;font-size:.62rem;background:#1a2433;color:#aac;"
        "border:1px solid #2a3a4a;padding:.15em .3em;cursor:pointer}"
  ".dtcbar{display:flex;gap:.5rem;margin-bottom:.8rem;flex-wrap:wrap}"
  ".dtcbtn{font-family:monospace;font-size:.7rem;padding:.35em .9em;"
           "border:none;border-radius:3px;cursor:pointer;font-weight:700}"
  ".dtcbtn.rd{background:#ddeeff;color:#0055cc}"
  ".dtcbtn.cl{background:#ffdddd;color:#cc0022}"
  ".dtcbtn:active{opacity:.7}"
  "</style>";

static const char HTML_FOOT[] PROGMEM =
  "<footer>(C) 2026 Ralf Burger &nbsp;&middot;&nbsp; GPL v3"
  " &nbsp;&middot;&nbsp; ralf@RalfBurger.com"
  " &nbsp;&middot;&nbsp; TCP Port 1234</footer>"
  "</div>"
  "<script>"
  "var _t=null;"
  "function setR(v){"
    "clearTimeout(_t);"
    "var n=parseInt(v);"
    "var l=document.getElementById('rfl');"
    "if(n===0){l.textContent='Refresh: AUS';l.style.color='#cc4444';}"
    "else{l.textContent='&#8635; '+n+'s';l.style.color='#4a90d9';"
         "_t=setTimeout(function(){location.reload();},n*1000);}"
    "try{sessionStorage.setItem('r',n);}catch(e){}"
  "}"
  "var sv=3;"
  "try{var ss=sessionStorage.getItem('r');if(ss!==null)sv=parseInt(ss);}catch(e){}"
  "var sel=document.getElementById('rfs');"
  "if(sel)sel.value=String(sv);"
  "setR(sv);"
  "</script>"
  "</body></html>";

// ---- Macros -------------------------------------------------
#define PP(c, s)      c.print((__FlashStringHelper*)(s))
#define HPR(c, s)     c.print(F(s))
#define PF(c, ...)    do { char _b[96]; snprintf(_b,sizeof(_b),__VA_ARGS__); c.print(_b); } while(0)

// ---- Helper -------------------------------------------------
static const char* _grpCls(int i) {
  if(i <=  6) return "mo";
  if(i <= 17) return "kr";
  if(i <= 26) return "ab";
  if(i <= 31) return "zu";
  if(i <= 38) return "fe";
  return "in";
}

static void _sbCell(WiFiClient& c, const char* label,
                    bool valid, const char* cls,
                    const char* val, const char* unit) {
  PF(c, "<div class='sc'><div class='sl'>%s</div>", label);
  if(valid) {
    PF(c, "<div class='sv %s'>%s</div>", cls, val);
  } else {
    HPR(c, "<div class='sv di'>&#8212;</div>");
  }
  PF(c, "<div class='su'>%s</div></div>", unit);
}

// ---- Main page ----------------------------------------------
static void __attribute__((noinline)) httpSendPage(WiFiClient& client) {
  char tmp[32];

  // Device-specific constants -- set before any use
  const char* devName;
  const char* devColor;
#if defined(DEVICE_BRIDGE)
  devName  = "obdBT-Bridge v3.1";
  devColor = "#0055cc";
#else
  devName  = "obdBT-Receiver v1.3";
  devColor = "#007a3d";
#endif

  String ip = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString();

  PP(client, HTTP_HDR);

  // Head
  HPR(client, "<!DOCTYPE html><html lang='de'><head>"
             "<meta charset='UTF-8'>"
             "<meta name='viewport' content='width=device-width,initial-scale=1'>"
             ""  /* refresh via JS */);
  PF(client, "<title>%s</title>", devName);
  PP(client, HTML_CSS);
  HPR(client, "</head>");

  // Nav + refresh controls
  HPR(client, "<body><nav>");
  PF(client, "<span class='lg' style='color:%s'>%s</span>", devColor, devName);
  PF(client, "<span class='mt'>%s &nbsp;&middot;&nbsp; TCP:1234 &nbsp;&middot;&nbsp; HTTP:80</span>",
             ip.c_str());
  HPR(client,
    "<span class='rf'>"
    "<span id='rfl'>&#8635; Auto</span>"
    "<select id='rfs' onchange='setR(this.value)'>"
    "<option value='2'>2s</option>"
    "<option value='3' selected>3s</option>"
    "<option value='5'>5s</option>"
    "<option value='10'>10s</option>"
    "<option value='30'>30s</option>"
    "<option value='60'>60s</option>"
    "<option value='0'>AUS</option>"
    "</select></span>"
  );
  HPR(client, "</nav><div class='wr'>");

  // Status bar
  HPR(client, "<div class='sb'>");

  // RPM
  if(pidValid[PID_RPM]) snprintf(tmp,sizeof(tmp),"%d",(int)pidValues[PID_RPM]);
  _sbCell(client,"RPM", pidValid[PID_RPM],"ok", tmp,"1/min");

  // Speed
  if(pidValid[PID_SPEED]) snprintf(tmp,sizeof(tmp),"%d",(int)pidValues[PID_SPEED]);
  _sbCell(client,"Speed", pidValid[PID_SPEED],"ok", tmp,"km/h");

  // Temp
  if(pidValid[PID_TEMP]) {
    const char* tc = pidValues[PID_TEMP]>105?"er":pidValues[PID_TEMP]>95?"wa":"ok";
    snprintf(tmp,sizeof(tmp),"%d",(int)pidValues[PID_TEMP]);
    _sbCell(client,"Kuehlmittel", true, tc, tmp,"&#176;C");
  } else {
    _sbCell(client,"Kuehlmittel", false,"ok","","&#176;C");
  }

  // Voltage
  if(pidValid[PID_ECU_SPANNUNG]) {
    const char* vc = pidValues[PID_ECU_SPANNUNG]<11.5f?"er"
                   : pidValues[PID_ECU_SPANNUNG]>14.8f?"wa":"ok";
    snprintf(tmp,sizeof(tmp),"%.1f",pidValues[PID_ECU_SPANNUNG]);
    _sbCell(client,"Spannung", true, vc, tmp,"V");
  } else {
    _sbCell(client,"Spannung", false,"ok","","V");
  }

#if defined(DEVICE_BRIDGE)
  // Akku (µC-seitig)
  {
    if(batGetValid()) {
      int   pct = batGetPct();
      float v   = batGetVolt();
      const char* bc = pct<15?"er":pct<30?"wa":"ok";
      snprintf(tmp,sizeof(tmp),"%d",pct);
      char unit[12]; snprintf(unit,sizeof(unit),"%%  %.2fV",v);
      _sbCell(client,"Akku", true, bc, tmp, unit);
    } else {
      _sbCell(client,"Akku", false,"ok","","");
    }
  }
#endif

#if defined(DEVICE_BRIDGE)
  // MIL
  if(pidValid[PID_MIL_STATUS]) {
    bool mil = pidValues[PID_MIL_STATUS]>0.5f;
    _sbCell(client,"MIL", true, mil?"er":"ok", mil?"AN":"AUS","");
  } else {
    _sbCell(client,"MIL", false,"ok","","");
  }
  // BLE
  {
    bool con = pClient && pClient->isConnected();
    _sbCell(client,"BLE", true, con?"ok":"er", con?"OK":"GETRENNT","");
  }
#endif

#if defined(DEVICE_RECEIVER)
  {
    bool stale = (rx.rawCount==0)||(millis()-rx.lastRx>10000UL);
    if(!stale && rx.rawCount>0) {
      const char* rc = rx.rssi<-100.0f?"er":rx.rssi<-80.0f?"wa":"ok";
      snprintf(tmp,sizeof(tmp),"%.0f",rx.rssi);
      _sbCell(client,"LoRa RSSI", true, rc, tmp,"dBm");
      snprintf(tmp,sizeof(tmp),"%.0f",rx.snr);
      _sbCell(client,"SNR", true,"ok", tmp,"dB");
    } else {
      _sbCell(client,"LoRa", true,"wa","WARTE","");
    }
  }
#endif

  HPR(client, "</div>"); // sb

#if defined(DEVICE_BRIDGE)
  // DTC-Aktionsleiste (nur Bridge)
  HPR(client, "<div class='dtcbar'>");
  HPR(client,
    "<form method='POST' action='/dtc' style='margin:0'>"
    "<button class='dtcbtn rd' type='submit'>&#128269; DTC lesen (Mode 03)</button>"
    "</form>");
  HPR(client,
    "<form method='POST' action='/cleardtc' style='margin:0'"
    " onsubmit=\"return confirm('Fehlerspeicher wirklich loeschen?')\">"
    "<button class='dtcbtn cl' type='submit'>&#10005; Fehlerspeicher loeschen (Mode 04)</button>"
    "</form>");
  HPR(client, "</div>");
#endif

  // PID table
  static const char* grpNames[] = {
    "Motor","Kraftstoff","Abgas","Zuendung","Fehler","Info"
  };
  static const int grpEnd[] = {7,18,27,32,39,PID_COUNT_USED};

  for(int g=0; g<6; g++) {
    int start = (g==0)?0:grpEnd[g-1];
    int end   = grpEnd[g];
    bool any  = false;
    for(int i=start;i<end&&i<PID_COUNT_USED;i++)
      if(PID_INFO[i].id[0]!='\0'){any=true;break;}
    if(!any) continue;

    PF(client,"<h2>%s</h2>",grpNames[g]);
    HPR(client,"<div class='pg'>");

    for(int i=start;i<end&&i<PID_COUNT_USED;i++) {
      if(PID_INFO[i].id[0]=='\0') continue;
      bool v = pidValid[i];
      PF(client,"<div class='pc%s'>",v?"":" st");
      PF(client,"<span class='pb %s'>%s</span>",_grpCls(i),PID_INFO[i].id);
      PF(client,"<span class='pn'>%s</span>",PID_INFO[i].name);
      if(v) {
        int dec = PID_INFO[i].decimals;
        if(PID_INFO[i].unit[0]=='\0') {
          PF(client,"<span class='pv ac'>%d</span>",(int)pidValues[i]);
        } else if(dec==0) {
          PF(client,"<span class='pv ac'>%d</span>"
                    "<span class='pu'>%s</span>",
                    (int)pidValues[i],PID_INFO[i].unit);
        } else {
          PF(client,"<span class='pv ac'>%.*f</span>"
                    "<span class='pu'>%s</span>",
                    dec,pidValues[i],PID_INFO[i].unit);
        }
      } else {
        HPR(client,"<span class='pv ia'>&#8212;</span>");
      }
      HPR(client,"</div>");
    }
    HPR(client,"</div>"); // pg
  }

  PP(client, HTML_FOOT);
}

// ---- HTTP connection state (non-blocking) -------------------
static WiFiClient _httpClient;
static String     _httpReq    = "";
static bool       _httpActive = false;
static uint8_t    _httpState  = 0; // 0=read req, 1=drain headers, 2=respond
static unsigned long _httpT   = 0;
#define HTTP_TIMEOUT_MS 400

void httpCheck() {
  // Accept new connection only when idle
  if(!_httpActive) {
    _httpClient = httpServer.available();
    if(!_httpClient) return;
    _httpClient.setNoDelay(true);
    _httpReq    = "";
    _httpState  = 0;
    _httpActive = true;
    _httpT      = millis();
  }

  // Timeout guard
  if(millis() - _httpT > HTTP_TIMEOUT_MS) {
    _httpClient.stop();
    _httpActive = false;
    return;
  }

  if(!_httpClient.connected()) {
    _httpActive = false;
    return;
  }

  // State 0: read first request line (non-blocking, one char per call)
  if(_httpState == 0) {
    while(_httpClient.available()) {
      char c = (char)_httpClient.read();
      if(c == '\n') { _httpState = 1; break; }
      if(c != '\r' && _httpReq.length() < 64) _httpReq += c;
    }
    return;
  }

  // State 1: drain remaining HTTP headers
  if(_httpState == 1) {
    while(_httpClient.available()) {
      char c = (char)_httpClient.read();
      (void)c;
    }
    if(!_httpClient.available()) _httpState = 2;
    return;
  }

  // State 2: send response
  if(_httpState == 2) {
    if(_httpReq.startsWith("GET / ") || _httpReq == "GET /") {
      httpSendPage(_httpClient);
#if defined(DEVICE_BRIDGE)
    } else if(_httpReq.startsWith("POST /dtc")) {
      // DTC lesen -- Redirect zurueck zur Hauptseite
      readDTCs();
      _httpClient.print(F("HTTP/1.1 303 See Other\r\nLocation: /\r\nConnection: close\r\n\r\n"));
    } else if(_httpReq.startsWith("POST /cleardtc")) {
      // DTC loeschen -- Redirect zurueck zur Hauptseite
      clearDTCs();
      _httpClient.print(F("HTTP/1.1 303 See Other\r\nLocation: /\r\nConnection: close\r\n\r\n"));
#endif
    } else {
      PP(_httpClient, HTML_404);
    }
    _httpClient.stop();
    _httpActive = false;
  }
}
