#include "data.h"

#include <atomic>
#include <chrono>
#include <memory>
#include <thread>

#include "core/http_request.h"
#include "core/sse_client.h"
#include "core/logger.h"
#include <nlohmann/json.hpp>

namespace kanban {

using json = nlohmann::json;

State& state() {
    static State s;
    return s;
}

static std::unique_ptr<fn_sse::Client> g_sse;

static std::vector<std::string> j_arr(const json& j, const char* key) {
    std::vector<std::string> out;
    if (!j.contains(key) || !j[key].is_array()) return out;
    for (const auto& v : j[key]) {
        if (v.is_string()) out.push_back(v.get<std::string>());
    }
    return out;
}

static std::string j_str(const json& j, const char* key) {
    if (!j.contains(key) || !j[key].is_string()) return "";
    return j[key].get<std::string>();
}

static Issue parse_issue(const json& j) {
    Issue i;
    i.id        = j_str(j, "id");
    i.title     = j_str(j, "title");
    i.status    = j_str(j, "status");
    i.type      = j_str(j, "type");
    i.scope     = j_str(j, "scope");
    i.priority  = j_str(j, "priority");
    i.domain    = j_arr(j, "domain");
    i.tags      = j_arr(j, "tags");
    i.depends   = j_arr(j, "depends");
    i.blocks    = j_arr(j, "blocks");
    i.related   = j_arr(j, "related");
    i.flow      = j_str(j, "flow");
    i.file_path = j_str(j, "file_path");
    if (j.contains("completed") && j["completed"].is_boolean())
        i.completed = j["completed"].get<bool>();
    i.body = j_str(j, "body");
    return i;
}

static Flow parse_flow(const json& j) {
    Flow f;
    f.id        = j_str(j, "id");
    f.title     = j_str(j, "title");
    f.status    = j_str(j, "status");
    f.kind      = j_str(j, "kind");
    f.tags      = j_arr(j, "tags");
    f.file_path = j_str(j, "file_path");
    f.body      = j_str(j, "body");
    return f;
}

static fn_http::Response http_get(const std::string& path) {
    fn_http::Request req;
    req.method     = "GET";
    req.url        = state().backend_url + path;
    req.timeout_ms = 5000;
    return fn_http::request(req);
}

static fn_http::Response http_patch_json(const std::string& path, const std::string& body) {
    fn_http::Request req;
    req.method     = "PATCH";
    req.url        = state().backend_url + path;
    req.timeout_ms = 5000;
    req.headers.push_back({"Content-Type", "application/json"});
    req.body       = body;
    return fn_http::request(req);
}

static fn_http::Response http_post_json(const std::string& path, const std::string& body) {
    fn_http::Request req;
    req.method     = "POST";
    req.url        = state().backend_url + path;
    req.timeout_ms = 10000;
    req.headers.push_back({"Content-Type", "application/json"});
    req.body       = body;
    return fn_http::request(req);
}

bool refresh_issues() {
    auto resp = http_get("/api/issues");
    if (resp.status != 200) {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_error = "issues fetch: " + std::to_string(resp.status) + " " + resp.error;
        return false;
    }
    auto j = json::parse(resp.body, nullptr, false);
    if (j.is_discarded() || !j.is_array()) {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_error = "issues: invalid JSON";
        return false;
    }
    std::vector<Issue> out;
    out.reserve(j.size());
    for (const auto& it : j) out.push_back(parse_issue(it));
    std::lock_guard<std::mutex> g(state().mu);
    state().issues          = std::move(out);
    state().last_refresh_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
        std::chrono::steady_clock::now().time_since_epoch()).count();
    state().last_error.clear();
    return true;
}

bool refresh_flows() {
    auto resp = http_get("/api/flows");
    if (resp.status != 200) {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_error = "flows fetch: " + std::to_string(resp.status);
        return false;
    }
    auto j = json::parse(resp.body, nullptr, false);
    if (j.is_discarded() || !j.is_array()) return false;
    std::vector<Flow> out;
    out.reserve(j.size());
    for (const auto& it : j) out.push_back(parse_flow(it));
    std::lock_guard<std::mutex> g(state().mu);
    state().flows = std::move(out);
    return true;
}

bool refresh_meta() {
    auto resp = http_get("/api/meta");
    if (resp.status != 200) return false;
    auto j = json::parse(resp.body, nullptr, false);
    if (j.is_discarded()) return false;
    std::lock_guard<std::mutex> g(state().mu);
    state().meta.statuses   = j_arr(j, "statuses");
    state().meta.priorities = j_arr(j, "priorities");
    state().meta.scopes     = j_arr(j, "scopes");
    state().meta.types      = j_arr(j, "types");
    return true;
}

bool refresh_issue_detail(const std::string& id) {
    auto resp = http_get("/api/issues/" + id);
    if (resp.status != 200) return false;
    auto j = json::parse(resp.body, nullptr, false);
    if (j.is_discarded()) return false;
    std::lock_guard<std::mutex> g(state().mu);
    state().selected_issue_id     = id;
    state().selected_issue_detail = parse_issue(j);
    return true;
}

static std::string json_escape(const std::string& s) {
    json j = s;
    return j.dump();
}

bool patch_issue_status(const std::string& id, const std::string& new_status) {
    std::string body = "{\"status\":" + json_escape(new_status) + "}";
    auto resp = http_patch_json("/api/issues/" + id, body);
    if (resp.status != 200) {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_error = "PATCH status " + id + ": " + std::to_string(resp.status);
        return false;
    }
    // Update local cache without full refresh.
    auto j = json::parse(resp.body, nullptr, false);
    if (!j.is_discarded()) {
        Issue updated = parse_issue(j);
        std::lock_guard<std::mutex> g(state().mu);
        for (auto& i : state().issues) {
            if (i.id == id) {
                i = updated;
                break;
            }
        }
    }
    return true;
}

bool refresh_agent_status() {
    auto resp = http_get("/api/agent_status");
    if (resp.status != 200) {
        std::lock_guard<std::mutex> g(state().mu);
        state().agent_runner_up = false;
        state().agent_active.clear();
        return false;
    }
    auto j = json::parse(resp.body, nullptr, false);
    if (j.is_discarded()) return false;
    std::map<std::string, std::string> active;
    if (j.contains("active") && j["active"].is_object()) {
        for (auto it = j["active"].begin(); it != j["active"].end(); ++it) {
            if (it.value().is_string()) active[it.key()] = it.value().get<std::string>();
        }
    }
    bool up = j.value("available", false);
    std::lock_guard<std::mutex> g(state().mu);
    state().agent_runner_up = up;
    state().agent_active    = std::move(active);
    return true;
}

bool launch_agent(const std::string& issue_id) {
    std::string body = "{\"issue_id\":" + json_escape(issue_id) + ",\"mode\":\"fix-issue\"}";
    auto resp = http_post_json("/api/agent_launch", body);
    if (resp.status < 200 || resp.status >= 300) {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_launch_msg = "launch failed (" + std::to_string(resp.status) + "): " + resp.body;
        return false;
    }
    {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_launch_msg = "launched agent on " + issue_id;
        // Optimistically mark as active so the dot shows up immediately.
        state().agent_active[issue_id] = "pending";
    }
    return true;
}

bool is_agent_active(const std::string& issue_id) {
    std::lock_guard<std::mutex> g(state().mu);
    return state().agent_active.find(issue_id) != state().agent_active.end();
}

bool patch_issue_fields(const std::string& id, const std::string& json_partial) {
    auto resp = http_patch_json("/api/issues/" + id, json_partial);
    if (resp.status != 200) {
        std::lock_guard<std::mutex> g(state().mu);
        state().last_error = "PATCH " + id + ": " + std::to_string(resp.status);
        return false;
    }
    return true;
}

void start_sse() {
    if (g_sse) return;
    fn_sse::Config cfg;
    cfg.url = state().backend_url + "/api/sse";
    g_sse = std::make_unique<fn_sse::Client>();
    g_sse->start(
        cfg,
        [](const fn_sse::Event& /*ev*/) {
            std::lock_guard<std::mutex> g(state().mu);
            state().last_refresh_ns = 0;
        },
        nullptr);
}

void stop_sse() {
    if (g_sse) {
        g_sse->stop();
        g_sse.reset();
    }
}

static bool contains(const std::set<std::string>& s, const std::string& v) {
    return s.find(v) != s.end();
}

bool passes_filters(const Issue& iss) {
    const auto& f = state().filters;
    if (!f.include_completed && iss.completed) return false;
    if (!f.priorities.empty() && !contains(f.priorities, iss.priority)) return false;
    if (!f.scopes.empty() && !contains(f.scopes, iss.scope)) return false;
    if (!f.domains.empty()) {
        bool match = false;
        for (const auto& d : iss.domain) {
            if (contains(f.domains, d)) {
                match = true;
                break;
            }
        }
        if (!match) return false;
    }
    if (!f.tags.empty()) {
        bool match = false;
        for (const auto& t : iss.tags) {
            if (contains(f.tags, t)) {
                match = true;
                break;
            }
        }
        if (!match) return false;
    }
    return true;
}

std::vector<std::string> collect_domains() {
    std::set<std::string> uniq;
    for (const auto& i : state().issues) {
        for (const auto& d : i.domain) uniq.insert(d);
    }
    return {uniq.begin(), uniq.end()};
}

std::vector<std::string> collect_tags() {
    std::set<std::string> uniq;
    for (const auto& i : state().issues) {
        for (const auto& t : i.tags) uniq.insert(t);
    }
    return {uniq.begin(), uniq.end()};
}

}  // namespace kanban
