// Test runner implementation
#include "test_runner.h"
#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>

namespace mosis::test {

nlohmann::json TestResult::ToJson() const {
    nlohmann::json j;
    j["name"] = name;
    j["status"] = status == TestStatus::Passed ? "passed" :
                  status == TestStatus::Failed ? "failed" :
                  status == TestStatus::Skipped ? "skipped" : "error";
    j["message"] = message;
    j["duration_ms"] = durationMs;
    return j;
}

nlohmann::json TestSuiteResult::ToJson() const {
    nlohmann::json j;
    j["name"] = name;
    j["summary"] = {
        {"passed", passed},
        {"failed", failed},
        {"skipped", skipped},
        {"errors", errors},
        {"total", static_cast<int>(tests.size())},
        {"duration_ms", totalDurationMs}
    };

    nlohmann::json testsJson = nlohmann::json::array();
    for (const auto& test : tests) {
        testsJson.push_back(test.ToJson());
    }
    j["tests"] = testsJson;

    return j;
}

void TestSuiteResult::SaveToFile(const std::filesystem::path& path) const {
    std::ofstream file(path);
    if (file.is_open()) {
        file << ToJson().dump(2);
        std::cout << "Test results saved to: " << path << std::endl;
    } else {
        std::cerr << "Failed to save test results to: " << path << std::endl;
    }
}

TestRunner::TestRunner() = default;

TestRunner::~TestRunner() {
    StopDesigner();
}

void TestRunner::SetDesignerPath(const std::filesystem::path& path) {
    m_designerPath = path;
}

void TestRunner::SetDocumentPath(const std::filesystem::path& path) {
    m_documentPath = path;
}

void TestRunner::SetLogPath(const std::filesystem::path& path) {
    m_logPath = path;
    m_log.SetLogPath(path);
}

void TestRunner::SetHierarchyPath(const std::filesystem::path& path) {
    m_hierarchyPath = path;
    m_hierarchy.SetHierarchyPath(path);
}

void TestRunner::AddTest(const std::string& name, TestFunc func) {
    m_tests.push_back({name, std::move(func)});
}

bool TestRunner::StartDesigner() {
    if (m_designerPath.empty() || m_documentPath.empty()) {
        std::cerr << "Designer or document path not set" << std::endl;
        return false;
    }

    // Delete old log file
    if (!m_logPath.empty() && std::filesystem::exists(m_logPath)) {
        std::filesystem::remove(m_logPath);
    }

    // Delete old hierarchy file
    if (!m_hierarchyPath.empty() && std::filesystem::exists(m_hierarchyPath)) {
        std::filesystem::remove(m_hierarchyPath);
    }

    // Build command line
    std::string cmdLine = "\"" + m_designerPath.string() + "\" \"" + m_documentPath.string() + "\"";
    if (!m_logPath.empty()) {
        cmdLine += " --log \"" + m_logPath.string() + "\"";
    }
    if (!m_hierarchyPath.empty()) {
        cmdLine += " --hierarchy \"" + m_hierarchyPath.string() + "\"";
    }

    std::cout << "Starting designer: " << cmdLine << std::endl;

    // Create process
    STARTUPINFOA si = {};
    si.cb = sizeof(si);
    PROCESS_INFORMATION pi = {};

    // Need a mutable buffer for CreateProcess
    std::vector<char> cmdLineBuffer(cmdLine.begin(), cmdLine.end());
    cmdLineBuffer.push_back('\0');

    if (!CreateProcessA(
            nullptr,
            cmdLineBuffer.data(),
            nullptr,
            nullptr,
            FALSE,
            0,
            nullptr,
            nullptr,
            &si,
            &pi)) {
        std::cerr << "Failed to start designer process: " << GetLastError() << std::endl;
        return false;
    }

    m_designerProcess = pi.hProcess;
    CloseHandle(pi.hThread);

    std::cout << "Designer process started with PID: " << pi.dwProcessId << std::endl;

    // Wait for window to appear
    if (!m_window.WaitForWindow("Mosis Designer", m_startupTimeoutMs)) {
        std::cerr << "Designer window did not appear" << std::endl;
        StopDesigner();
        return false;
    }

    // Resize window to full phone resolution to ensure consistent UI layout
    m_window.ResizeToPhone();
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // Wait for document to load
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));

    // Verify log file was created
    if (!m_logPath.empty()) {
        m_log.Reload();
        if (!m_log.Contains("Document loaded successfully")) {
            std::cerr << "Warning: Document load confirmation not found in log" << std::endl;
        }
    }

    return true;
}

void TestRunner::StopDesigner() {
    if (m_designerProcess) {
        // Try graceful close first
        m_window.Close();

        // Wait briefly for graceful exit
        if (WaitForSingleObject(m_designerProcess, 2000) == WAIT_TIMEOUT) {
            // Force terminate
            TerminateProcess(m_designerProcess, 1);
        }

        CloseHandle(m_designerProcess);
        m_designerProcess = nullptr;
    }
}

TestResult TestRunner::ExecuteTest(const TestCase& test) {
    TestResult result;
    result.name = test.name;

    auto startTime = std::chrono::steady_clock::now();

    try {
        // Reload hierarchy before each test
        m_hierarchy.Reload();

        TestContext ctx{m_window, m_log, m_hierarchy};

        // Clear log before test for cleaner verification
        m_log.Clear();

        bool passed = test.func(ctx);

        result.status = passed ? TestStatus::Passed : TestStatus::Failed;
        result.message = passed ? "Test passed" : "Test assertions failed";

    } catch (const std::exception& e) {
        result.status = TestStatus::Error;
        result.message = std::string("Exception: ") + e.what();
    }

    auto endTime = std::chrono::steady_clock::now();
    result.durationMs = std::chrono::duration<double, std::milli>(endTime - startTime).count();

    return result;
}

TestResult TestRunner::RunTest(const std::string& name) {
    for (const auto& test : m_tests) {
        if (test.name == name) {
            return ExecuteTest(test);
        }
    }

    TestResult result;
    result.name = name;
    result.status = TestStatus::Error;
    result.message = "Test not found";
    return result;
}

TestSuiteResult TestRunner::RunAll() {
    TestSuiteResult suite;
    suite.name = "Mosis Designer UI Tests";

    auto suiteStart = std::chrono::steady_clock::now();

    std::cout << "\n========================================" << std::endl;
    std::cout << "Running " << m_tests.size() << " tests" << std::endl;
    std::cout << "========================================\n" << std::endl;

    for (const auto& test : m_tests) {
        std::cout << "Running: " << test.name << "..." << std::endl;

        TestResult result = ExecuteTest(test);
        suite.tests.push_back(result);

        switch (result.status) {
            case TestStatus::Passed:
                suite.passed++;
                std::cout << "  PASSED (" << result.durationMs << "ms)" << std::endl;
                break;
            case TestStatus::Failed:
                suite.failed++;
                std::cout << "  FAILED: " << result.message << std::endl;
                break;
            case TestStatus::Skipped:
                suite.skipped++;
                std::cout << "  SKIPPED: " << result.message << std::endl;
                break;
            case TestStatus::Error:
                suite.errors++;
                std::cout << "  ERROR: " << result.message << std::endl;
                break;
        }

        // Delay between tests
        std::this_thread::sleep_for(std::chrono::milliseconds(m_actionDelayMs));
    }

    auto suiteEnd = std::chrono::steady_clock::now();
    suite.totalDurationMs = std::chrono::duration<double, std::milli>(suiteEnd - suiteStart).count();

    std::cout << "\n========================================" << std::endl;
    std::cout << "Test Results:" << std::endl;
    std::cout << "  Passed:  " << suite.passed << std::endl;
    std::cout << "  Failed:  " << suite.failed << std::endl;
    std::cout << "  Skipped: " << suite.skipped << std::endl;
    std::cout << "  Errors:  " << suite.errors << std::endl;
    std::cout << "  Total:   " << suite.tests.size() << std::endl;
    std::cout << "  Duration: " << suite.totalDurationMs << "ms" << std::endl;
    std::cout << "========================================\n" << std::endl;

    return suite;
}

} // namespace mosis::test
