#include "GmadExtractor.hpp"
#include "../platform/PathUtils.hpp"
#include "../platform/Process.hpp"

#include <iostream>
#include <sstream>

namespace GModTeach {
namespace Extractor {

namespace fs = std::filesystem;

bool GmadExtractor::MoveItem(const fs::path& src, const fs::path& dst) {
    std::error_code ec;
    fs::create_directories(dst.parent_path(), ec);
    ec.clear();

    fs::rename(src, dst, ec);
    if (!ec) {
        return true;
    }

    // fallba
    ec.clear();
    if (fs::is_directory(src, ec)) {
        fs::copy(src, dst, fs::copy_options::recursive | fs::copy_options::overwrite_existing, ec);
    } else {
        fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
    }

    if (ec) {
        std::cerr << "[ERROR] Could not copy fallback: " << src << " -> " << dst
                  << " (" << ec.message() << ")\n";
        return false;
    }

    ec.clear();
    fs::remove_all(src, ec);
    if (ec) {
        std::cerr << "[WARN] Source could not be removed after copy: " << src
                  << " (" << ec.message() << ")\n";
    }

    return true;
}

bool GmadExtractor::MoveDirectoryContents(
    const fs::path& srcDir,
    const fs::path& dstDir,
    const std::string& skipName
) {
    std::error_code ec;
    if (!fs::exists(srcDir, ec) || !fs::is_directory(srcDir, ec)) {
        return true;
    }

    for (const auto& entry : fs::directory_iterator(srcDir, ec)) {
        if (ec) break;
        const fs::path name = entry.path().filename();
        if (!skipName.empty() && Platform::PathUtils::ToLower(name.string()) == Platform::PathUtils::ToLower(skipName)) {
            continue;
        }

        const fs::path target = dstDir / name;
        if (!MoveItem(entry.path(), target)) {
            return false;
        }
    }
    return true;
}

bool GmadExtractor::ExtractWith7Zip(
    const fs::path& archive,
    const fs::path& outDir,
    const fs::path& custom7zExe
) {
    std::error_code ec;
    fs::create_directories(outDir, ec);

    fs::path exe7z = custom7zExe.empty() ? Platform::PathUtils::Get7zExecutablePath() : custom7zExe;
    if (!fs::exists(exe7z, ec)) {
        exe7z = L"7za.exe";
    }

    std::wstring args = L"x \"" + archive.wstring() + L"\" -o\"" + outDir.wstring() + L"\" -y";
    auto result = Platform::Process::Execute(exe7z, args, true);

    // 7zip always retirn 0 even if it dont fails y
    if (fs::exists(outDir, ec) && !fs::is_empty(outDir, ec)) {
        return true;
    }

    std::cerr << "[ERROR] 7-Zip failed or produced no output. Exit code: " << result.exitCode << "\n";
    return false;
}

bool GmadExtractor::ProcessExtractedFolder(
    const fs::path& extractedDir,
    const fs::path& gmodDir
) {
    std::error_code ec;
    if (!fs::exists(extractedDir, ec) || !fs::is_directory(extractedDir, ec)) {
        std::cerr << "[WARN] Extracted folder not found: " << extractedDir << "\n";
        return false;
    }

    const fs::path mapsDir = extractedDir / "maps";
    const fs::path gmodMaps = gmodDir / "maps";
    const fs::path gmodAddons = gmodDir / "addons";

    const bool hasMaps = fs::exists(mapsDir, ec) && fs::is_directory(mapsDir, ec);

    if (hasMaps) {
        std::cout << "[INFO] Folder contains 'maps': " << extractedDir << "\n";

        // move map files 
        if (!MoveDirectoryContents(mapsDir, gmodMaps)) {
            std::cerr << "[ERROR] Failed to move maps from: " << mapsDir << "\n";
        }

        ec.clear();
        fs::remove_all(mapsDir, ec);

        // move addons
        const fs::path targetAddonFolder = gmodAddons / extractedDir.filename();
        fs::create_directories(targetAddonFolder, ec);
        if (!MoveDirectoryContents(extractedDir, targetAddonFolder, "maps")) {
            std::cerr << "[ERROR] Failed to move remaining content to addons: " << extractedDir << "\n";
        }

        ec.clear();
        fs::remove_all(extractedDir, ec);
    } else {
        std::cout << "[INFO] Folder contains standard addon: " << extractedDir << "\n";
        const fs::path targetAddonFolder = gmodAddons / extractedDir.filename();
        if (!MoveItem(extractedDir, targetAddonFolder)) {
            std::cerr << "[ERROR] Failed to move addon folder: " << extractedDir << "\n";
            return false;
        }
    }

    return true;
}

bool GmadExtractor::ProcessGmaFile(
    const fs::path& gmaPath,
    const fs::path& gmadExe,
    const fs::path& gmodDir
) {
    std::error_code ec;
    if (!fs::exists(gmadExe, ec)) {
        std::cerr << "[ERROR] gmad.exe not found at: " << gmadExe << "\n";
        return false;
    }

    std::wstringstream args;
    args << L"extract -file \"" << gmaPath.wstring() << L"\"";

    auto result = Platform::Process::Execute(gmadExe, args.str(), true);
    if (!result.executed) {
        std::cerr << "[ERROR] Failed to launch gmad.exe for: " << gmaPath << "\n";
        return false;
    }

    const fs::path extractedDir = gmaPath.parent_path() / gmaPath.stem();
    return ProcessExtractedFolder(extractedDir, gmodDir);
}

bool GmadExtractor::ProcessBinFile(
    const fs::path& binPath,
    const fs::path& gmadExe,
    const fs::path& gmodDir,
    const fs::path& custom7zExe
) {
    std::cout << "[INFO] Processing .bin file: " << binPath << "\n";

    const fs::path tempDir = binPath.parent_path() / (binPath.stem().string() + "_extracted");
    
    if (!ExtractWith7Zip(binPath, tempDir, custom7zExe)) {
        std::cerr << "[ERROR] Could not extract .bin with 7-Zip: " << binPath << "\n";
        return false;
    }

    const fs::path innerPayload = Platform::PathUtils::FindInnerPayload(tempDir);
    if (innerPayload.empty()) {
        std::cerr << "[ERROR] No payload found inside extracted .bin: " << tempDir << "\n";
        return false;
    }

    fs::path gmaPath = innerPayload;
    gmaPath.replace_extension(".gma");

    std::error_code ec;
    fs::rename(innerPayload, gmaPath, ec);
    if (ec) {
        std::cerr << "[ERROR] Could not rename payload to .gma: " << ec.message() << "\n";
        return false;
    }

    const bool ok = ProcessGmaFile(gmaPath, gmadExe, gmodDir);

    ec.clear();
    fs::remove_all(tempDir, ec);

    return ok;
}

bool GmadExtractor::CleanSourceDirectory(const fs::path& sourceDir) {
    std::error_code ec;
    if (!fs::exists(sourceDir, ec) || !fs::is_directory(sourceDir, ec)) {
        return false;
    }

    for (const auto& entry : fs::directory_iterator(sourceDir, ec)) {
        if (ec) break;
        fs::remove_all(entry.path(), ec);
    }
    return true;
}

PipelineStats GmadExtractor::RunInstallationPipeline(
    const Core::AppConfig& config,
    bool cleanAfterwards,
    ProgressCallback progressCb
) {
    PipelineStats stats;

    if (!config.IsValid()) {
        std::cerr << "[ERROR] Invalid configuration passed to extraction pipeline.\n";
        return stats;
    }

    const fs::path gmadExe = Platform::PathUtils::GetGmadExecutablePath();

    auto binFiles = Platform::PathUtils::CollectFilesByExtension(config.gmadLsDir, ".bin", true);
    auto gmaFiles = Platform::PathUtils::CollectFilesByExtension(config.gmadLsDir, ".gma", true);

    stats.totalBinsFound = binFiles.size();
    stats.totalGmasFound = gmaFiles.size();

    const size_t totalItems = stats.totalBinsFound + stats.totalGmasFound;
    size_t processedSoFar = 0;

    auto updateProgress = [&](const std::string& msg) {
        if (progressCb) {
            float ratio = (totalItems == 0) ? 1.0f : static_cast<float>(processedSoFar) / static_cast<float>(totalItems);
            progressCb(ratio, msg);
        }
    };

    updateProgress("Starting batch processing...");

    // bin files processing
    for (const auto& bin : binFiles) {
        updateProgress("Decompressing BIN: " + bin.filename().string());
        if (ProcessBinFile(bin, gmadExe, config.gmodDir)) {
            stats.binsProcessed++;
        } else {
            stats.errors++;
        }
        processedSoFar++;
    }

    // rescan 4 gmas
    if (stats.totalBinsFound > 0) {
        gmaFiles = Platform::PathUtils::CollectFilesByExtension(config.gmadLsDir, ".gma", true);
    }

    // process gmas
    for (const auto& gma : gmaFiles) {
        updateProgress("Extracting GMA: " + gma.filename().string());
        if (ProcessGmaFile(gma, gmadExe, config.gmodDir)) {
            stats.gmasProcessed++;
        } else {
            stats.errors++;
        }
        processedSoFar++;
    }

    // clean (optional)
    if (cleanAfterwards) {
        updateProgress("Cleaning source directory...");
        CleanSourceDirectory(config.gmadLsDir);
    }

    updateProgress("Installation complete.");
    return stats;
}

} 
} 
