#include "fgc/TestReport.h"

#include <cmath>
#include <cstdio>
#include <sstream>

namespace fgc {

namespace {

// Format a double compactly: integers print without a decimal point, otherwise
// up to 4 significant decimals with trailing zeros trimmed.
std::string num(double v) {
    if (std::isfinite(v) && v == std::floor(v) && std::fabs(v) < 1e15) {
        char b[32];
        std::snprintf(b, sizeof(b), "%lld", static_cast<long long>(v));
        return b;
    }
    char b[32];
    std::snprintf(b, sizeof(b), "%.4f", v);
    std::string s(b);
    auto dot = s.find('.');
    if (dot != std::string::npos) {
        size_t last = s.find_last_not_of('0');
        if (last == dot) last--;  // drop the dot too
        s.erase(last + 1);
    }
    return s;
}

}  // namespace

const TestMetric* TestReport::find(const std::string& section_id,
                                   const std::string& metric) const {
    for (const auto& s : sections) {
        if (s.id != section_id) continue;
        for (const auto& m : s.metrics)
            if (m.name == metric) return &m;
    }
    return nullptr;
}

std::string formatTestReport(const TestReport& r) {
    std::ostringstream o;
    o << "# Fire Gimbal Control - hardware test report\n";
    o << "ts_ms = " << r.ts_ms << "\n";
    o << "profile = " << r.profile << "\n";
    o << "host = " << r.host << "\n";
    o << "fw = " << r.fw << "\n";
    o << "result = " << (r.all_pass ? "PASS" : "FAIL") << "\n";

    for (const auto& s : r.sections) {
        o << "\n[" << s.id << "]  " << (s.ran ? (s.pass ? "PASS" : "FAIL") : "SKIP")
          << "  " << num(s.duration_ms) << " ms";
        if (!s.title.empty()) o << "  # " << s.title;
        o << "\n";
        for (const auto& m : s.metrics) {
            o << "  " << m.name << " = " << num(m.value);
            if (!m.unit.empty()) o << " " << m.unit;
            o << "  " << (m.pass ? "PASS" : "FAIL");
            if (m.has_baseline) o << "  base=" << num(m.baseline);
            if (m.has_prev)     o << "  prev=" << num(m.prev);
            if (m.has_baseline || m.has_prev)
                o << "  drift=" << num(m.drift_pct) << "%";
            if (m.drift_flag) o << "  DRIFT";
            o << "\n";
        }
        for (const auto& n : s.notes) o << "  ; " << n << "\n";
    }
    return o.str();
}

TestReport parseTestReport(const std::string& text) {
    TestReport r;
    std::istringstream in(text);
    std::string line;
    TestSection* cur = nullptr;
    while (std::getline(in, line)) {
        // strip trailing CR
        if (!line.empty() && line.back() == '\r') line.pop_back();
        // find first non-space
        size_t i = line.find_first_not_of(" \t");
        if (i == std::string::npos) continue;
        if (line[i] == '#' || line[i] == ';') continue;  // comment / note

        if (line[i] == '[') {                              // section header
            size_t end = line.find(']', i);
            if (end == std::string::npos) continue;
            r.sections.push_back(TestSection{});
            cur = &r.sections.back();
            cur->id = line.substr(i + 1, end - i - 1);
            cur->ran = line.find("SKIP", end) == std::string::npos;
            cur->pass = line.find("FAIL", end) == std::string::npos && cur->ran;
            continue;
        }

        // key = value [unit] [VERDICT] ...
        std::istringstream ls(line.substr(i));
        std::string name, eq, valtok;
        if (!(ls >> name >> eq >> valtok) || eq != "=") continue;
        double value = 0.0;
        try { value = std::stod(valtok); } catch (...) { continue; }

        if (!cur) {  // header block (ts_ms / profile / host / fw / result)
            if (name == "ts_ms") r.ts_ms = static_cast<long long>(value);
            continue;
        }
        std::string rest;
        ls >> rest;  // unit or verdict (we only need name+value for comparison)
        TestMetric m;
        m.name = name;
        m.value = value;
        if (rest != "PASS" && rest != "FAIL") m.unit = rest;
        cur->metrics.push_back(m);
    }
    // recover header fields that aren't numeric (profile/host/fw/result)
    {
        std::istringstream in2(text);
        std::string l;
        while (std::getline(in2, l)) {
            auto eq = l.find('=');
            if (eq == std::string::npos) continue;
            auto key = l.substr(0, eq);
            auto val = l.substr(eq + 1);
            auto trim = [](std::string s) {
                size_t a = s.find_first_not_of(" \t\r");
                size_t b = s.find_last_not_of(" \t\r");
                return a == std::string::npos ? std::string() : s.substr(a, b - a + 1);
            };
            key = trim(key);
            val = trim(val);
            if (key == "profile") r.profile = val;
            else if (key == "host") r.host = val;
            else if (key == "fw") r.fw = val;
            else if (key == "result") r.all_pass = (val == "PASS");
        }
    }
    return r;
}

void applyComparison(TestReport& cur, const TestReport* baseline, const TestReport* prev,
                     double drift_warn_pct, bool drift_gates_pass) {
    for (auto& s : cur.sections) {
        for (auto& m : s.metrics) {
            const TestMetric* b = baseline ? baseline->find(s.id, m.name) : nullptr;
            const TestMetric* p = prev ? prev->find(s.id, m.name) : nullptr;
            if (b) { m.has_baseline = true; m.baseline = b->value; }
            if (p) { m.has_prev = true; m.prev = p->value; }

            const double ref = b ? m.baseline : (p ? m.prev : 0.0);
            if (b || p) {
                if (std::fabs(ref) > 1e-9)
                    m.drift_pct = 100.0 * (m.value - ref) / std::fabs(ref);
                else
                    m.drift_pct = (std::fabs(m.value) < 1e-9) ? 0.0 : 100.0;
                // Only an adverse move counts as drift: worse = larger when
                // lower_is_better, smaller otherwise.
                const double adverse = m.lower_is_better ? m.drift_pct : -m.drift_pct;
                if (drift_warn_pct > 0.0 && adverse > drift_warn_pct) {
                    m.drift_flag = true;
                    if (drift_gates_pass) m.pass = false;
                }
            }
            if (!m.pass) s.pass = false;
        }
        if (s.ran && !s.pass) cur.all_pass = false;
    }
}

}  // namespace fgc
