#include "fgc/DiagParser.h"

#include <map>
#include <sstream>

namespace fgc {

namespace {

long toLong(const std::string& s, long fallback = 0) {
    if (s.empty()) return fallback;
    try {
        bool hex = s.size() > 1 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X');
        return std::stol(s, nullptr, hex ? 16 : 10);
    } catch (const std::exception&) {
        return fallback;
    }
}

}  // namespace

bool DiagResult::allPass() const {
    bool any = false;
    for (const auto& ax : axes) {
        if (!ax.has_result) continue;
        any = true;
        if (!ax.pass) return false;
    }
    return any;
}

DiagResult parseDiag(const std::string& block) {
    DiagResult d;
    std::istringstream iss(block);
    std::string line;
    DiagAxis* cur = nullptr;

    auto axisFor = [&d](char a) -> DiagAxis& {
        for (auto& ax : d.axes)
            if (ax.axis == a) return ax;
        d.axes.push_back(DiagAxis{});
        d.axes.back().axis = a;
        return d.axes.back();
    };

    while (std::getline(iss, line)) {
        if (!line.empty() && line.back() == '\r') line.pop_back();
        // Only "DG ..." lines are part of the diag (ST/other lines interleave).
        auto pos = line.find("DG ");
        if (pos == std::string::npos) continue;
        std::istringstream ls(line.substr(pos));
        std::vector<std::string> tok;
        std::string t;
        while (ls >> t) tok.push_back(t);
        if (tok.size() < 2) continue;  // "DG" + something

        d.valid = true;
        if (tok[1] == "DONE") { d.done = true; continue; }
        if (tok[1] == "BEGIN" && tok.size() >= 3) { cur = &axisFor(tok[2][0]); continue; }

        // Otherwise tok[1] is the axis letter.
        char axis = tok[1][0];
        if (axis != 'Y' && axis != 'P') continue;
        DiagAxis& ax = axisFor(axis);
        cur = &ax;

        if (tok.size() >= 4 && tok[2] == "RESULT") {
            ax.has_result = true;
            ax.pass = (tok[3] == "PASS");
            continue;
        }

        // Test line: "DG <axis> S<speed> <FWD|REV> KEY val KEY val ... <PASS|FAIL>"
        if (tok.size() < 4 || tok[2].empty() || (tok[2][0] != 'S' && tok[2][0] != 's'))
            continue;
        DiagTest test;
        test.speed = static_cast<int>(toLong(tok[2].substr(1)));
        test.fwd   = (tok[3] != "REV");
        test.pass  = (tok.back() == "PASS");
        // Remaining KEY value pairs (between dir and the trailing PASS/FAIL).
        std::map<std::string, std::string> kv;
        for (size_t i = 4; i + 1 < tok.size(); i += 2) kv[tok[i]] = tok[i + 1];
        auto g = [&](const char* k) { auto it = kv.find(k); return it == kv.end() ? std::string() : it->second; };
        test.err_peak  = toLong(g("ERR_PEAK"), 0);
        test.err_rms   = toLong(g("ERR_RMS"), 0);
        test.err_still = toLong(g("ERR_STILL"), 0);
        test.cs_min    = static_cast<int>(toLong(g("CS_MIN")));
        test.cs_max    = static_cast<int>(toLong(g("CS_MAX")));
        test.sg_min    = static_cast<int>(toLong(g("SG_MIN")));
        test.pwm_avg   = static_cast<int>(toLong(g("PWM_AVG")));
        test.flags     = static_cast<unsigned>(toLong(g("FLAGS")));
        ax.tests.push_back(test);
    }
    return d;
}

std::vector<std::string> formatDiag(const DiagResult& d) {
    std::vector<std::string> out;
    if (!d.valid) {
        out.emplace_back("(no diagnostic data)");
        return out;
    }
    for (const auto& ax : d.axes) {
        std::ostringstream hdr;
        hdr << "[" << ax.axis << "] " << ax.tests.size() << " tests  RESULT="
            << (ax.has_result ? (ax.pass ? "PASS" : "FAIL") : "?");
        out.push_back(hdr.str());
        for (const auto& t : ax.tests) {
            std::ostringstream os;
            os << "    S" << t.speed << " " << (t.fwd ? "FWD" : "REV")
               << "  err_peak=" << t.err_peak << " err_rms=" << t.err_rms
               << " err_still=" << t.err_still << "  cs=" << t.cs_min << ".." << t.cs_max
               << " sg_min=" << t.sg_min << " pwm=" << t.pwm_avg
               << " flags=0x" << std::hex << t.flags << std::dec
               << "  " << (t.pass ? "PASS" : "FAIL");
            out.push_back(os.str());
        }
    }
    out.push_back(std::string("overall: ") + (d.allPass() ? "PASS" : "FAIL") +
                  (d.done ? "" : "  (incomplete - no DG DONE)"));
    return out;
}

}  // namespace fgc
