#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <Preferences.h>
#include <LittleFS.h>
#include <qrcode.h>
#include "config.h"
#include "epd.h"

// ── Globals ──────────────────────────────────────────────────────────────────

static WebServer  server(80);
static DNSServer  dns;
static Preferences prefs;

static bool  g_provisioning  = false;
static bool  g_connect_req   = false;
static String g_req_ssid;
static String g_req_pass;

// ── QR helpers ───────────────────────────────────────────────────────────────

static void show_ap_qr(const String& apSsid) {
    // Encode open WiFi network so phone can auto-join
    String content = "WIFI:S:" + apSsid + ";T:nopass;;";

    QRCode qr;
    uint8_t buf[qrcode_getBufferSize(5)];
    qrcode_initText(&qr, buf, 5, ECC_LOW, content.c_str());
    epd_draw_qr(&qr, 8, COLOR_WHITE, COLOR_BLACK);
}

static void show_setup_qr(const String& mac) {
    String url = String(APP_BASE_URL) + "/setup/" + mac;

    QRCode qr;
    uint8_t buf[qrcode_getBufferSize(6)];
    qrcode_initText(&qr, buf, 6, ECC_LOW, url.c_str());
    // Green background indicates success
    epd_draw_qr(&qr, 7, COLOR_GREEN, COLOR_BLACK);
}

// ── Captive portal HTML ───────────────────────────────────────────────────────

static const char PORTAL_HTML[] PROGMEM = R"html(
<!DOCTYPE html><html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>pictureFrame Setup</title>
<style>
  body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;
       min-height:100vh;margin:0;background:#fdf6ee;color:#3a2e22}
  .card{width:100%;max-width:340px;margin:1rem;padding:1.5rem;background:#fff9f2;
        border:1px solid #e8d9c4;border-radius:16px}
  h1{font-size:1.25rem;margin:0 0 1.25rem}
  label{display:block;font-size:.8125rem;font-weight:600;color:#8a7060;margin-bottom:.3rem}
  input{width:100%;min-height:44px;padding:0 .875rem;border:1px solid #e8d9c4;
        border-radius:10px;background:#fff;font-size:1rem;color:#3a2e22;box-sizing:border-box;margin-bottom:1rem}
  button{width:100%;min-height:44px;background:#c97c3a;color:#fff;border:none;
         border-radius:9999px;font-size:1rem;font-weight:700;cursor:pointer}
  p{font-size:.875rem;color:#8a7060;margin-top:1rem;text-align:center}
</style>
</head>
<body>
<div class="card">
  <h1>Connect to WiFi</h1>
  <form method="POST" action="/connect">
    <label for="s">WiFi network name</label>
    <input id="s" name="ssid" type="text" autocomplete="off" placeholder="e.g. HomeNetwork">
    <label for="p">Password</label>
    <input id="p" name="pass" type="password" autocomplete="off">
    <button type="submit">Connect</button>
  </form>
  <p>pictureFrame will join your network and display a setup QR code.</p>
</div>
</body></html>
)html";

static const char CONNECTING_HTML[] PROGMEM = R"html(
<!DOCTYPE html><html><head><meta charset="UTF-8">
<title>Connecting…</title>
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;
min-height:100vh;margin:0;background:#fdf6ee;color:#3a2e22;text-align:center}
.card{padding:2rem;max-width:300px}</style></head>
<body><div class="card"><h2>Connecting…</h2>
<p>The frame is joining your network.<br>Watch the display for a setup QR code.</p></div></body></html>
)html";

// ── Web server handlers ───────────────────────────────────────────────────────

static void handle_root() {
    server.send_P(200, "text/html", PORTAL_HTML);
}

static void handle_connect() {
    if (!server.hasArg("ssid") || server.arg("ssid").isEmpty()) {
        server.send(400, "text/plain", "Missing ssid");
        return;
    }
    g_req_ssid = server.arg("ssid");
    g_req_pass = server.arg("pass");
    g_connect_req = true;
    server.send_P(200, "text/html", CONNECTING_HTML);
}

// Captive portal detection endpoints — all redirect to portal
static void handle_captive() {
    server.sendHeader("Location", "http://" AP_IP "/");
    server.send(302, "text/plain", "");
}

// ── WiFi provisioning ─────────────────────────────────────────────────────────

static void enter_provisioning(const String& mac) {
    // Derive AP name from last 4 hex chars of MAC (no colons)
    String suffix = mac;
    suffix.replace(":", "");
    suffix = suffix.substring(suffix.length() - 4);
    suffix.toUpperCase();
    String apSsid = "PictureFrame-" + suffix;

    Serial.println("AP: " + apSsid);

    WiFi.disconnect(true);
    WiFi.mode(WIFI_AP);
    WiFi.softAP(apSsid.c_str());
    delay(500);

    // Redirect all DNS to this device
    dns.setErrorReplyCode(DNSReplyCode::NoError);
    dns.start(53, "*", WiFi.softAPIP());

    server.on("/",                     HTTP_GET,  handle_root);
    server.on("/connect",              HTTP_POST, handle_connect);
    server.on("/generate_204",         handle_captive);
    server.on("/hotspot-detect.html",  handle_captive);
    server.on("/ncsi.txt",             handle_captive);
    server.onNotFound(handle_root);
    server.begin();

    epd_init();
    show_ap_qr(apSsid);
    epd_sleep();

    g_provisioning = true;
}

static bool attempt_wifi(const String& ssid, const String& pass) {
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid.c_str(), pass.c_str());

    uint32_t start = millis();
    while (WiFi.status() != WL_CONNECTED) {
        if (millis() - start > WIFI_TIMEOUT_MS) return false;
        delay(200);
    }
    return true;
}

// ── Normal operation ──────────────────────────────────────────────────────────

static void normal_operation(const String& mac) {
    String url = String(APP_BASE_URL) + "/api/device/" + mac + "/image";

    WiFiClientSecure client;
    client.setInsecure();  // V1: no cert pinning for personal-scale device

    HTTPClient http;
    http.begin(client, url);
    int code = http.GET();

    epd_init();

    if (code == 200) {
        // Stream new image to LittleFS and display it
        File f = LittleFS.open(IMAGE_PATH, "w", true);
        if (f) {
            http.writeToStream(&f);
            f.close();
        }
        File r = LittleFS.open(IMAGE_PATH, "r");
        if (r) {
            epd_draw_image_from_file(r);
            r.close();
        }
    } else if (code == 204) {
        // No new image — display whatever is stored
        File r = LittleFS.open(IMAGE_PATH, "r");
        if (r) {
            epd_draw_image_from_file(r);
            r.close();
        }
        // No stored image yet: keep current display (e-ink is persistent)
    } else if (code == 404) {
        // Device not registered — show setup QR so user can link this device
        show_setup_qr(mac);
    } else {
        // Server error / timeout: yellow border indication (server reachable, sync failed)
        epd_fill(COLOR_YELLOW);
    }

    http.end();
    epd_sleep();

    // Deep sleep until next poll
    esp_sleep_enable_timer_wakeup((uint64_t)FETCH_INTERVAL_MS * 1000ULL);
    esp_deep_sleep_start();
}

// ── Setup ─────────────────────────────────────────────────────────────────────

void setup() {
    Serial.begin(115200);
    Serial.println("pictureFrame boot");

    // Init GPIO
    pinMode(PIN_CS,         OUTPUT);
    pinMode(PIN_DC,         OUTPUT);
    pinMode(PIN_RST,        OUTPUT);
    pinMode(PIN_BUSY,       INPUT);
    pinMode(PIN_BTN_RESET,  INPUT_PULLUP);

    SPI.begin(PIN_SCK, -1, PIN_MOSI, PIN_CS);
    SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));

    LittleFS.begin(true);  // format on first use

    // Check reset button: if held at boot, wipe credentials
    uint32_t hold_start = millis();
    bool clear_creds = false;
    while (digitalRead(PIN_BTN_RESET) == LOW) {
        if (millis() - hold_start >= RESET_HOLD_MS) {
            clear_creds = true;
            break;
        }
        delay(50);
    }

    prefs.begin(NVS_NAMESPACE, false);
    if (clear_creds) {
        prefs.clear();
        Serial.println("Credentials cleared — entering provisioning");
    }

    String mac = WiFi.macAddress();
    String ssid = prefs.getString(NVS_KEY_SSID, "");
    String pass = prefs.getString(NVS_KEY_PASS, "");
    prefs.end();

    if (ssid.isEmpty()) {
        enter_provisioning(mac);
        return;
    }

    // Attempt to join saved network
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid.c_str(), pass.c_str());
    uint32_t start = millis();
    while (WiFi.status() != WL_CONNECTED) {
        if (millis() - start > WIFI_TIMEOUT_MS) break;
        delay(200);
    }

    if (WiFi.status() == WL_CONNECTED) {
        normal_operation(mac);
        // normal_operation calls deep_sleep — never returns
    } else {
        // Can't reach saved network — enter provisioning to get new credentials
        enter_provisioning(mac);
    }
}

// ── Loop (provisioning mode only) ────────────────────────────────────────────

void loop() {
    if (!g_provisioning) return;

    dns.processNextRequest();
    server.handleClient();

    if (!g_connect_req) return;
    g_connect_req = false;

    dns.stop();
    server.stop();

    String mac = WiFi.macAddress();
    bool ok = attempt_wifi(g_req_ssid, g_req_pass);

    if (ok) {
        // Save credentials for future boots
        prefs.begin(NVS_NAMESPACE, false);
        prefs.putString(NVS_KEY_SSID, g_req_ssid);
        prefs.putString(NVS_KEY_PASS, g_req_pass);
        prefs.end();

        // Show Phase 2 QR and transition to polling loop
        epd_init();
        show_setup_qr(mac);
        epd_sleep();

        g_provisioning = false;

        // Give user time to scan the QR, then start normal operation
        delay(120000);  // 2 minutes
        normal_operation(mac);
    } else {
        // Connection failed — fill red, restart AP
        epd_init();
        epd_fill(COLOR_RED);
        epd_sleep();
        delay(3000);

        // Re-enter provisioning to retry
        enter_provisioning(mac);
    }
}
