#include "game_501.hpp"

#include <algorithm>
#include <chrono>
#include <cctype>
#include <numeric>

using json = nlohmann::json;

namespace
{
std::string trim(std::string value)
{
    const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch)
                                        { return std::isspace(ch); });
    const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch)
                                       { return std::isspace(ch); })
                          .base();
    if (first >= last)
        return {};
    return std::string(first, last);
}
} // namespace

Game501::Game501() = default;

std::int64_t Game501::nowMillis()
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(
               std::chrono::system_clock::now().time_since_epoch())
        .count();
}

std::string Game501::normalizeName(const std::string &name, std::size_t index)
{
    std::string clean = trim(name);
    if (clean.empty())
        clean = "Spelare " + std::to_string(index + 1);
    if (clean.size() > 32)
        clean.resize(32);
    return clean;
}

std::optional<Game501::DartValue> Game501::parseDart(const std::string &raw_notation)
{
    std::string notation = trim(raw_notation);
    std::transform(notation.begin(), notation.end(), notation.begin(), [](unsigned char ch)
                   { return static_cast<char>(std::toupper(ch)); });

    if (notation == "MISS" || notation == "M" || notation == "0")
        return DartValue{"MISS", 0, false};
    if (notation == "OUTER" || notation == "SB" || notation == "S25" || notation == "25")
        return DartValue{"OUTER", 25, false};
    if (notation == "BULL" || notation == "DB" || notation == "D25" || notation == "50")
        return DartValue{"BULL", 50, true};

    char multiplier = 'S';
    std::string number_part = notation;
    if (!notation.empty() && (notation.front() == 'S' || notation.front() == 'D' || notation.front() == 'T'))
    {
        multiplier = notation.front();
        number_part = notation.substr(1);
    }

    if (number_part.empty() || !std::all_of(number_part.begin(), number_part.end(), [](unsigned char ch)
                                             { return std::isdigit(ch); }))
        return std::nullopt;

    int number = 0;
    try
    {
        number = std::stoi(number_part);
    }
    catch (...)
    {
        return std::nullopt;
    }

    if (number < 1 || number > 20)
        return std::nullopt;

    const int factor = multiplier == 'T' ? 3 : (multiplier == 'D' ? 2 : 1);
    return DartValue{std::string(1, multiplier) + std::to_string(number), number * factor, multiplier == 'D'};
}

Game501::Snapshot Game501::snapshotLocked() const
{
    return Snapshot{status_, players_, active_player_, round_, turn_start_score_, current_darts_, history_, awaiting_clear_, winner_};
}

void Game501::restoreLocked(const Snapshot &snapshot)
{
    status_ = snapshot.status;
    players_ = snapshot.players;
    active_player_ = snapshot.active_player;
    round_ = snapshot.round;
    turn_start_score_ = snapshot.turn_start_score;
    current_darts_ = snapshot.current_darts;
    history_ = snapshot.history;
    awaiting_clear_ = snapshot.awaiting_clear;
    winner_ = snapshot.winner;
}

void Game501::archiveTurnLocked(const std::string &outcome)
{
    if (players_.empty())
        return;

    history_.push_back(Turn{active_player_, round_, turn_start_score_, players_[active_player_].score, outcome, current_darts_});
    while (history_.size() > 100)
        history_.pop_front();
}

void Game501::advancePlayerLocked()
{
    if (players_.empty())
        return;

    active_player_ = (active_player_ + 1) % static_cast<int>(players_.size());
    if (active_player_ == 0)
        ++round_;
    turn_start_score_ = players_[active_player_].score;
    current_darts_.clear();
}

json Game501::start(const std::vector<std::string> &player_names)
{
    std::lock_guard<std::mutex> lock(mutex_);

    if (player_names.empty() || player_names.size() > 8)
        return responseLocked(false, "Välj mellan 1 och 8 spelare");

    players_.clear();
    players_.reserve(player_names.size());
    for (std::size_t index = 0; index < player_names.size(); ++index)
        players_.push_back(Player{normalizeName(player_names[index], index), 501});

    status_ = "playing";
    active_player_ = 0;
    round_ = 1;
    turn_start_score_ = 501;
    current_darts_.clear();
    history_.clear();
    undo_stack_.clear();
    awaiting_clear_ = false;
    winner_ = -1;
    return responseLocked(true, "501-spelet har startat");
}

json Game501::addDart(const std::string &notation, const std::string &source)
{
    std::lock_guard<std::mutex> lock(mutex_);

    if (status_ != "playing" || players_.empty())
        return responseLocked(false, "Inget aktivt 501-spel");
    if (awaiting_clear_)
        return responseLocked(false, "Ta bort pilarna och bekräfta att tavlan är tom");

    const auto parsed = parseDart(notation);
    if (!parsed)
        return responseLocked(false, "Ogiltigt kast: " + notation);

    undo_stack_.push_back(snapshotLocked());
    if (undo_stack_.size() > 100)
        undo_stack_.erase(undo_stack_.begin());

    Dart dart{parsed->notation, parsed->value, parsed->is_double, false, source, nowMillis()};
    const int candidate = players_[active_player_].score - dart.value;
    const bool bust = candidate < 0 || candidate == 1 || (candidate == 0 && !dart.is_double);

    if (bust)
    {
        dart.bust = true;
        current_darts_.push_back(dart);
        players_[active_player_].score = turn_start_score_;
        archiveTurnLocked("bust");
        awaiting_clear_ = true;
        advancePlayerLocked();
        return responseLocked(true, "Bust – turens poäng återställdes");
    }

    players_[active_player_].score = candidate;
    current_darts_.push_back(dart);

    if (candidate == 0)
    {
        archiveTurnLocked("checkout");
        winner_ = active_player_;
        status_ = "finished";
        awaiting_clear_ = false;
        return responseLocked(true, players_[winner_].name + " vann med " + parsed->notation);
    }

    if (current_darts_.size() == 3)
    {
        archiveTurnLocked("scored");
        awaiting_clear_ = true;
        advancePlayerLocked();
        return responseLocked(true, "Tur klar – ta bort pilarna");
    }

    return responseLocked(true, parsed->notation + " registrerad");
}

json Game501::undo()
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (undo_stack_.empty())
        return responseLocked(false, "Det finns inget kast att ångra");

    const Snapshot previous = undo_stack_.back();
    undo_stack_.pop_back();
    restoreLocked(previous);
    return responseLocked(true, "Senaste kastet ångrades");
}

json Game501::markBoardClear()
{
    std::lock_guard<std::mutex> lock(mutex_);
    awaiting_clear_ = false;
    return responseLocked(true, "Tavlan är redo för nästa spelare");
}

json Game501::reset()
{
    std::lock_guard<std::mutex> lock(mutex_);
    status_ = "idle";
    players_.clear();
    active_player_ = 0;
    round_ = 1;
    turn_start_score_ = 501;
    current_darts_.clear();
    history_.clear();
    undo_stack_.clear();
    awaiting_clear_ = false;
    winner_ = -1;
    return responseLocked(true, "Spelet nollställdes");
}

json Game501::dartJson(const Dart &dart)
{
    return {
        {"notation", dart.notation},
        {"value", dart.value},
        {"double", dart.is_double},
        {"bust", dart.bust},
        {"source", dart.source},
        {"timestamp", dart.timestamp},
    };
}

json Game501::stateLocked() const
{
    json players = json::array();
    for (std::size_t index = 0; index < players_.size(); ++index)
    {
        players.push_back({
            {"name", players_[index].name},
            {"score", players_[index].score},
            {"active", status_ == "playing" && static_cast<int>(index) == active_player_},
            {"winner", static_cast<int>(index) == winner_},
        });
    }

    json darts = json::array();
    int turn_total = 0;
    for (const auto &dart : current_darts_)
    {
        darts.push_back(dartJson(dart));
        turn_total += dart.value;
    }

    json history = json::array();
    for (auto it = history_.rbegin(); it != history_.rend() && history.size() < 20; ++it)
    {
        json turn_darts = json::array();
        for (const auto &dart : it->darts)
            turn_darts.push_back(dartJson(dart));

        history.push_back({
            {"player_index", it->player_index},
            {"player", it->player_index >= 0 && it->player_index < static_cast<int>(players_.size()) ? players_[it->player_index].name : ""},
            {"round", it->round},
            {"start_score", it->start_score},
            {"end_score", it->end_score},
            {"outcome", it->outcome},
            {"darts", turn_darts},
        });
    }

    return {
        {"status", status_},
        {"variant", "501"},
        {"double_out", true},
        {"round", round_},
        {"active_player", active_player_},
        {"winner", winner_},
        {"awaiting_clear", awaiting_clear_},
        {"can_undo", !undo_stack_.empty()},
        {"players", players},
        {"current_turn", {
             {"start_score", turn_start_score_},
             {"total", turn_total},
             {"darts", darts},
         }},
        {"history", history},
    };
}

json Game501::responseLocked(bool accepted, const std::string &message) const
{
    return {
        {"accepted", accepted},
        {"message", message},
        {"game", stateLocked()},
    };
}

json Game501::state() const
{
    std::lock_guard<std::mutex> lock(mutex_);
    return stateLocked();
}

bool Game501::isPlaying() const
{
    std::lock_guard<std::mutex> lock(mutex_);
    return status_ == "playing";
}

