#include "virtual_fs.h"

#include <lua.hpp>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <chrono>

namespace fs = std::filesystem;

namespace mosis {

//=============================================================================
// VIRTUALFS IMPLEMENTATION
//=============================================================================

VirtualFS::VirtualFS(const std::string& app_id,
                     const std::string& app_root,
                     const VirtualFSLimits& limits)
    : m_app_id(app_id)
    , m_app_root(app_root)
    , m_limits(limits) {
    // Ensure app root exists
    std::error_code ec;
    fs::create_directories(m_app_root, ec);

    // Recalculate usage on startup
    RecalculateUsage();
}

VirtualFS::~VirtualFS() {
}

//=============================================================================
// PATH VALIDATION
//=============================================================================

bool VirtualFS::IsValidPathChar(char c) {
    // Allow alphanumeric, dash, underscore, dot, forward slash
    return std::isalnum(static_cast<unsigned char>(c)) ||
           c == '-' || c == '_' || c == '.' || c == '/';
}

int VirtualFS::GetPathDepth(const std::string& path) {
    int depth = 0;
    for (char c : path) {
        if (c == '/') depth++;
    }
    return depth;
}

bool VirtualFS::ValidatePath(const std::string& virtual_path, std::string& error) {
    // Check length
    if (virtual_path.length() > m_limits.max_path_length) {
        error = "path too long";
        return false;
    }

    // Must start with /
    if (virtual_path.empty() || virtual_path[0] != '/') {
        error = "path must start with /";
        return false;
    }

    // Check valid prefix
    bool valid_prefix = false;
    if (virtual_path.find("/data/") == 0 || virtual_path == "/data") {
        valid_prefix = true;
    } else if (virtual_path.find("/cache/") == 0 || virtual_path == "/cache") {
        valid_prefix = true;
    } else if (virtual_path.find("/temp/") == 0 || virtual_path == "/temp") {
        valid_prefix = true;
    } else if (virtual_path.find("/shared/") == 0 || virtual_path == "/shared") {
        // Check permission for shared
        if (CheckPermission && !CheckPermission("storage.shared")) {
            error = "permission denied: storage.shared required";
            return false;
        }
        valid_prefix = true;
    }

    if (!valid_prefix) {
        error = "invalid path prefix (must be /data/, /cache/, /temp/, or /shared/)";
        return false;
    }

    // Check for path traversal
    if (virtual_path.find("..") != std::string::npos) {
        error = "path traversal not allowed";
        return false;
    }

    // Check for double slashes (except at start)
    if (virtual_path.find("//") != std::string::npos) {
        error = "invalid path (double slashes)";
        return false;
    }

    // Check all characters are valid
    for (char c : virtual_path) {
        if (!IsValidPathChar(c)) {
            error = "invalid character in path";
            return false;
        }
    }

    // Check depth
    if (GetPathDepth(virtual_path) > m_limits.max_path_depth) {
        error = "path too deep";
        return false;
    }

    return true;
}

std::string VirtualFS::ResolvePath(const std::string& virtual_path) {
    // Map virtual path to physical path
    // /data/foo.txt -> <app_root>/data/foo.txt
    // /cache/bar.txt -> <app_root>/cache/bar.txt
    // /temp/baz.txt -> <app_root>/temp/baz.txt
    // /shared/x.txt -> <app_root>/shared/x.txt

    fs::path base(m_app_root);

    // Remove leading slash and append
    std::string relative = virtual_path.substr(1);  // Remove leading /

    return (base / relative).string();
}

//=============================================================================
// FILE OPERATIONS
//=============================================================================

bool VirtualFS::EnsureParentDir(const std::string& path) {
    fs::path p(path);
    fs::path parent = p.parent_path();

    if (parent.empty()) return true;

    std::error_code ec;
    fs::create_directories(parent, ec);
    return !ec;
}

void VirtualFS::UpdateUsage(int64_t delta) {
    if (delta < 0 && static_cast<size_t>(-delta) > m_used_bytes) {
        m_used_bytes = 0;
    } else {
        m_used_bytes = static_cast<size_t>(static_cast<int64_t>(m_used_bytes) + delta);
    }
}

bool VirtualFS::CheckQuota(size_t additional_bytes, std::string& error) {
    if (m_used_bytes + additional_bytes > m_limits.max_quota_bytes) {
        error = "quota exceeded";
        return false;
    }
    return true;
}

std::optional<std::string> VirtualFS::Read(const std::string& path, std::string& error) {
    if (!ValidatePath(path, error)) {
        return std::nullopt;
    }

    std::string physical_path = ResolvePath(path);

    std::ifstream file(physical_path, std::ios::binary);
    if (!file) {
        error = "file not found";
        return std::nullopt;
    }

    std::ostringstream ss;
    ss << file.rdbuf();
    return ss.str();
}

bool VirtualFS::Write(const std::string& path, const std::string& data, std::string& error) {
    if (!ValidatePath(path, error)) {
        return false;
    }

    // Check file size limit
    if (data.size() > m_limits.max_file_size) {
        error = "file size limit exceeded";
        return false;
    }

    std::string physical_path = ResolvePath(path);

    // Get current file size if exists (for quota calculation)
    size_t old_size = 0;
    std::error_code ec;
    if (fs::exists(physical_path, ec)) {
        old_size = static_cast<size_t>(fs::file_size(physical_path, ec));
    }

    // Check quota for net change
    int64_t delta = static_cast<int64_t>(data.size()) - static_cast<int64_t>(old_size);
    if (delta > 0 && !CheckQuota(static_cast<size_t>(delta), error)) {
        return false;
    }

    // Ensure parent directory exists
    if (!EnsureParentDir(physical_path)) {
        error = "failed to create parent directory";
        return false;
    }

    std::ofstream file(physical_path, std::ios::binary | std::ios::trunc);
    if (!file) {
        error = "failed to open file for writing";
        return false;
    }

    file.write(data.data(), data.size());
    if (!file) {
        error = "failed to write data";
        return false;
    }

    file.close();
    UpdateUsage(delta);
    return true;
}

bool VirtualFS::Append(const std::string& path, const std::string& data, std::string& error) {
    if (!ValidatePath(path, error)) {
        return false;
    }

    std::string physical_path = ResolvePath(path);

    // Get current file size
    size_t current_size = 0;
    std::error_code ec;
    if (fs::exists(physical_path, ec)) {
        current_size = static_cast<size_t>(fs::file_size(physical_path, ec));
    }

    // Check file size limit
    if (current_size + data.size() > m_limits.max_file_size) {
        error = "file size limit exceeded";
        return false;
    }

    // Check quota
    if (!CheckQuota(data.size(), error)) {
        return false;
    }

    // Ensure parent directory exists
    if (!EnsureParentDir(physical_path)) {
        error = "failed to create parent directory";
        return false;
    }

    std::ofstream file(physical_path, std::ios::binary | std::ios::app);
    if (!file) {
        error = "failed to open file for appending";
        return false;
    }

    file.write(data.data(), data.size());
    if (!file) {
        error = "failed to append data";
        return false;
    }

    file.close();
    UpdateUsage(static_cast<int64_t>(data.size()));
    return true;
}

bool VirtualFS::Delete(const std::string& path, std::string& error) {
    if (!ValidatePath(path, error)) {
        return false;
    }

    std::string physical_path = ResolvePath(path);

    std::error_code ec;
    if (!fs::exists(physical_path, ec)) {
        error = "file not found";
        return false;
    }

    // Get size before deletion
    size_t file_size = 0;
    if (fs::is_regular_file(physical_path, ec)) {
        file_size = static_cast<size_t>(fs::file_size(physical_path, ec));
    }

    if (!fs::remove(physical_path, ec)) {
        error = "failed to delete";
        return false;
    }

    UpdateUsage(-static_cast<int64_t>(file_size));
    return true;
}

bool VirtualFS::Exists(const std::string& path) {
    std::string error;
    if (!ValidatePath(path, error)) {
        return false;
    }

    std::string physical_path = ResolvePath(path);
    std::error_code ec;
    return fs::exists(physical_path, ec);
}

std::optional<std::vector<std::string>> VirtualFS::List(const std::string& path, std::string& error) {
    if (!ValidatePath(path, error)) {
        return std::nullopt;
    }

    std::string physical_path = ResolvePath(path);

    std::error_code ec;
    if (!fs::exists(physical_path, ec) || !fs::is_directory(physical_path, ec)) {
        error = "directory not found";
        return std::nullopt;
    }

    std::vector<std::string> entries;
    for (const auto& entry : fs::directory_iterator(physical_path, ec)) {
        entries.push_back(entry.path().filename().string());
    }

    if (ec) {
        error = "failed to list directory";
        return std::nullopt;
    }

    return entries;
}

bool VirtualFS::MakeDir(const std::string& path, std::string& error) {
    if (!ValidatePath(path, error)) {
        return false;
    }

    std::string physical_path = ResolvePath(path);

    std::error_code ec;
    if (!fs::create_directories(physical_path, ec) && ec) {
        error = "failed to create directory";
        return false;
    }

    return true;
}

std::optional<FileStat> VirtualFS::Stat(const std::string& path, std::string& error) {
    if (!ValidatePath(path, error)) {
        return std::nullopt;
    }

    std::string physical_path = ResolvePath(path);

    std::error_code ec;
    if (!fs::exists(physical_path, ec)) {
        error = "file not found";
        return std::nullopt;
    }

    FileStat stat;
    stat.is_dir = fs::is_directory(physical_path, ec);

    if (stat.is_dir) {
        stat.size = 0;
    } else {
        stat.size = static_cast<size_t>(fs::file_size(physical_path, ec));
    }

    auto ftime = fs::last_write_time(physical_path, ec);
    // Convert file_time_type to system_clock (portable workaround for clock_cast)
    auto file_time_ns = ftime.time_since_epoch();
    auto sys_time_ns = std::chrono::duration_cast<std::chrono::seconds>(file_time_ns);
    stat.modified = sys_time_ns.count();

    return stat;
}

//=============================================================================
// CLEANUP
//=============================================================================

void VirtualFS::DeleteDirectoryRecursive(const std::string& path) {
    std::error_code ec;
    fs::remove_all(path, ec);
}

size_t VirtualFS::CalculateDirectorySize(const std::string& path) {
    size_t total = 0;
    std::error_code ec;

    if (!fs::exists(path, ec)) {
        return 0;
    }

    for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
        if (fs::is_regular_file(entry, ec)) {
            total += static_cast<size_t>(fs::file_size(entry, ec));
        }
    }

    return total;
}

void VirtualFS::RecalculateUsage() {
    m_used_bytes = CalculateDirectorySize(m_app_root);
}

void VirtualFS::ClearTemp() {
    fs::path temp_path = fs::path(m_app_root) / "temp";

    std::error_code ec;
    if (fs::exists(temp_path, ec)) {
        size_t temp_size = CalculateDirectorySize(temp_path.string());
        DeleteDirectoryRecursive(temp_path.string());
        UpdateUsage(-static_cast<int64_t>(temp_size));
    }
}

void VirtualFS::ClearAll() {
    DeleteDirectoryRecursive(m_app_root);
    m_used_bytes = 0;
}

//=============================================================================
// LUA API
//=============================================================================

static const char* VFS_KEY = "__mosis_vfs";

static VirtualFS* GetVFS(lua_State* L) {
    lua_getfield(L, LUA_REGISTRYINDEX, VFS_KEY);
    if (lua_islightuserdata(L, -1)) {
        VirtualFS* vfs = static_cast<VirtualFS*>(lua_touserdata(L, -1));
        lua_pop(L, 1);
        return vfs;
    }
    lua_pop(L, 1);
    return nullptr;
}

// fs.read(path) -> content|nil, error
static int lua_fs_read(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushnil(L);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    std::string error;

    auto content = vfs->Read(path, error);
    if (content) {
        lua_pushlstring(L, content->data(), content->size());
        return 1;
    } else {
        lua_pushnil(L);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// fs.write(path, data) -> bool, error
static int lua_fs_write(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    size_t len;
    const char* data = luaL_checklstring(L, 2, &len);
    std::string error;

    if (vfs->Write(path, std::string(data, len), error)) {
        lua_pushboolean(L, 1);
        return 1;
    } else {
        lua_pushboolean(L, 0);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// fs.append(path, data) -> bool, error
static int lua_fs_append(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    size_t len;
    const char* data = luaL_checklstring(L, 2, &len);
    std::string error;

    if (vfs->Append(path, std::string(data, len), error)) {
        lua_pushboolean(L, 1);
        return 1;
    } else {
        lua_pushboolean(L, 0);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// fs.delete(path) -> bool, error
static int lua_fs_delete(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    std::string error;

    if (vfs->Delete(path, error)) {
        lua_pushboolean(L, 1);
        return 1;
    } else {
        lua_pushboolean(L, 0);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// fs.exists(path) -> bool
static int lua_fs_exists(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushboolean(L, 0);
        return 1;
    }

    const char* path = luaL_checkstring(L, 1);
    lua_pushboolean(L, vfs->Exists(path) ? 1 : 0);
    return 1;
}

// fs.list(path) -> array|nil, error
static int lua_fs_list(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushnil(L);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    std::string error;

    auto entries = vfs->List(path, error);
    if (entries) {
        lua_createtable(L, static_cast<int>(entries->size()), 0);
        int i = 1;
        for (const auto& name : *entries) {
            lua_pushlstring(L, name.c_str(), name.size());
            lua_rawseti(L, -2, i++);
        }
        return 1;
    } else {
        lua_pushnil(L);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// fs.mkdir(path) -> bool, error
static int lua_fs_mkdir(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushboolean(L, 0);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    std::string error;

    if (vfs->MakeDir(path, error)) {
        lua_pushboolean(L, 1);
        return 1;
    } else {
        lua_pushboolean(L, 0);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// fs.stat(path) -> {size, modified, isDir}|nil, error
static int lua_fs_stat(lua_State* L) {
    VirtualFS* vfs = GetVFS(L);
    if (!vfs) {
        lua_pushnil(L);
        lua_pushstring(L, "VirtualFS not initialized");
        return 2;
    }

    const char* path = luaL_checkstring(L, 1);
    std::string error;

    auto stat = vfs->Stat(path, error);
    if (stat) {
        lua_createtable(L, 0, 3);

        lua_pushinteger(L, static_cast<lua_Integer>(stat->size));
        lua_setfield(L, -2, "size");

        lua_pushinteger(L, static_cast<lua_Integer>(stat->modified));
        lua_setfield(L, -2, "modified");

        lua_pushboolean(L, stat->is_dir ? 1 : 0);
        lua_setfield(L, -2, "isDir");

        return 1;
    } else {
        lua_pushnil(L);
        lua_pushstring(L, error.c_str());
        return 2;
    }
}

// Helper to set a global in the real _G (bypassing any proxy)
static void SetGlobalInRealG(lua_State* L, const char* name) {
    // Stack: value to set as global

    // Get _G (might be a proxy)
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);

    // Check if it has a metatable with __index (proxy pattern)
    if (lua_getmetatable(L, -1)) {
        lua_getfield(L, -1, "__index");
        if (lua_istable(L, -1)) {
            // Found real _G through proxy's __index
            // Stack: value, proxy, mt, real_G
            lua_pushvalue(L, -4);  // Copy value
            lua_setfield(L, -2, name);  // real_G[name] = value
            lua_pop(L, 4);  // pop real_G, mt, proxy, original value
            return;
        }
        lua_pop(L, 2);  // pop __index, metatable
    }

    // No proxy, set directly in _G
    // Stack: value, _G
    lua_pushvalue(L, -2);  // Copy value
    lua_setfield(L, -2, name);  // _G[name] = value
    lua_pop(L, 2);  // pop _G, original value
}

void RegisterVirtualFS(lua_State* L, VirtualFS* vfs) {
    // Store VFS in registry
    lua_pushlightuserdata(L, vfs);
    lua_setfield(L, LUA_REGISTRYINDEX, VFS_KEY);

    // Create fs table
    lua_newtable(L);

    lua_pushcfunction(L, lua_fs_read);
    lua_setfield(L, -2, "read");

    lua_pushcfunction(L, lua_fs_write);
    lua_setfield(L, -2, "write");

    lua_pushcfunction(L, lua_fs_append);
    lua_setfield(L, -2, "append");

    lua_pushcfunction(L, lua_fs_delete);
    lua_setfield(L, -2, "delete");

    lua_pushcfunction(L, lua_fs_exists);
    lua_setfield(L, -2, "exists");

    lua_pushcfunction(L, lua_fs_list);
    lua_setfield(L, -2, "list");

    lua_pushcfunction(L, lua_fs_mkdir);
    lua_setfield(L, -2, "mkdir");

    lua_pushcfunction(L, lua_fs_stat);
    lua_setfield(L, -2, "stat");

    // Set as global (bypassing proxy)
    SetGlobalInRealG(L, "fs");
}

} // namespace mosis
