#include "BalancingWindow.h"

#include <algorithm>

#include <QFile>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QTextStream>
#include <QVBoxLayout>

#include "ConfigLoader.h"
#include "InspectWindow.h"
#include "VisualsLoader.h"

namespace
{
    // Escapes characters that would break a markdown table cell.
    QString escapeCell(const QString& text)
    {
        QString escaped = text;
        escaped.replace("|", "\\|");
        return escaped;
    }

    QString stateText(ArenaWidget::State state)
    {
        switch (state)
        {
            case ArenaWidget::State::NotStarted:
                return QStringLiteral("not started");
            case ArenaWidget::State::Running:
                return QStringLiteral("running");
            case ArenaWidget::State::Ended:
                return QStringLiteral("ended");
        }
        return QString();
    }

    // Team column header: "[WON] Name — threat N", matching the arena widget.
    QString teamHeaderCell(const ArenaStatus& status, int teamIndex)
    {
        const ArenaStatus::TeamStatus& team = status.teams[teamIndex];
        QString header = QString::fromStdString(team.name);
        if (status.finished && status.winnerTeam == teamIndex)
        {
            header = QStringLiteral("[WON] ") + header;
        }
        header += QStringLiteral(" - threat %1").arg(QString::number(team.threatLevel, 'f', 0));
        header += QStringLiteral(" - EHP %1").arg(QString::fromStdString(team.getEhpPercentText()));
        return escapeCell(header);
    }

    // Single entry line: "surviving/total DisplayName [L<level>]", matching the arena widget.
    QString entryCell(const ArenaStatus::Entry& entry)
    {
        QString cell = QString("%1/%2 %3")
                           .arg(entry.surviving)
                           .arg(entry.total)
                           .arg(QString::fromStdString(entry.displayName));
        if (entry.level.has_value())
        {
            cell += QStringLiteral(" L%1").arg(entry.level.value());
        }
        return escapeCell(cell);
    }
}

BalancingWindow::BalancingWindow(const BalancingConfig& balancingConfig,
                                 GameConfig gameConfig,
                                 const std::string& configDir,
                                 const std::string& balancingConfigPath,
                                 QWidget* parent)
    : QWidget(parent)
    , m_gameConfig(std::move(gameConfig))
    , m_configDir(configDir)
    , m_balancingConfigPath(balancingConfigPath)
    , m_nextSeed(0)
    , m_inspectWindow(nullptr)
{
    m_visuals = VisualsLoader::load(m_configDir + "/visuals.toml");
    setWindowTitle(tr("DotaFactory — Balancing Tool"));
    resize(800, 600);

    QVBoxLayout* mainLayout = new QVBoxLayout(this);
    mainLayout->setContentsMargins(0, 0, 0, 0);

    QHBoxLayout* buttonRow = new QHBoxLayout();
    m_reloadButton = new QPushButton(tr("Reload Config"), this);
    m_startAllButton = new QPushButton(tr("Start All"), this);
    m_logButton = new QPushButton(tr("Log"), this);
    buttonRow->addWidget(m_reloadButton);
    buttonRow->addWidget(m_startAllButton);
    buttonRow->addWidget(m_logButton);
    buttonRow->addStretch();
    mainLayout->addLayout(buttonRow);

    connect(m_reloadButton, &QPushButton::clicked, this, &BalancingWindow::reloadConfig);
    connect(m_startAllButton, &QPushButton::clicked, this, &BalancingWindow::startAll);
    connect(m_logButton, &QPushButton::clicked, this, &BalancingWindow::writeLog);

    m_scrollArea = new QScrollArea(this);
    m_scrollArea->setWidgetResizable(true);
    mainLayout->addWidget(m_scrollArea);

    populateArenas(balancingConfig);

    m_pollTimer = new QTimer(this);
    connect(m_pollTimer, &QTimer::timeout, this, &BalancingWindow::pollStatuses);
    m_pollTimer->start(100);

    registerForEvents();
}

BalancingWindow::~BalancingWindow()
{
    unregisterForEvents();

    m_pollTimer->stop();
    if (m_inspectWindow)
    {
        delete m_inspectWindow;
        m_inspectWindow = nullptr;
    }
    m_inspectedSim.reset();
    stopAllArenas();
}

void BalancingWindow::populateArenas(const BalancingConfig& balancingConfig)
{
    stopAllArenas();
    m_arenas.clear();

    QWidget* scrollContent = new QWidget(m_scrollArea);
    QVBoxLayout* contentLayout = new QVBoxLayout(scrollContent);
    contentLayout->setSpacing(8);
    contentLayout->setContentsMargins(8, 8, 8, 8);

    for (const ArenaConfig& arenaConfig : balancingConfig.arenas)
    {
        int index = static_cast<int>(m_arenas.size());

        ArenaEntry entry;
        entry.config = arenaConfig;
        entry.simulation = std::make_unique<ArenaSimulation>(
            m_gameConfig, arenaConfig, m_nextSeed++);
        entry.widget = new ArenaWidget(index, arenaConfig.name, scrollContent);
        contentLayout->addWidget(entry.widget);

        entry.widget->updateStatus(entry.simulation->getStatus());

        m_arenas.push_back(std::move(entry));
    }

    contentLayout->addStretch();
    m_scrollArea->setWidget(scrollContent);

    updateButtons();
}

void BalancingWindow::stopAllArenas()
{
    for (ArenaEntry& entry : m_arenas)
    {
        entry.simulation->requestStop();
    }
    for (ArenaEntry& entry : m_arenas)
    {
        if (entry.worker.joinable())
        {
            entry.worker.join();
        }
    }
}

void BalancingWindow::pollStatuses()
{
    for (ArenaEntry& entry : m_arenas)
    {
        if (entry.worker.joinable())
        {
            const ArenaStatus status = entry.simulation->getStatus();
            entry.widget->updateStatus(status);
        }
    }

    if (m_inspectedSim && m_inspectedArenaIndex.has_value())
    {
        const ArenaStatus status = m_inspectedSim->getStatus();
        m_arenas[static_cast<std::size_t>(*m_inspectedArenaIndex)].widget->updateStatus(status);
    }

    updateButtons();
}

void BalancingWindow::reloadConfig()
{
    try
    {
        GameConfig newGameConfig = ConfigLoader::loadFromDirectory(m_configDir);
        BalancingConfig newBalancingConfig = loadBalancingConfig(m_balancingConfigPath);
        m_gameConfig = std::move(newGameConfig);
        populateArenas(newBalancingConfig);
    }
    catch (const std::exception& e)
    {
        QMessageBox::critical(this, tr("Reload Failed"), QString::fromStdString(e.what()));
    }
}

void BalancingWindow::startAll()
{
    for (int i = 0; i < static_cast<int>(m_arenas.size()); ++i)
    {
        startArena(i);
    }
}

void BalancingWindow::handleEvent(std::shared_ptr<const ArenaStartRequestedEvent> event)
{
    startArena(event->arenaIndex);
}

void BalancingWindow::handleEvent(std::shared_ptr<const ArenaInspectRequestedEvent> event)
{
    inspectArena(event->arenaIndex);
}

void BalancingWindow::handleEvent(std::shared_ptr<const InspectWindowClosedEvent> /*event*/)
{
    closeInspectWindow();
}

void BalancingWindow::startArena(int index)
{
    ArenaEntry& entry = m_arenas[index];
    if (entry.worker.joinable())
    {
        entry.simulation->requestStop();
        entry.worker.join();
    }
    entry.simulation = std::make_unique<ArenaSimulation>(
        m_gameConfig, entry.config, m_nextSeed++);
    entry.widget->startSimulation();
    entry.widget->updateStatus(entry.simulation->getStatus());
    ArenaSimulation* sim = entry.simulation.get();
    entry.worker = std::thread([sim]() { sim->run(); });
    updateButtons();
}

void BalancingWindow::inspectArena(int index)
{
    if (m_inspectWindow)
    {
        delete m_inspectWindow;
        m_inspectWindow = nullptr;

        if (m_inspectedSim && m_inspectedArenaIndex.has_value()
            && !m_inspectedSim->isFinished())
        {
            m_arenas[static_cast<std::size_t>(*m_inspectedArenaIndex)].widget->resetToGrey();
        }
        m_inspectedSim.reset();
        m_inspectedArenaIndex = std::nullopt;
    }

    ArenaEntry& entry = m_arenas[static_cast<std::size_t>(index)];

    if (entry.worker.joinable())
    {
        entry.simulation->requestStop();
        entry.worker.join();
    }

    m_inspectedSim = std::make_unique<ArenaSimulation>(
        m_gameConfig, entry.config, m_nextSeed++);
    m_inspectedArenaIndex = index;

    entry.widget->resetToGrey();
    entry.widget->startSimulation();
    entry.widget->updateStatus(m_inspectedSim->getStatus());

    m_inspectWindow = new InspectWindow(
        m_inspectedSim.get(), &m_gameConfig, &m_visuals, entry.config.name, nullptr);

    setMainControlsEnabled(false);
    m_inspectWindow->show();
}

void BalancingWindow::closeInspectWindow()
{
    if (!m_inspectWindow)
    {
        return;
    }

    m_inspectWindow->deleteLater();
    m_inspectWindow = nullptr;

    if (m_inspectedArenaIndex.has_value() && m_inspectedSim)
    {
        if (!m_inspectedSim->isFinished())
        {
            m_arenas[static_cast<std::size_t>(*m_inspectedArenaIndex)].widget->resetToGrey();
        }
    }

    m_inspectedSim.reset();
    m_inspectedArenaIndex = std::nullopt;
    setMainControlsEnabled(true);
    updateButtons();
}

void BalancingWindow::setMainControlsEnabled(bool enabled)
{
    m_reloadButton->setEnabled(enabled);
    m_startAllButton->setEnabled(enabled);
    m_logButton->setEnabled(enabled);
    for (ArenaEntry& entry : m_arenas)
    {
        for (QPushButton* btn : entry.widget->findChildren<QPushButton*>())
        {
            btn->setEnabled(enabled);
        }
    }
}

void BalancingWindow::updateButtons()
{
    if (m_inspectWindow)
    {
        return;
    }

    bool anyRunning = false;
    bool allRunning = true;
    for (ArenaEntry& entry : m_arenas)
    {
        if (entry.worker.joinable() && !entry.simulation->getStatus().finished)
        {
            anyRunning = true;
        }
        else
        {
            allRunning = false;
        }
    }
    if (m_arenas.empty())
    {
        allRunning = false;
    }

    m_reloadButton->setEnabled(!anyRunning);
    m_startAllButton->setEnabled(!allRunning);
    m_logButton->setEnabled(true);
}

void BalancingWindow::writeLog()
{
    QFile file(QStringLiteral("balancing_log.md"));
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
    {
        QMessageBox::warning(this, tr("Log Failed"),
            tr("Could not open balancing_log.md for writing."));
        return;
    }

    QTextStream out(&file);
    out.setCodec("UTF-8");

    out << "# Balancing Log\n";

    for (const ArenaEntry& entry : m_arenas)
    {
        const ArenaStatus& status = entry.widget->getLastStatus();
        const ArenaWidget::State state = entry.widget->getState();

        QString stateSuffix = stateText(state);
        if (state == ArenaWidget::State::Ended)
        {
            stateSuffix += QStringLiteral(", %1 s")
                .arg(QString::number(status.durationSeconds, 'f', 1));
        }

        out << "\n## Arena: " << QString::fromStdString(entry.config.name)
            << "  (" << stateSuffix << ")\n\n";

        out << "| " << teamHeaderCell(status, 0) << " | "
            << teamHeaderCell(status, 1) << " |\n";
        out << "|---|---|\n";

        const std::size_t rowCount = std::max(status.teams[0].entries.size(),
                                              status.teams[1].entries.size());
        for (std::size_t row = 0; row < rowCount; ++row)
        {
            QString leftCell;
            if (row < status.teams[0].entries.size())
            {
                leftCell = entryCell(status.teams[0].entries[row]);
            }
            QString rightCell;
            if (row < status.teams[1].entries.size())
            {
                rightCell = entryCell(status.teams[1].entries[row]);
            }
            out << "| " << leftCell << " | " << rightCell << " |\n";
        }
    }
}
