#include "viz/agent_runs_timeline.h"
#include "viz/agent_runs_timeline_helpers.h"
#include "core/icons_tabler.h"
#include "core/tokens.h"

#include "imgui.h"

#include <algorithm>
#include <cstdio>
#include <ctime>
#include <unordered_map>

namespace fn_viz {

namespace {

// Map color token -> fn_tokens color. Mirrors timeline::status_color_token.
ImVec4 token_to_color(int t) {
    using namespace fn_tokens::colors;
    switch (t) {
        case 1: return info;
        case 2: return success;
        case 3: return primary;
        case 5: return warning;
        case 6: return error;
        case 0:
        default: return text_muted;
    }
}

// Parse "#rrggbb" to ImVec4. On bad input returns text_muted.
ImVec4 parse_hex(const std::string& h) {
    if (h.size() != 7 || h[0] != '#') return fn_tokens::colors::text_muted;
    auto hex2 = [](const std::string& s, size_t i) -> int {
        int v = 0;
        for (int k = 0; k < 2; ++k) {
            char c = s[i + k];
            int d = (c >= '0' && c <= '9') ? c - '0'
                  : (c >= 'a' && c <= 'f') ? 10 + c - 'a'
                  : (c >= 'A' && c <= 'F') ? 10 + c - 'A'
                  : -1;
            if (d < 0) return -1;
            v = v * 16 + d;
        }
        return v;
    };
    int r = hex2(h, 1), g = hex2(h, 3), b = hex2(h, 5);
    if (r < 0 || g < 0 || b < 0) return fn_tokens::colors::text_muted;
    return ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
}

void draw_app_chip(const std::string& app) {
    ImVec4 c = parse_hex(timeline::app_chip_hex(app));
    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(c.x, c.y, c.z, 0.20f));
    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(c.x, c.y, c.z, 0.30f));
    ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(c.x, c.y, c.z, 0.40f));
    ImGui::PushStyleColor(ImGuiCol_Text, c);
    ImGui::SmallButton(app.empty() ? "?" : app.c_str());
    ImGui::PopStyleColor(4);
}

void draw_dod_badge(int done, int validated, int total) {
    char buf[48];
    std::snprintf(buf, sizeof(buf), "%d/%d/%d", done, validated, total);
    ImVec4 c = (validated >= total && total > 0)
        ? fn_tokens::colors::success
        : (done > 0 ? fn_tokens::colors::info : fn_tokens::colors::text_muted);
    ImGui::TextColored(c, "%s", buf);
}

std::string fmt_started(int64_t ts) {
    if (ts <= 0) return "—";
    std::time_t t = (std::time_t)ts;
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);
#else
    localtime_r(&t, &tm);
#endif
    char buf[32];
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", &tm);
    return std::string(buf);
}

void draw_filters(TimelineState& state,
                  const std::vector<std::string>& known_apps,
                  const std::vector<std::string>& known_statuses) {
    if (ImGui::Button(TI_FILTER " Apps")) ImGui::OpenPopup("##apps_filter");
    if (ImGui::BeginPopup("##apps_filter")) {
        for (const auto& a : known_apps) {
            bool sel = false;
            for (const auto& s : state.filter.apps) if (s == a) { sel = true; break; }
            if (ImGui::Checkbox(a.c_str(), &sel)) {
                if (sel) state.filter.apps.push_back(a);
                else {
                    state.filter.apps.erase(
                        std::remove(state.filter.apps.begin(), state.filter.apps.end(), a),
                        state.filter.apps.end());
                }
            }
        }
        ImGui::EndPopup();
    }
    ImGui::SameLine();
    if (ImGui::Button(TI_FILTER " Status")) ImGui::OpenPopup("##status_filter");
    if (ImGui::BeginPopup("##status_filter")) {
        for (const auto& s : known_statuses) {
            bool sel = false;
            for (const auto& x : state.filter.statuses) if (x == s) { sel = true; break; }
            if (ImGui::Checkbox(s.c_str(), &sel)) {
                if (sel) state.filter.statuses.push_back(s);
                else {
                    state.filter.statuses.erase(
                        std::remove(state.filter.statuses.begin(), state.filter.statuses.end(), s),
                        state.filter.statuses.end());
                }
            }
        }
        ImGui::EndPopup();
    }
    ImGui::SameLine();
    int days = (state.filter.since_ts == 0)
        ? 0
        : (int)((std::time(nullptr) - state.filter.since_ts) / 86400);
    if (ImGui::InputInt("Since (days)", &days, 1, 7)) {
        if (days <= 0) state.filter.since_ts = 0;
        else state.filter.since_ts = std::time(nullptr) - (int64_t)days * 86400;
    }
}

void draw_connection_badge(const std::string& status) {
    ImVec4 c;
    const char* sym;
    if (status == "connected") {
        c = fn_tokens::colors::success;
        sym = "\xe2\x97\x8f";  // ● U+25CF
    } else if (status == "connecting") {
        c = fn_tokens::colors::warning;
        sym = "\xe2\x97\x90"; // ◐
    } else {
        c = fn_tokens::colors::error;
        sym = "\xe2\x97\x8b";  // ○ U+25CB
    }
    ImGui::TextColored(c, "%s %s", sym,
        status.empty() ? "disconnected" : status.c_str());
}

}  // namespace

void render_agent_runs_timeline(TimelineState& state) {
    // Snapshot under the lock so the rest of the frame is lock-free.
    std::vector<AgentRun> runs_copy;
    std::string conn_copy;
    {
        std::lock_guard<std::mutex> lk(state.runs_mutex);
        runs_copy = state.runs;
        conn_copy = state.connection_status;
    }

    // Build "known" sets from the runs themselves so the dropdowns reflect
    // whatever the source emits, even if it grows beyond the docs list.
    std::vector<std::string> known_apps;
    std::vector<std::string> known_statuses;
    {
        std::unordered_map<std::string, int> apps_seen, status_seen;
        for (const auto& r : runs_copy) {
            if (!r.app.empty() && !apps_seen[r.app]++) known_apps.push_back(r.app);
            if (!r.status.empty() && !status_seen[r.status]++) known_statuses.push_back(r.status);
        }
    }

    draw_filters(state, known_apps, known_statuses);
    ImGui::SameLine();
    ImGui::Dummy(ImVec2(16, 0));
    ImGui::SameLine();
    draw_connection_badge(conn_copy);

    ImGui::Separator();

    auto rows = timeline::filter_and_sort(runs_copy, state.filter);

    if (ImGui::BeginTable("##agent_runs_table", 7,
            ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders |
            ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
        ImGui::TableSetupColumn("",         ImGuiTableColumnFlags_WidthFixed, 24.0f);
        ImGui::TableSetupColumn("app",      ImGuiTableColumnFlags_WidthFixed, 130.0f);
        ImGui::TableSetupColumn("issue/card",ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("branch",   ImGuiTableColumnFlags_WidthStretch);
        ImGui::TableSetupColumn("dod",      ImGuiTableColumnFlags_WidthFixed, 70.0f);
        ImGui::TableSetupColumn("duration", ImGuiTableColumnFlags_WidthFixed, 90.0f);
        ImGui::TableSetupColumn("started",  ImGuiTableColumnFlags_WidthFixed, 130.0f);
        ImGui::TableHeadersRow();

        for (const auto& r : rows) {
            ImGui::TableNextRow();

            // Whole-row highlight + selectable via the first cell spanning.
            ImGui::TableSetColumnIndex(0);
            bool selected = (state.selected_run_id == r.id);
            ImGui::PushID(r.id.c_str());

            // Icon coloured by status.
            ImVec4 c = token_to_color(timeline::status_color_token(r.status));
            ImGui::TextColored(c, "%s", timeline::status_icon_id(r.status).c_str());

            // Make the row clickable: invisible button spanning all columns.
            // Simpler — use Selectable on cell 1 with SpanAllColumns.
            ImGui::TableSetColumnIndex(1);
            char label[96];
            std::snprintf(label, sizeof(label), "##row_%s", r.id.c_str());
            if (ImGui::Selectable(label, selected,
                    ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
                state.selected_run_id = r.id;
                if (state.on_select) state.on_select(r.id);
            }
            ImGui::SameLine();
            draw_app_chip(r.app);

            ImGui::TableSetColumnIndex(2);
            if (!r.issue_id.empty() && !r.card_id.empty()) {
                ImGui::Text("%s / %s", r.issue_id.c_str(), r.card_id.c_str());
            } else if (!r.issue_id.empty()) {
                ImGui::TextUnformatted(r.issue_id.c_str());
            } else {
                ImGui::TextUnformatted(r.card_id.c_str());
            }

            ImGui::TableSetColumnIndex(3);
            ImGui::TextUnformatted(r.branch.c_str());

            ImGui::TableSetColumnIndex(4);
            draw_dod_badge(r.dod_done, r.dod_validated, r.dod_total);

            ImGui::TableSetColumnIndex(5);
            std::string dur = timeline::format_duration(r.started_at, r.finished_at);
            ImGui::TextColored(
                r.finished_at == 0 ? fn_tokens::colors::info : fn_tokens::colors::text,
                "%s", dur.c_str());

            ImGui::TableSetColumnIndex(6);
            ImGui::TextUnformatted(fmt_started(r.started_at).c_str());

            ImGui::PopID();
        }

        ImGui::EndTable();
    }

    // Footer counts per status (over the FULL set, not the filtered one — gives
    // a global sense even when filters narrow the table).
    ImGui::Separator();
    std::unordered_map<std::string, int> counts;
    for (const auto& r : runs_copy) counts[r.status]++;
    bool first = true;
    for (const auto& s : {"pending", "running", "done", "validated", "merged", "aborted", "failed"}) {
        int n = counts[s];
        if (!first) { ImGui::SameLine(); ImGui::TextUnformatted("  "); ImGui::SameLine(); }
        first = false;
        ImVec4 c = token_to_color(timeline::status_color_token(s));
        ImGui::TextColored(c, "%s: %d", s, n);
    }
}

void poll_sse_runs(TimelineState& state) {
    // STUB. See header comment + agent_runs_timeline.md ## Gotchas.
    //
    // Intended future wiring:
    //   1. spawn background thread with libcurl multi handle on state.sse_url
    //      - parse "data:" lines, json-decode AgentRun events
    //      - lock state.runs_mutex; upsert by run.id; set connection_status
    //   2. OR call fn_core::http_request("GET", url + "?since=...", ...)
    //      every N seconds and full-refresh state.runs
    //
    // For now: noop so that consumers that wire poll_sse_runs() in their
    // main loop don't crash. Consumers populate state.runs manually
    // (fixtures, in-process orchestrator) until a real SSE issue lands.
    (void)state;
}

}  // namespace fn_viz
