#include "mining_benchmark.h"

#include <algorithm>
#include <cmath>

namespace dragonx {
namespace ui {

void ThreadBenchmark::reset()
{
    phase = Phase::Idle;
    candidates.clear();
    current_index = 0;
    results.clear();
    phase_timer = 0.0f;
    prev_window_avg = 0.0;
    window_sum = 0.0;
    window_samples = 0;
    window_timer = 0.0f;
    consecutive_stable = 0;
    measure_sum = 0.0;
    measure_samples = 0;
    optimal_threads = 0;
    optimal_hashrate = 0.0;
    was_pool_running = false;
    prev_threads = 0;
    total_warmup_secs = 0.0f;
}

void ThreadBenchmark::buildCandidates(int maxThreads)
{
    candidates.clear();
    int start = std::max(1, maxThreads / 2);
    for (int threads = start; threads <= maxThreads; ++threads) {
        candidates.push_back(threads);
    }
}

float ThreadBenchmark::avgWarmupSecs() const
{
    if (current_index > 0) {
        return total_warmup_secs / static_cast<float>(current_index);
    }
    return (MIN_WARMUP_SECS + MAX_WARMUP_SECS) * 0.5f;
}

float ThreadBenchmark::perTestSecs() const
{
    return avgWarmupSecs() + MEASURE_SECS;
}

float ThreadBenchmark::totalEstimatedSecs() const
{
    int count = static_cast<int>(candidates.size());
    if (count <= 0) return 0.0f;
    float completedTime = total_warmup_secs
                        + static_cast<float>(current_index) * (MEASURE_SECS + COOLDOWN_SECS);
    int remaining = count - current_index;
    float remainingTime = static_cast<float>(remaining) * (avgWarmupSecs() + MEASURE_SECS)
                        + static_cast<float>(std::max(0, remaining - 1)) * COOLDOWN_SECS;
    return completedTime + remainingTime;
}

float ThreadBenchmark::elapsedSecs() const
{
    float completed = total_warmup_secs
                    + static_cast<float>(current_index) * (MEASURE_SECS + COOLDOWN_SECS);
    return completed + phase_timer;
}

float ThreadBenchmark::progress() const
{
    float total = totalEstimatedSecs();
    return (total > 0.0f) ? std::min(1.0f, elapsedSecs() / total) : 0.0f;
}

void ThreadBenchmark::resetStabilityTracking()
{
    prev_window_avg = 0.0;
    window_sum = 0.0;
    window_samples = 0;
    window_timer = 0.0f;
    consecutive_stable = 0;
}

bool ThreadBenchmark::active() const
{
    return phase != Phase::Idle && phase != Phase::Done;
}

ThreadBenchmarkUpdate AdvanceThreadBenchmark(ThreadBenchmark& benchmark,
                                             float deltaSeconds,
                                             double poolHashrate10s)
{
    ThreadBenchmarkUpdate update;
    if (!benchmark.active()) return update;

    benchmark.phase_timer += deltaSeconds;

    switch (benchmark.phase) {
        case ThreadBenchmark::Phase::Starting:
            if (benchmark.current_index < static_cast<int>(benchmark.candidates.size())) {
                int threads = benchmark.candidates[benchmark.current_index];
                update.stopPoolMining = true;
                update.startPoolMining = true;
                update.startThreads = threads;
                benchmark.phase = ThreadBenchmark::Phase::WarmingUp;
                benchmark.phase_timer = 0.0f;
                benchmark.resetStabilityTracking();
                benchmark.measure_sum = 0.0;
                benchmark.measure_samples = 0;
            } else {
                benchmark.phase = ThreadBenchmark::Phase::Done;
            }
            break;

        case ThreadBenchmark::Phase::WarmingUp: {
            bool pastMin = benchmark.phase_timer >= ThreadBenchmark::MIN_WARMUP_SECS;
            bool pastMax = benchmark.phase_timer >= ThreadBenchmark::MAX_WARMUP_SECS;

            if (poolHashrate10s > 0.0) {
                benchmark.window_sum += poolHashrate10s;
                benchmark.window_samples++;
            }
            benchmark.window_timer += deltaSeconds;

            bool stable = false;
            if (pastMin && benchmark.window_timer >= ThreadBenchmark::STABILITY_WINDOW_SECS &&
                benchmark.window_samples > 0) {
                double currentAverage = benchmark.window_sum / benchmark.window_samples;
                if (benchmark.prev_window_avg > 0.0) {
                    double change = std::abs(currentAverage - benchmark.prev_window_avg)
                                  / benchmark.prev_window_avg;
                    if (change < ThreadBenchmark::STABILITY_THRESHOLD)
                        benchmark.consecutive_stable++;
                    else
                        benchmark.consecutive_stable = 0;
                    if (benchmark.consecutive_stable >= ThreadBenchmark::STABLE_WINDOWS_NEEDED)
                        stable = true;
                }
                benchmark.prev_window_avg = currentAverage;
                benchmark.window_sum = 0.0;
                benchmark.window_samples = 0;
                benchmark.window_timer = 0.0f;
            }

            if (stable || pastMax) {
                benchmark.total_warmup_secs += benchmark.phase_timer;
                benchmark.phase = ThreadBenchmark::Phase::Measuring;
                benchmark.phase_timer = 0.0f;
                benchmark.measure_sum = 0.0;
                benchmark.measure_samples = 0;
            }
            break;
        }

        case ThreadBenchmark::Phase::Measuring:
            if (poolHashrate10s > 0.0) {
                benchmark.measure_sum += poolHashrate10s;
                benchmark.measure_samples++;
            }
            if (benchmark.phase_timer >= ThreadBenchmark::MEASURE_SECS) {
                int threads = benchmark.candidates[benchmark.current_index];
                double average = (benchmark.measure_samples > 0)
                               ? benchmark.measure_sum / benchmark.measure_samples
                               : 0.0;
                benchmark.results.push_back({threads, average});
                if (average > benchmark.optimal_hashrate) {
                    benchmark.optimal_hashrate = average;
                    benchmark.optimal_threads = threads;
                }
                benchmark.phase = ThreadBenchmark::Phase::Advancing;
                benchmark.phase_timer = 0.0f;
            }
            break;

        case ThreadBenchmark::Phase::Advancing:
            update.stopPoolMining = true;
            benchmark.current_index++;
            if (benchmark.current_index < static_cast<int>(benchmark.candidates.size())) {
                benchmark.phase = ThreadBenchmark::Phase::CoolingDown;
                benchmark.phase_timer = 0.0f;
            } else {
                benchmark.phase = ThreadBenchmark::Phase::Done;
                if (benchmark.optimal_threads > 0) {
                    update.saveOptimalThreads = true;
                    update.optimalThreads = benchmark.optimal_threads;
                    if (benchmark.was_pool_running) {
                        update.startPoolMining = true;
                        update.startThreads = benchmark.optimal_threads;
                    }
                } else {
                    // No candidate produced a nonzero hashrate (e.g. a pool that never
                    // reported a rate). Don't finish silently — flag the run as
                    // inconclusive so the caller can surface it (keeps this core pure).
                    update.inconclusive = true;
                }
            }
            break;

        case ThreadBenchmark::Phase::CoolingDown:
            if (benchmark.phase_timer >= ThreadBenchmark::COOLDOWN_SECS) {
                benchmark.phase = ThreadBenchmark::Phase::Starting;
                benchmark.phase_timer = 0.0f;
            }
            break;

        default:
            break;
    }

    return update;
}

} // namespace ui
} // namespace dragonx
