// app_manager.cpp - App installation and management implementation
// Milestone 10: Device-Side App Management

#include "app_manager.h"
#include <mosis/util/logger.h>

#include "../sandbox/sandbox_manager.h"

#include <fstream>
#include <sstream>
#include <filesystem>
#include <random>
#include <iomanip>
#include <ctime>

// For JSON parsing
#include <nlohmann/json.hpp>

// For ZIP extraction
#include <minizip/unzip.h>

namespace fs = std::filesystem;
using json = nlohmann::json;

namespace mosis {

AppManager::AppManager(const std::string& data_root)
    : m_data_root(data_root)
{
    // Create directory structure
    fs::create_directories(m_data_root + "/apps");
    fs::create_directories(m_data_root + "/downloads");
    fs::create_directories(m_data_root + "/backups");
    fs::create_directories(m_data_root + "/config");

    // Load installed apps registry
    LoadInstalledApps();

    LOG_INFO("AppManager initialized at: %s", m_data_root.c_str());
}

AppManager::~AppManager() {
    SaveInstalledApps();
}

bool AppManager::Install(const std::string& package_url,
                         const std::string& signature,
                         ProgressCallback callback) {
    callback({InstallProgress::Stage::Downloading, 0.0f, ""});

    // Generate download path
    std::string download_path = m_data_root + "/downloads/" + GenerateUUID() + ".mosis";

    // Download package
    if (!DownloadFile(package_url, download_path, [&](float p) {
        callback({InstallProgress::Stage::Downloading, p, ""});
    })) {
        callback({InstallProgress::Stage::Failed, 0.0f, "Download failed"});
        return false;
    }

    callback({InstallProgress::Stage::Verifying, 0.0f, ""});

    // Verify signature
    if (!signature.empty() && !VerifySignature(download_path, signature)) {
        fs::remove(download_path);
        callback({InstallProgress::Stage::Failed, 0.0f, "Signature verification failed"});
        return false;
    }

    // Verify package integrity
    if (!VerifyPackage(download_path)) {
        fs::remove(download_path);
        callback({InstallProgress::Stage::Failed, 0.0f, "Package verification failed"});
        return false;
    }

    // Continue with installation
    bool result = InstallFromFile(download_path, callback);

    // Clean up download
    fs::remove(download_path);

    return result;
}

bool AppManager::InstallFromFile(const std::string& package_path,
                                  ProgressCallback callback) {
    callback({InstallProgress::Stage::Verifying, 0.0f, ""});

    // Extract manifest to get package_id
    auto manifest = ExtractManifest(package_path);
    if (!manifest) {
        callback({InstallProgress::Stage::Failed, 0.0f, "Invalid manifest"});
        return false;
    }

    LOG_INFO("Installing app: %s v%s", manifest->id.c_str(), manifest->version.c_str());

    callback({InstallProgress::Stage::Extracting, 0.0f, ""});

    // Determine installation path
    std::string install_path = m_data_root + "/apps/" + manifest->id;

    // Check if already installed (update path)
    if (fs::exists(install_path + "/package")) {
        LOG_INFO("App already installed, updating: %s", manifest->id.c_str());
        // Backup existing data
        BackupAppData(manifest->id);
        // Remove old package
        fs::remove_all(install_path + "/package");
    }

    // Create directories
    fs::create_directories(install_path + "/package");
    fs::create_directories(install_path + "/data");
    fs::create_directories(install_path + "/cache");
    fs::create_directories(install_path + "/db");

    // Extract package
    if (!ExtractPackage(package_path, install_path + "/package")) {
        callback({InstallProgress::Stage::Failed, 0.0f, "Extraction failed"});
        return false;
    }

    callback({InstallProgress::Stage::Registering, 0.0f, ""});

    // Get package file size
    int64_t package_size = 0;
    try {
        package_size = static_cast<int64_t>(fs::file_size(package_path));
    } catch (...) {
        package_size = 0;
    }

    // Register app
    InstalledApp app;
    app.package_id = manifest->id;
    app.name = manifest->name;
    app.version_name = manifest->version;
    app.version_code = manifest->version_code;
    app.install_path = install_path;
    app.permissions = manifest->permissions;
    app.installed_at = std::chrono::system_clock::now();
    app.updated_at = std::chrono::system_clock::now();
    app.package_size = package_size;
    app.data_size = 0;
    app.is_system_app = false;
    app.entry_point = manifest->entry;
    app.icon_path = manifest->icon;
    app.developer_name = manifest->developer_name;

    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_installed_apps[manifest->id] = app;
        SaveInstalledApps();
    }

    LOG_INFO("App installed successfully: %s", manifest->id.c_str());
    callback({InstallProgress::Stage::Complete, 1.0f, ""});
    return true;
}

bool AppManager::Uninstall(const std::string& package_id, bool keep_data) {
    std::lock_guard<std::mutex> lock(m_mutex);

    auto it = m_installed_apps.find(package_id);
    if (it == m_installed_apps.end()) {
        LOG_WARN("Cannot uninstall: app not found: %s", package_id.c_str());
        return false;
    }

    // Cannot uninstall system apps
    if (it->second.is_system_app) {
        LOG_WARN("Cannot uninstall system app: %s", package_id.c_str());
        return false;
    }

    LOG_INFO("Uninstalling app: %s (keep_data=%d)", package_id.c_str(), keep_data);

    // Stop app if running
    if (m_sandbox_manager && m_sandbox_manager->IsAppRunning(package_id)) {
        LOG_INFO("Stopping running app before uninstall: %s", package_id.c_str());
        m_sandbox_manager->StopApp(package_id);
    }

    // Remove files
    std::string install_path = it->second.install_path;

    try {
        fs::remove_all(install_path + "/package");
        fs::remove_all(install_path + "/cache");

        if (!keep_data) {
            fs::remove_all(install_path + "/data");
            fs::remove_all(install_path + "/db");
            fs::remove_all(install_path);
        }
    } catch (const std::exception& e) {
        LOG_ERROR("Error removing app files: %s", e.what());
        return false;
    }

    // Unregister
    m_installed_apps.erase(it);
    SaveInstalledApps();

    LOG_INFO("App uninstalled: %s", package_id.c_str());
    return true;
}

bool AppManager::Update(const std::string& package_id,
                        const std::string& package_url,
                        const std::string& signature,
                        ProgressCallback callback) {
    // Updates use the same flow as Install, which handles existing installations
    return Install(package_url, signature, callback);
}

std::vector<InstalledApp> AppManager::GetInstalledApps() const {
    std::lock_guard<std::mutex> lock(m_mutex);

    std::vector<InstalledApp> apps;
    apps.reserve(m_installed_apps.size());
    for (const auto& [id, app] : m_installed_apps) {
        apps.push_back(app);
    }
    return apps;
}

std::optional<InstalledApp> AppManager::GetApp(const std::string& package_id) const {
    std::lock_guard<std::mutex> lock(m_mutex);

    auto it = m_installed_apps.find(package_id);
    if (it != m_installed_apps.end()) {
        return it->second;
    }
    return std::nullopt;
}

bool AppManager::IsInstalled(const std::string& package_id) const {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_installed_apps.find(package_id) != m_installed_apps.end();
}

int64_t AppManager::GetAppDataSize(const std::string& package_id) const {
    std::string data_path = GetAppDataPath(package_id);
    return CalculateDirectorySize(data_path);
}

bool AppManager::ClearAppData(const std::string& package_id) {
    std::string data_path = GetAppDataPath(package_id);
    std::string db_path = m_data_root + "/apps/" + package_id + "/db";

    try {
        fs::remove_all(data_path);
        fs::remove_all(db_path);
        fs::create_directories(data_path);
        fs::create_directories(db_path);
        LOG_INFO("Cleared app data: %s", package_id.c_str());
        return true;
    } catch (const std::exception& e) {
        LOG_ERROR("Error clearing app data: %s", e.what());
        return false;
    }
}

bool AppManager::ClearAppCache(const std::string& package_id) {
    std::string cache_path = GetAppCachePath(package_id);

    try {
        fs::remove_all(cache_path);
        fs::create_directories(cache_path);
        LOG_INFO("Cleared app cache: %s", package_id.c_str());
        return true;
    } catch (const std::exception& e) {
        LOG_ERROR("Error clearing app cache: %s", e.what());
        return false;
    }
}

bool AppManager::BackupAppData(const std::string& package_id) {
    std::string data_path = GetAppDataPath(package_id);
    std::string backup_path = m_data_root + "/backups/" + package_id;

    try {
        if (fs::exists(data_path)) {
            fs::remove_all(backup_path);
            fs::copy(data_path, backup_path, fs::copy_options::recursive);
            LOG_INFO("Backed up app data: %s", package_id.c_str());
        }
        return true;
    } catch (const std::exception& e) {
        LOG_ERROR("Error backing up app data: %s", e.what());
        return false;
    }
}

bool AppManager::RestoreAppData(const std::string& package_id) {
    std::string data_path = GetAppDataPath(package_id);
    std::string backup_path = m_data_root + "/backups/" + package_id;

    try {
        if (fs::exists(backup_path)) {
            fs::remove_all(data_path);
            fs::copy(backup_path, data_path, fs::copy_options::recursive);
            LOG_INFO("Restored app data: %s", package_id.c_str());
        }
        return true;
    } catch (const std::exception& e) {
        LOG_ERROR("Error restoring app data: %s", e.what());
        return false;
    }
}

bool AppManager::LaunchApp(const std::string& package_id) {
    auto app = GetApp(package_id);
    if (!app) {
        LOG_ERROR("Cannot launch app: not installed: %s", package_id.c_str());
        return false;
    }

    if (!m_sandbox_manager) {
        LOG_ERROR("Cannot launch app: sandbox manager not set");
        return false;
    }

    if (m_sandbox_manager->IsAppRunning(package_id)) {
        LOG_WARN("App already running: %s", package_id.c_str());
        return true;
    }

    // Determine app path - check for package/ subfolder or direct structure
    std::string app_path;
    std::string package_subdir = app->install_path + "/package";
    std::string entry_in_package = package_subdir + "/" + app->entry_point;
    std::string entry_direct = app->install_path + "/" + app->entry_point;

    if (fs::exists(entry_in_package)) {
        // Installed package structure: files in package/ subfolder
        app_path = package_subdir;
    } else if (fs::exists(entry_direct)) {
        // Direct structure: files in install_path directly
        app_path = app->install_path;
    } else {
        LOG_ERROR("Cannot launch app: entry point not found: %s", app->entry_point.c_str());
        return false;
    }

    LOG_INFO("Launching app: %s from %s", package_id.c_str(), app_path.c_str());

    return m_sandbox_manager->StartApp(package_id, app_path, app->permissions, app->is_system_app);
}

bool AppManager::StopApp(const std::string& package_id) {
    if (!m_sandbox_manager) {
        LOG_ERROR("Cannot stop app: sandbox manager not set");
        return false;
    }

    if (!m_sandbox_manager->IsAppRunning(package_id)) {
        LOG_WARN("App not running: %s", package_id.c_str());
        return true;
    }

    LOG_INFO("Stopping app: %s", package_id.c_str());
    return m_sandbox_manager->StopApp(package_id);
}

bool AppManager::IsAppRunning(const std::string& package_id) const {
    if (!m_sandbox_manager) {
        return false;
    }
    return m_sandbox_manager->IsAppRunning(package_id);
}

void AppManager::SetSandboxManager(LuaSandboxManager* manager) {
    m_sandbox_manager = manager;
}

std::string AppManager::GetAppPath(const std::string& package_id) const {
    return m_data_root + "/apps/" + package_id + "/package";
}

std::string AppManager::GetAppDataPath(const std::string& package_id) const {
    return m_data_root + "/apps/" + package_id + "/data";
}

std::string AppManager::GetAppCachePath(const std::string& package_id) const {
    return m_data_root + "/apps/" + package_id + "/cache";
}

void AppManager::RegisterSystemApp(const InstalledApp& app) {
    std::lock_guard<std::mutex> lock(m_mutex);

    InstalledApp system_app = app;
    system_app.is_system_app = true;
    m_installed_apps[app.package_id] = system_app;

    LOG_INFO("Registered system app: %s", app.package_id.c_str());
}

bool AppManager::VerifyPackage(const std::string& path) {
    // Verify ZIP structure and manifest presence
    unzFile zip = unzOpen(path.c_str());
    if (!zip) {
        LOG_ERROR("Cannot open package: %s", path.c_str());
        return false;
    }

    bool has_manifest = false;

    if (unzGoToFirstFile(zip) == UNZ_OK) {
        do {
            char filename[256];
            unz_file_info file_info;
            if (unzGetCurrentFileInfo(zip, &file_info, filename, sizeof(filename),
                                      nullptr, 0, nullptr, 0) == UNZ_OK) {
                if (std::string(filename) == "manifest.json") {
                    has_manifest = true;
                    break;
                }
            }
        } while (unzGoToNextFile(zip) == UNZ_OK);
    }

    unzClose(zip);

    if (!has_manifest) {
        LOG_ERROR("Package missing manifest.json: %s", path.c_str());
        return false;
    }

    return true;
}

bool AppManager::VerifySignature(const std::string& path, const std::string& signature) {
    // TODO: Implement Ed25519 signature verification
    // For now, accept packages without strict verification
    LOG_WARN("Signature verification not yet implemented");
    return true;
}

std::optional<AppManifest> AppManager::ExtractManifest(const std::string& package_path) {
    unzFile zip = unzOpen(package_path.c_str());
    if (!zip) {
        return std::nullopt;
    }

    std::string manifest_content;

    // Find and read manifest.json
    if (unzLocateFile(zip, "manifest.json", 0) != UNZ_OK) {
        unzClose(zip);
        return std::nullopt;
    }

    if (unzOpenCurrentFile(zip) != UNZ_OK) {
        unzClose(zip);
        return std::nullopt;
    }

    char buffer[4096];
    int bytes_read;
    while ((bytes_read = unzReadCurrentFile(zip, buffer, sizeof(buffer))) > 0) {
        manifest_content.append(buffer, bytes_read);
    }

    unzCloseCurrentFile(zip);
    unzClose(zip);

    // Parse JSON
    try {
        json j = json::parse(manifest_content);

        AppManifest manifest;
        manifest.id = j.value("id", "");
        manifest.name = j.value("name", "");
        manifest.version = j.value("version", "1.0.0");
        manifest.version_code = j.value("version_code", 1);
        manifest.entry = j.value("entry", "main.rml");
        manifest.icon = j.value("icon", "");
        manifest.description = j.value("description", "");
        manifest.min_api_version = j.value("min_api_version", 1);

        if (j.contains("developer")) {
            manifest.developer_name = j["developer"].value("name", "");
            manifest.developer_email = j["developer"].value("email", "");
        }

        if (j.contains("permissions") && j["permissions"].is_array()) {
            for (const auto& perm : j["permissions"]) {
                manifest.permissions.push_back(perm.get<std::string>());
            }
        }

        if (manifest.id.empty()) {
            LOG_ERROR("Manifest missing required 'id' field");
            return std::nullopt;
        }

        return manifest;

    } catch (const json::exception& e) {
        LOG_ERROR("Failed to parse manifest: %s", e.what());
        return std::nullopt;
    }
}

bool AppManager::ExtractPackage(const std::string& package_path, const std::string& dest_path) {
    unzFile zip = unzOpen(package_path.c_str());
    if (!zip) {
        LOG_ERROR("Cannot open package for extraction: %s", package_path.c_str());
        return false;
    }

    bool success = true;

    if (unzGoToFirstFile(zip) == UNZ_OK) {
        do {
            char filename[512];
            unz_file_info file_info;

            if (unzGetCurrentFileInfo(zip, &file_info, filename, sizeof(filename),
                                      nullptr, 0, nullptr, 0) != UNZ_OK) {
                continue;
            }

            std::string full_path = dest_path + "/" + filename;

            // Skip META-INF directory (signatures)
            if (std::string(filename).rfind("META-INF/", 0) == 0) {
                continue;
            }

            // Create directories
            size_t len = strlen(filename);
            if (len > 0 && filename[len - 1] == '/') {
                fs::create_directories(full_path);
                continue;
            }

            // Ensure parent directory exists
            fs::create_directories(fs::path(full_path).parent_path());

            // Extract file
            if (unzOpenCurrentFile(zip) != UNZ_OK) {
                LOG_ERROR("Cannot open file in archive: %s", filename);
                success = false;
                break;
            }

            std::ofstream out(full_path, std::ios::binary);
            if (!out) {
                LOG_ERROR("Cannot create file: %s", full_path.c_str());
                unzCloseCurrentFile(zip);
                success = false;
                break;
            }

            char buffer[8192];
            int bytes_read;
            while ((bytes_read = unzReadCurrentFile(zip, buffer, sizeof(buffer))) > 0) {
                out.write(buffer, bytes_read);
            }

            out.close();
            unzCloseCurrentFile(zip);

        } while (unzGoToNextFile(zip) == UNZ_OK);
    }

    unzClose(zip);
    return success;
}

bool AppManager::DownloadFile(const std::string& url, const std::string& dest_path,
                               std::function<void(float)> progress_callback) {
    // TODO: Implement HTTP download using platform-specific APIs
    // For now, return false as this is a placeholder
    LOG_ERROR("HTTP download not yet implemented for: %s", url.c_str());
    return false;
}

void AppManager::ScanAppsDirectory() {
    std::string apps_dir = m_data_root + "/apps";

    try {
        if (!fs::exists(apps_dir)) {
            return;
        }
    } catch (const std::exception& e) {
        LOG_ERROR("Cannot access apps directory: %s", e.what());
        return;
    }

    LOG_INFO("Scanning apps directory: %s", apps_dir.c_str());

    try {
        for (const auto& entry : fs::directory_iterator(apps_dir)) {
            try {
                if (!entry.is_directory()) {
                    continue;
                }

                std::string package_id = entry.path().filename().string();

                // Skip if already registered
                if (m_installed_apps.find(package_id) != m_installed_apps.end()) {
                    continue;
                }

                // Look for manifest.json in direct folder or package/ subfolder
                std::string manifest_path = entry.path().string() + "/manifest.json";
                std::string manifest_path_pkg = entry.path().string() + "/package/manifest.json";

                std::string actual_manifest;
                std::string app_base_path;

                // Use error_code overload to avoid exceptions on permission errors
                std::error_code ec;
                if (fs::exists(manifest_path, ec) && !ec) {
                    actual_manifest = manifest_path;
                    app_base_path = entry.path().string();
                } else if (fs::exists(manifest_path_pkg, ec) && !ec) {
                    actual_manifest = manifest_path_pkg;
                    app_base_path = entry.path().string() + "/package";
                } else {
                    if (ec) {
                        LOG_WARN("Cannot access %s: %s", package_id.c_str(), ec.message().c_str());
                    }
                    continue;
                }

                // Parse manifest
                std::ifstream mf(actual_manifest);
                if (!mf) {
                    continue;
                }

                json j;
                mf >> j;

                InstalledApp app;
                app.package_id = j.value("id", package_id);
                app.name = j.value("name", package_id);
                app.version_name = j.value("version", "1.0.0");
                app.version_code = j.value("version_code", 1);
                app.install_path = entry.path().string();
                app.entry_point = j.value("entry", "main.rml");
                app.icon_path = j.value("icon", "");
                app.is_system_app = false;

                if (j.contains("developer")) {
                    app.developer_name = j["developer"].value("name", "");
                }

                if (j.contains("permissions") && j["permissions"].is_array()) {
                    for (const auto& perm : j["permissions"]) {
                        app.permissions.push_back(perm.get<std::string>());
                    }
                }

                app.installed_at = std::chrono::system_clock::now();
                app.updated_at = std::chrono::system_clock::now();

                m_installed_apps[app.package_id] = app;
                LOG_INFO("Discovered app: %s (%s)", app.name.c_str(), app.package_id.c_str());

            } catch (const std::exception& e) {
                LOG_WARN("Error processing app entry: %s", e.what());
            }
        }
    } catch (const std::exception& e) {
        LOG_ERROR("Error scanning apps directory: %s", e.what());
    }
}

void AppManager::LoadInstalledApps() {
    std::string registry_path = m_data_root + "/config/apps.json";

    std::ifstream file(registry_path);
    if (!file) {
        LOG_INFO("No existing app registry found, scanning directory...");
        ScanAppsDirectory();
        return;
    }

    try {
        json j;
        file >> j;

        if (j.contains("apps") && j["apps"].is_array()) {
            for (const auto& app_json : j["apps"]) {
                InstalledApp app;
                app.package_id = app_json.value("package_id", "");
                app.name = app_json.value("name", "");
                app.version_name = app_json.value("version_name", "");
                app.version_code = app_json.value("version_code", 0);
                app.install_path = app_json.value("install_path", "");
                app.package_size = app_json.value("package_size", 0);
                app.data_size = app_json.value("data_size", 0);
                app.is_system_app = app_json.value("is_system_app", false);
                app.entry_point = app_json.value("entry_point", "main.rml");
                app.icon_path = app_json.value("icon_path", "");
                app.developer_name = app_json.value("developer_name", "");

                if (app_json.contains("permissions") && app_json["permissions"].is_array()) {
                    for (const auto& perm : app_json["permissions"]) {
                        app.permissions.push_back(perm.get<std::string>());
                    }
                }

                // Parse timestamps
                if (app_json.contains("installed_at")) {
                    auto ts = app_json["installed_at"].get<int64_t>();
                    app.installed_at = std::chrono::system_clock::time_point(
                        std::chrono::seconds(ts));
                }
                if (app_json.contains("updated_at")) {
                    auto ts = app_json["updated_at"].get<int64_t>();
                    app.updated_at = std::chrono::system_clock::time_point(
                        std::chrono::seconds(ts));
                }

                if (!app.package_id.empty()) {
                    m_installed_apps[app.package_id] = app;
                }
            }
        }

        LOG_INFO("Loaded %zu apps from registry", m_installed_apps.size());

    } catch (const std::exception& e) {
        LOG_ERROR("Error loading app registry: %s", e.what());
    }

    // Always scan directory to discover newly copied apps
    ScanAppsDirectory();
    LOG_INFO("Total installed apps: %zu", m_installed_apps.size());
}

void AppManager::SaveInstalledApps() {
    std::string registry_path = m_data_root + "/config/apps.json";

    json j;
    j["version"] = 1;
    j["apps"] = json::array();

    for (const auto& [id, app] : m_installed_apps) {
        json app_json;
        app_json["package_id"] = app.package_id;
        app_json["name"] = app.name;
        app_json["version_name"] = app.version_name;
        app_json["version_code"] = app.version_code;
        app_json["install_path"] = app.install_path;
        app_json["permissions"] = app.permissions;
        app_json["package_size"] = app.package_size;
        app_json["data_size"] = app.data_size;
        app_json["is_system_app"] = app.is_system_app;
        app_json["entry_point"] = app.entry_point;
        app_json["icon_path"] = app.icon_path;
        app_json["developer_name"] = app.developer_name;

        // Store timestamps as Unix seconds
        app_json["installed_at"] = std::chrono::duration_cast<std::chrono::seconds>(
            app.installed_at.time_since_epoch()).count();
        app_json["updated_at"] = std::chrono::duration_cast<std::chrono::seconds>(
            app.updated_at.time_since_epoch()).count();

        j["apps"].push_back(app_json);
    }

    std::ofstream file(registry_path);
    if (file) {
        file << j.dump(2);
        LOG_DEBUG("Saved app registry with %zu apps", m_installed_apps.size());
    } else {
        LOG_ERROR("Failed to save app registry");
    }
}

int64_t AppManager::CalculateDirectorySize(const std::string& path) const {
    int64_t size = 0;
    try {
        for (const auto& entry : fs::recursive_directory_iterator(path)) {
            if (entry.is_regular_file()) {
                size += static_cast<int64_t>(entry.file_size());
            }
        }
    } catch (...) {
        // Directory may not exist
    }
    return size;
}

std::string AppManager::GenerateUUID() const {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 15);

    std::stringstream ss;
    for (int i = 0; i < 32; ++i) {
        if (i == 8 || i == 12 || i == 16 || i == 20) {
            ss << '-';
        }
        ss << std::hex << dis(gen);
    }
    return ss.str();
}

} // namespace mosis
