#include "ApiServer.hpp"
#include <ArduinoJson.h>
#include <WiFi.h>

namespace cyd {

namespace {
constexpr const char* htmlHead = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>CYD ThermoPro Bridge</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 1rem; max-width: 700px; }
    label { display: block; margin-top: 1rem; font-weight: bold; }
    input, select { width: 100%; padding: 0.5rem; box-sizing: border-box; }
    button { margin-top: 1.5rem; padding: 0.6rem 1.2rem; font-size: 1rem; }
    .note { color: #555; font-size: 0.9rem; margin-top: 0.25rem; }
    .card { background: #f4f4f4; padding: 1rem; border-radius: 0.5rem; margin-top: 1rem; }
    pre { overflow-x: auto; }
  </style>
</head>
<body>
)rawliteral";

constexpr const char* htmlFoot = R"rawliteral(
</body>
</html>
)rawliteral";
} // namespace

ApiServer::ApiServer(SettingsManager& settings, ThermoproBLE& bleClient)
    : server_(80), settings_(settings), bleClient_(bleClient) {
}

void ApiServer::begin(uint16_t port) {
    port_ = port;

    server_.on("/", HTTP_GET, [this]() { handleRoot_(); });
    server_.on("/api/health", HTTP_GET, [this]() { handleHealth_(); });
    server_.on("/api/latest", HTTP_GET, [this]() { handleLatest_(); });
    server_.on("/api/settings", HTTP_GET, [this]() { handleGetSettings_(); });
    server_.on("/api/settings", HTTP_POST, [this]() { handlePostSettings_(); });
    server_.on("/setup", HTTP_POST, [this]() { handleSetupForm_(); });
    server_.onNotFound([this]() { handleNotFound_(); });

    server_.begin(port_);
    Serial.printf("ApiServer: listening on port %d\n", port_);
}

void ApiServer::update() {
    server_.handleClient();
}

void ApiServer::handleRoot_() {
    if (setupMode_) {
        server_.send(200, "text/html", buildSetupPage_());
    } else {
        server_.send(200, "text/html", buildStatusPage_());
    }
}

void ApiServer::handleHealth_() {
    JsonDocument doc;
    doc["ok"] = true;
    doc["deviceId"] = settings_.getDeviceId();
    doc["wifiConnected"] = (WiFi.status() == WL_CONNECTED);
    doc["wifiSsid"] = settings_.getWifiSsid();
    doc["ip"] = WiFi.localIP().toString();
    doc["bleConnected"] = bleClient_.getReading().connected;
    doc["freeHeap"] = ESP.getFreeHeap();

    String body;
    serializeJsonPretty(doc, body);
    server_.send(200, "application/json", body);
}

void ApiServer::handleLatest_() {
    JsonDocument doc;
    JsonObject obj = doc.to<JsonObject>();
    fillLatestJson_(obj);

    String body;
    serializeJsonPretty(doc, body);
    server_.send(200, "application/json", body);
}

void ApiServer::fillLatestJson_(JsonObject& obj) {
    ThermoproReading reading = bleClient_.getReading();
    char targetUnit = settings_.getUnit();

    obj["ok"] = true;
    obj["deviceId"] = settings_.getDeviceId();
    obj["device"] = "ThermoPro TP930";
    obj["mac"] = settings_.getThermoproMac();
    obj["connected"] = reading.connected;
    obj["battery"] = reading.battery;
    obj["unit"] = String(targetUnit);
    obj["probeCount"] = reading.probeCount;

    JsonArray probes = obj["probes"].to<JsonArray>();
    for (size_t i = 0; i < 4; ++i) {
        JsonObject p = probes.add<JsonObject>();
        p["id"] = i + 1;
        p["name"] = settings_.getProbeName(i);

        float raw = reading.rawTemperatures[i];
        if (isValidTemperature(raw)) {
            float display = convertTemperature(raw, reading.deviceUnit, targetUnit);
            p["temperature"] = serialized(String(display, 1).c_str());
            p["connected"] = true;
        } else {
            p["temperature"] = nullptr;
            p["connected"] = false;
        }
    }

    // Timestamps are relative for the device; we add device millis where possible.
    if (reading.lastUpdateMillis > 0) {
        obj["readingTime"] = String(reading.lastUpdateMillis);
    } else {
        obj["readingTime"] = nullptr;
    }
    obj["bridgeTime"] = String(millis());
    obj["source"] = "cyd-bridge";
}

void ApiServer::handleGetSettings_() {
    JsonDocument doc;
    doc["ok"] = true;
    doc["wifiSsid"] = settings_.getWifiSsid();
    doc["thermoproMac"] = settings_.getThermoproMac();
    doc["apiEndpoint"] = settings_.getApiEndpoint();
    // do not expose apiToken
    doc["pollIntervalSec"] = settings_.getPollIntervalSec();
    doc["unit"] = String(settings_.getUnit());
    doc["deviceId"] = settings_.getDeviceId();

    JsonArray names = doc["probeNames"].to<JsonArray>();
    for (size_t i = 0; i < 4; ++i) {
        names.add(settings_.getProbeName(i));
    }

    String body;
    serializeJsonPretty(doc, body);
    server_.send(200, "application/json", body);
}

void ApiServer::handlePostSettings_() {
    String body = server_.arg("plain");
    JsonDocument doc;
    DeserializationError err = deserializeJson(doc, body);

    if (err != DeserializationError::Ok) {
        server_.send(400, "application/json", "{\"ok\":false,\"error\":\"Invalid JSON\"}");
        return;
    }

    bool changed = false;

    if (doc["wifiSsid"].is<String>()) {
        settings_.setWifiSsid(doc["wifiSsid"].as<String>());
        changed = true;
    }
    if (doc["wifiPass"].is<String>()) {
        String pass = doc["wifiPass"].as<String>();
        // Allow empty string to clear the stored password, otherwise keep existing.
        settings_.setWifiPass(pass);
        changed = true;
    }
    if (doc["thermoproMac"].is<String>()) {
        String mac = doc["thermoproMac"].as<String>();
        if (SettingsManager::isValidMac(mac)) {
            settings_.setThermoproMac(mac);
            changed = true;
        } else {
            server_.send(422, "application/json", "{\"ok\":false,\"error\":\"Invalid MAC address\"}");
            return;
        }
    }
    if (doc["apiEndpoint"].is<String>()) {
        settings_.setApiEndpoint(doc["apiEndpoint"].as<String>());
        changed = true;
    }
    if (doc["apiToken"].is<String>()) {
        settings_.setApiToken(doc["apiToken"].as<String>());
        changed = true;
    }
    if (doc["pollIntervalSec"].is<uint16_t>()) {
        settings_.setPollIntervalSec(doc["pollIntervalSec"].as<uint16_t>());
        changed = true;
    }
    if (doc["unit"].is<String>()) {
        String unit = doc["unit"].as<String>();
        settings_.setUnit(unit.length() > 0 ? unit.charAt(0) : 'F');
        changed = true;
    }
    if (doc["deviceId"].is<String>()) {
        settings_.setDeviceId(doc["deviceId"].as<String>());
        changed = true;
    }
    if (doc["probeNames"].is<JsonArray>()) {
        JsonArray arr = doc["probeNames"].as<JsonArray>();
        for (size_t i = 0; i < 4 && i < arr.size(); ++i) {
            settings_.setProbeName(i, arr[i].as<String>());
        }
        changed = true;
    }

    if (changed) {
        settings_.save();
    }

    // Re-read and respond with current settings.
    handleGetSettings_();

    if (rebootCallback_ && (doc["reboot"] | false)) {
        rebootCallback_();
    }
}

void ApiServer::handleNotFound_() {
    server_.send(404, "application/json", "{\"ok\":false,\"error\":\"Not found\"}");
}

void ApiServer::handleSetupForm_() {
    String ssid = server_.arg("ssid");
    String pass = server_.arg("pass");
    String mac = server_.arg("mac");

    if (ssid.length() == 0 || mac.length() == 0) {
        server_.send(400, "text/html", "<h1>Error</h1><p>SSID and MAC are required.</p><a href='/'>Go back</a>");
        return;
    }

    if (!SettingsManager::isValidMac(mac)) {
        server_.send(422, "text/html", "<h1>Error</h1><p>Invalid MAC address format.</p><a href='/'>Go back</a>");
        return;
    }

    settings_.setWifiSsid(ssid);
    settings_.setWifiPass(pass);
    settings_.setThermoproMac(mac);
    settings_.save();

    String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Saved</title>
  <style>body { font-family: system-ui, sans-serif; margin: 1rem; }</style>
</head>
<body>
  <h1>Settings saved</h1>
  <p>The CYD will reboot and connect to your WiFi network.</p>
  <p>If it fails to connect, it will reopen this setup network.</p>
</body>
</html>
)rawliteral";
    server_.send(200, "text/html", html);

    if (rebootCallback_) {
        rebootCallback_();
    }
}

String ApiServer::buildSetupPage_() {
    String page = htmlHead;
    page += "<h1>CYD ThermoPro Setup</h1>\n";
    page += "<p>Connect this CYD to your WiFi network and enter your ThermoPro details.</p>\n";
    page += "<form method=\"POST\" action=\"/setup\" onsubmit=\"return submitForm()\">\n";
    page += R"rawliteral(
  <label>WiFi SSID</label>
  <input type="text" name="wifiSsid" id="wifiSsid" required>
  <label>WiFi Password</label>
  <input type="password" name="wifiPass" id="wifiPass">
  <div class="note">Leave blank to keep the existing password.</div>
  <label>ThermoPro MAC address</label>
  <input type="text" name="thermoproMac" id="thermoproMac" placeholder="C9:48:1D:B1:E1:E7" required>
  <div class="note">Format: AA:BB:CC:DD:EE:FF. Find it with a BLE scanner.</div>
  <label>Device ID</label>
  <input type="text" name="deviceId" id="deviceId" value=")rawliteral";
    page += settings_.getDeviceId();
    page += R"rawliteral(">
  <button type="submit">Save & Connect</button>
</form>
<div id="result" class="card" style="display:none"></div>
<script>
async function submitForm() {
  const result = document.getElementById('result');
  const payload = {
    wifiSsid: document.getElementById('wifiSsid').value,
    wifiPass: document.getElementById('wifiPass').value,
    thermoproMac: document.getElementById('thermoproMac').value,
    deviceId: document.getElementById('deviceId').value,
    reboot: true
  };
  try {
    const res = await fetch('/api/settings', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
    const json = await res.json();
    result.style.display = 'block';
    result.textContent = json.ok
      ? 'Saved. The CYD will restart and join your WiFi now.'
      : 'Error: ' + (json.error || 'unknown');
  } catch (e) {
    result.style.display = 'block';
    result.textContent = 'Error: ' + e.message;
  }
  return false;
}
</script>
)rawliteral";
    page += htmlFoot;
    return page;
}

String ApiServer::buildStatusPage_() {
    String page = htmlHead;
    page += "<h1>CYD ThermoPro Bridge</h1>\n";
    page += "<div class=\"card\">\n";
    page += "  <p><strong>Status page</strong></p>\n";
    page += "  <p>Use the JSON API for programmatic access:</p>\n";
    page += "  <ul>\n";
    page += "    <li><a href=\"/api/health\">GET /api/health</a></li>\n";
    page += "    <li><a href=\"/api/latest\">GET /api/latest</a></li>\n";
    page += "    <li><a href=\"/api/settings\">GET /api/settings</a></li>\n";
    page += "  </ul>\n";
    page += "  <p>POST JSON to <code>/api/settings</code> to change probe names, endpoint, interval, unit, etc.</p>\n";
    page += "</div>\n";
    page += htmlFoot;
    return page;
}

} // namespace cyd
