/*
 * main.cpp
 * MontaukOS Weather app - standalone Window Server process
 * Fetches current weather from wttr.in via HTTPS (BearSSL)
 * Displays temperature, description, feels like, and location
 * Copyright (c) 2026 Daniel Hammer
 */

#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/svg.hpp>
#include <gui/truetype.hpp>
#include <gui/mtk.hpp>
#include <tls/tls.hpp>

extern "C" {
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
}

using namespace gui;
using namespace gui::colors;

// ============================================================================
// Constants
// ============================================================================

static constexpr int INIT_W     = 380;
static constexpr int INIT_H     = 280;
static constexpr int HEADER_H   = 160;
static constexpr int FOOTER_H   = 50;
static constexpr int ICON_SIZE  = 80;
static constexpr int ICON_X     = 28;
static constexpr int ICON_Y     = 40;
static constexpr int INFO_X     = ICON_X + ICON_SIZE + 20;  // 128
static constexpr int TEMP_Y     = 40;
static int           TEMP_SIZE  = 40;
static constexpr int DESC_Y     = 92;
static int           DESC_SIZE  = 17;
static constexpr int FEELS_Y    = 116;
static int           LABEL_SIZE = 15;
static constexpr int RESP_MAX   = 65536;

static const char WTTR_HOST[] = "wttr.in";

// ============================================================================
// App state
// ============================================================================

enum class AppPhase { IDLE, LOADING, DONE, ERR };

static AppPhase g_phase    = AppPhase::IDLE;
static char     g_temp[32]       = {};
static char     g_desc[128]      = {};
static char     g_feels[48]      = {};
static char     g_location[128]  = {};
static char     g_status[256]    = {};
static int      g_win_w          = INIT_W;
static int      g_win_h          = INIT_H;

static char*    g_resp_buf       = nullptr;

// Fonts
static TrueTypeFont* g_font      = nullptr;  // Roboto Medium
static TrueTypeFont* g_font_bold = nullptr;  // Roboto Bold

// Weather icon (loaded per-fetch based on weather code)
static SvgIcon  g_icon           = {nullptr, 0, 0};
static char     g_icon_name[64]  = {};

// TLS state (lazy-init on first fetch)
static bool     g_tls_ready = false;
static uint32_t g_server_ip = 0;
static tls::TrustAnchors g_tas = {nullptr, 0, 0};

// ============================================================================
// HTTP parsing
// ============================================================================

static int find_header_end(const char* buf, int len) {
    for (int i = 0; i + 3 < len; i++)
        if (buf[i]=='\r' && buf[i+1]=='\n' && buf[i+2]=='\r' && buf[i+3]=='\n')
            return i + 4;
    return -1;
}

static int parse_status_code(const char* buf, int len) {
    int i = 0;
    while (i < len && buf[i] != ' ') i++;
    if (i >= len || i + 3 >= len) return -1;
    i++;
    if (buf[i] < '0' || buf[i] > '9') return -1;
    return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0');
}

// ============================================================================
// JSON parsing
// ============================================================================

// Extract value of a simple JSON string field: "key":"value"
static int extract_json_string(const char* buf, int len, const char* key,
                                char* out, int maxOut) {
    int klen = (int)strlen(key);
    for (int i = 0; i < len - klen - 3; i++) {
        if (buf[i] != '"') continue;
        if (memcmp(buf + i + 1, key, klen) != 0) continue;
        if (buf[i + 1 + klen] != '"') continue;
        if (buf[i + 2 + klen] != ':') continue;

        int p = i + 3 + klen;
        while (p < len && (buf[p]==' ' || buf[p]=='\t')) p++;
        if (p >= len || buf[p] != '"') continue;
        p++;

        int j = 0;
        while (p < len && j < maxOut - 1 && buf[p] != '"') {
            if (buf[p] == '\\' && p + 1 < len) { p++; out[j++] = buf[p]; }
            else out[j++] = buf[p];
            p++;
        }
        out[j] = '\0';
        return j;
    }
    out[0] = '\0';
    return 0;
}

// Find the first occurrence of needle in buf[0..len-1]. Returns offset or -1.
static int find_substr(const char* buf, int len, const char* needle) {
    int nlen = (int)strlen(needle);
    if (nlen > len) return -1;
    for (int i = 0; i <= len - nlen; i++) {
        if (memcmp(buf + i, needle, nlen) == 0) return i;
    }
    return -1;
}

// Extract the "value" string within a named JSON array-of-objects field.
// e.g., "weatherDesc":[{"value":"Partly cloudy"}]  →  "Partly cloudy"
// Works by finding section_key first, then the first "value" after it.
static int extract_array_value(const char* buf, int len, const char* section_key,
                                char* out, int maxOut) {
    int pos = find_substr(buf, len, section_key);
    if (pos < 0) { out[0] = '\0'; return 0; }
    return extract_json_string(buf + pos, len - pos, "value", out, maxOut);
}

// Parse a signed integer from a decimal string.
static int parse_int(const char* s) {
    int sign = 1, val = 0, i = 0;
    if (s[0] == '-') { sign = -1; i = 1; }
    while (s[i] >= '0' && s[i] <= '9') val = val * 10 + (s[i++] - '0');
    return sign * val;
}

// ============================================================================
// Weather code → icon filename
// Maps wttr.in WMO-style weather codes to Flat-Remix panel icon names.
// ============================================================================

static const char* weather_code_to_icon(int code) {
    switch (code) {
    case 113:                                                   return "weather-clear.svg";
    case 116:                                                   return "weather-few-clouds.svg";
    case 119:                                                   return "weather-clouds.svg";
    case 122:                                                   return "weather-overcast.svg";
    case 143:                                                   return "weather-mist.svg";
    case 248: case 260:                                         return "weather-fog.svg";
    case 176: case 263: case 266: case 353:                     return "weather-showers-scattered.svg";
    case 293: case 296: case 299: case 302:
    case 305: case 308: case 356: case 359:                     return "weather-showers.svg";
    case 179: case 362: case 365: case 368:                     return "weather-snow-scattered.svg";
    case 323: case 326: case 329: case 332:
    case 335: case 338: case 371: case 374:                     return "weather-snow.svg";
    case 227: case 230:                                         return "weather-snow.svg";
    case 182: case 311: case 314: case 317: case 320:           return "weather-snow-rain.svg";
    case 185: case 281: case 284:                               return "weather-freezing-rain.svg";
    case 350: case 377:                                         return "weather-hail.svg";
    case 200: case 386: case 389: case 392: case 395:           return "weather-storm.svg";
    default:                                                    return "weather-none-available.svg";
    }
}

// Load the weather icon for the given icon filename (caches the last result).
static void load_weather_icon(const char* icon_name) {
    if (strcmp(g_icon_name, icon_name) == 0 && g_icon.pixels) return;

    if (g_icon.pixels) svg_free(g_icon);

    static char path[80];
    snprintf(path, sizeof(path), "0:/icons/%s", icon_name);
    // ColorScheme-Text paths (main shape) use this fill_color; accent classes
    // (ColorScheme-Highlight=blue, ColorScheme-NeutralText=yellow, etc.) use their
    // CSS-defined colors as parsed from the SVG's <style> block by svg_load.
    static constexpr Color ICON_FG = Color::from_rgb(0x5C, 0x61, 0x6C); // dark neutral
    g_icon = svg_load(path, ICON_SIZE, ICON_SIZE, ICON_FG);

    int i = 0;
    while (icon_name[i] && i < 63) { g_icon_name[i] = icon_name[i]; i++; }
    g_icon_name[i] = '\0';
}

// ============================================================================
// UI scale
// ============================================================================

static void apply_scale(int scale) {
    switch (scale) {
    case 0: TEMP_SIZE=32; DESC_SIZE=14; LABEL_SIZE=12; break;
    case 2: TEMP_SIZE=50; DESC_SIZE=21; LABEL_SIZE=19; break;
    default: TEMP_SIZE=40; DESC_SIZE=17; LABEL_SIZE=15; break;
    }
}

// ============================================================================
// Network fetch  (blocking — called from the event loop)
// ============================================================================

static void do_fetch() {
    // Lazy init: resolve DNS and load CA certificates once
    if (!g_tls_ready) {
        g_server_ip = montauk::resolve(WTTR_HOST);
        if (g_server_ip == 0) {
            snprintf(g_status, sizeof(g_status),
                     "Error: could not resolve %s", WTTR_HOST);
            g_phase = AppPhase::ERR; return;
        }
        g_tas = tls::load_trust_anchors();
        if (g_tas.count == 0) {
            snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
            g_phase = AppPhase::ERR; return;
        }
        g_tls_ready = true;
    }

    static char request[512];
    int reqLen = snprintf(request, sizeof(request),
        "GET /?format=j1 HTTP/1.0\r\n"
        "Host: %s\r\n"
        "User-Agent: MontaukOS/1.0 weather\r\n"
        "Accept: application/json\r\n"
        "Connection: close\r\n"
        "\r\n",
        WTTR_HOST);

    int respLen = tls::https_fetch(WTTR_HOST, g_server_ip, 443,
                                   request, reqLen, g_tas,
                                   g_resp_buf, RESP_MAX);
    if (respLen <= 0) {
        snprintf(g_status, sizeof(g_status), "Error: no response from server");
        g_phase = AppPhase::ERR; return;
    }
    g_resp_buf[respLen] = '\0';

    int headerEnd = find_header_end(g_resp_buf, respLen);
    if (headerEnd < 0) {
        snprintf(g_status, sizeof(g_status), "Error: malformed HTTP response");
        g_phase = AppPhase::ERR; return;
    }

    int status = parse_status_code(g_resp_buf, headerEnd);
    if (status != 200) {
        snprintf(g_status, sizeof(g_status), "Error: HTTP %d from server", status);
        g_phase = AppPhase::ERR; return;
    }

    const char* body   = g_resp_buf + headerEnd;
    int          bodyLen = respLen - headerEnd;

    // Extract core weather fields
    static char temp_raw[16], feels_raw[16], code_raw[8];
    extract_json_string(body, bodyLen, "temp_C",      temp_raw,  sizeof(temp_raw));
    extract_json_string(body, bodyLen, "FeelsLikeC",  feels_raw, sizeof(feels_raw));
    extract_json_string(body, bodyLen, "weatherCode", code_raw,  sizeof(code_raw));

    extract_array_value(body, bodyLen, "\"weatherDesc\"", g_desc, sizeof(g_desc));

    static char area[64], country[64];
    extract_array_value(body, bodyLen, "\"areaName\"", area,    sizeof(area));
    extract_array_value(body, bodyLen, "\"country\"",  country, sizeof(country));

    // Degree sign is U+00B0 = 0xB0 in Latin-1 (single byte, within the 256-entry glyph cache)
    snprintf(g_temp,  sizeof(g_temp),  "%s\xb0""C", temp_raw);
    snprintf(g_feels, sizeof(g_feels), "Feels like: %s\xb0""C", feels_raw);

    if (area[0] && country[0])
        snprintf(g_location, sizeof(g_location), "%s, %s", area, country);
    else if (area[0])
        snprintf(g_location, sizeof(g_location), "%s", area);
    else
        snprintf(g_location, sizeof(g_location), "Unknown location");

    // Load matching weather icon
    int code = parse_int(code_raw);
    load_weather_icon(weather_code_to_icon(code));

    g_phase = AppPhase::DONE;
}

// ============================================================================
// Theme colors
// ============================================================================

static constexpr Color ERR_TEXT   = Color::from_rgb(0xCC, 0x22, 0x22);

static mtk::Theme weather_theme() {
    mtk::Theme theme = mtk::make_theme();
    theme.window_bg = WHITE;
    theme.surface = Color::from_rgb(0xF5, 0xF5, 0xF5);
    theme.surface_alt = Color::from_rgb(0xF8, 0xF8, 0xF8);
    theme.surface_hover = mtk::mix(theme.surface, theme.accent, 18);
    theme.border = Color::from_rgb(0xCC, 0xCC, 0xCC);
    theme.text = Color::from_rgb(0x33, 0x33, 0x33);
    theme.text_muted = Color::from_rgb(0x88, 0x88, 0x88);
    theme.text_subtle = Color::from_rgb(0x99, 0x99, 0x99);
    return theme;
}

// ============================================================================
// Rendering
// ============================================================================

static constexpr int BTN_W = 110;
static constexpr int BTN_H = 30;

static int g_mouse_x = -1;
static int g_mouse_y = -1;

static Rect refresh_button_rect() {
    int btn_x = (g_win_w - BTN_W) / 2;
    int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
    return {btn_x, btn_y, BTN_W, BTN_H};
}

static void render(Canvas& canvas) {
    mtk::Theme theme = weather_theme();
    Rect btn = refresh_button_rect();

    // ── Background ───────────────────────────────────────────────────────────
    canvas.fill_rect(0, 0, g_win_w, g_win_h - FOOTER_H, theme.window_bg);
    canvas.fill_rect(0, g_win_h - FOOTER_H, g_win_w, FOOTER_H, theme.surface);

    // Divider between content and location strip
    canvas.hline(0, HEADER_H, g_win_w, theme.border);
    // Divider above footer
    canvas.hline(0, g_win_h - FOOTER_H, g_win_w, theme.border);

    if (!g_font) return;

    // ── Main content area (y=0..HEADER_H) ────────────────────────────────────
    if (g_phase == AppPhase::LOADING) {
        draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
                  "Fetching weather data...", theme.text_subtle, 18);

    } else if (g_phase == AppPhase::ERR) {
        draw_text(canvas, g_font, 20, 20, g_status, ERR_TEXT, 15);

    } else if (g_phase == AppPhase::IDLE) {
        draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
                  "Click Refresh to check weather.", theme.text_subtle, 18);

    } else {  // DONE
        // Weather icon
        draw_icon(canvas, ICON_X, ICON_Y, g_icon);

        // Temperature (large bold)
        TrueTypeFont* temp_font = g_font_bold ? g_font_bold : g_font;
        draw_text(canvas, temp_font, INFO_X, TEMP_Y, g_temp, theme.text, TEMP_SIZE);

        // Weather description
        draw_text(canvas, g_font, INFO_X, DESC_Y, g_desc, theme.text, DESC_SIZE);

        // Feels like
        draw_text(canvas, g_font, INFO_X, FEELS_Y, g_feels, theme.text_muted, LABEL_SIZE);
    }

    // ── Location strip (y=HEADER_H..g_win_h-FOOTER_H) ────────────────────────
    if (g_phase == AppPhase::DONE) {
        draw_text(canvas, g_font, 20, HEADER_H + 14, g_location, theme.text, LABEL_SIZE);
    }

    // ── Refresh button (rounded, in footer) ───────────────────────────────────
    bool hovered = btn.contains(g_mouse_x, g_mouse_y);
    bool loading = (g_phase == AppPhase::LOADING);
    mtk::WidgetState state = mtk::widget_state(false, hovered, !loading);
    mtk::draw_button(canvas, btn, loading ? "Loading..." : "Refresh",
                    mtk::BUTTON_PRIMARY, state, theme);
}

// ============================================================================
// Entry point
// ============================================================================

extern "C" void _start() {
    if (!fonts::init())
        montauk::exit(1);

    // Allocate response buffer from heap
    g_resp_buf = (char*)malloc(RESP_MAX + 1);
    if (!g_resp_buf) montauk::exit(1);

    // Load fonts
    auto load_font = [](const char* path) -> TrueTypeFont* {
        TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
        if (!f) return nullptr;
        montauk::memset(f, 0, sizeof(TrueTypeFont));
        if (!f->init(path)) { montauk::mfree(f); return nullptr; }
        return f;
    };
    g_font      = load_font("0:/fonts/Roboto-Medium.ttf");
    g_font_bold = load_font("0:/fonts/Roboto-Bold.ttf");
    if (!g_font) montauk::exit(1);

    apply_scale(montauk::win_getscale());

    WsWindow win;
    if (!win.create("Weather", INIT_W, INIT_H))
        montauk::exit(1);

    mtk::StandaloneHost host(&win);
    Canvas canvas = host.canvas();

    // Initial fetch on startup
    g_phase = AppPhase::LOADING;
    render(canvas);
    host.present();
    do_fetch();

    // Event loop
    while (true) {
        Montauk::WinEvent ev;
        int r = win.poll(&ev);

        if (r < 0) break;

        if (r == 0) {
            montauk::sleep_ms(16);
            canvas = host.canvas();
            render(canvas);
            host.present();
            continue;
        }

        if (ev.type == 3) break;  // close

        if (ev.type == 4) {
            apply_scale(win.scale_factor);
        }

        if (ev.type == 2) {
            g_win_w = win.width;
            g_win_h = win.height;
            canvas = host.canvas();
        }

        if (ev.type == 1) {
            // Track mouse position for hover states
            g_mouse_x = ev.mouse.x;
            g_mouse_y = ev.mouse.y;

            // Mouse — check for Refresh button click
            bool just_clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
            if (just_clicked && g_phase != AppPhase::LOADING) {
                Rect btn = refresh_button_rect();
                int mx = ev.mouse.x, my = ev.mouse.y;
                if (btn.contains(mx, my)) {
                    g_phase = AppPhase::LOADING;
                    canvas = host.canvas();
                    render(canvas);
                    host.present();
                    do_fetch();
                }
            }
        }

        canvas = host.canvas();
        render(canvas);
        host.present();
    }

    if (g_icon.pixels) svg_free(g_icon);
    win.destroy();
    montauk::exit(0);
}
