#include "FileSystemTools.hpp"

#include <filesystem>
#include <fstream>
#include <sstream>
#include <regex>
#include <algorithm>

namespace fs = std::filesystem;

namespace celuna::tools {

// Static configuration defaults
std::vector<std::string> FileSystemTools::s_allowedPaths = {};
std::vector<std::string> FileSystemTools::s_blockedPatterns = {"*.env", "*credentials*", "*.key", "*.pem"};
size_t FileSystemTools::s_maxReadSize = 1024 * 1024;      // 1MB
size_t FileSystemTools::s_maxWriteSize = 10 * 1024 * 1024; // 10MB

// ============================================================================
// Tool Definitions
// ============================================================================

std::vector<json> FileSystemTools::getToolDefinitions() {
    return {
        {
            {"name", "read_file"},
            {"description", "Read content from a file. Returns file content with optional line offset and limit."},
            {"input_schema", {
                {"type", "object"},
                {"properties", {
                    {"path", {{"type", "string"}, {"description", "Absolute path to the file"}}},
                    {"offset", {{"type", "integer"}, {"description", "Line number to start from (0-based)"}}},
                    {"limit", {{"type", "integer"}, {"description", "Maximum number of lines to read"}}}
                }},
                {"required", json::array({"path"})}
            }}
        },
        {
            {"name", "write_file"},
            {"description", "Write content to a file. Creates the file if it doesn't exist, overwrites if it does."},
            {"input_schema", {
                {"type", "object"},
                {"properties", {
                    {"path", {{"type", "string"}, {"description", "Absolute path to the file"}}},
                    {"content", {{"type", "string"}, {"description", "Content to write to the file"}}}
                }},
                {"required", json::array({"path", "content"})}
            }}
        },
        {
            {"name", "edit_file"},
            {"description", "Replace an exact string in a file. The old_string must be unique in the file."},
            {"input_schema", {
                {"type", "object"},
                {"properties", {
                    {"path", {{"type", "string"}, {"description", "Absolute path to the file"}}},
                    {"old_string", {{"type", "string"}, {"description", "The exact string to find and replace"}}},
                    {"new_string", {{"type", "string"}, {"description", "The string to replace with"}}},
                    {"replace_all", {{"type", "boolean"}, {"description", "Replace all occurrences (default: false)"}}}
                }},
                {"required", json::array({"path", "old_string", "new_string"})}
            }}
        },
        {
            {"name", "list_directory"},
            {"description", "List contents of a directory."},
            {"input_schema", {
                {"type", "object"},
                {"properties", {
                    {"path", {{"type", "string"}, {"description", "Absolute path to the directory"}}}
                }},
                {"required", json::array({"path"})}
            }}
        },
        {
            {"name", "glob_files"},
            {"description", "Search for files matching a glob pattern (e.g., **/*.cpp)."},
            {"input_schema", {
                {"type", "object"},
                {"properties", {
                    {"pattern", {{"type", "string"}, {"description", "Glob pattern to match"}}},
                    {"path", {{"type", "string"}, {"description", "Base directory to search in"}}}
                }},
                {"required", json::array({"pattern"})}
            }}
        },
        {
            {"name", "grep_files"},
            {"description", "Search for content matching a regex pattern in files."},
            {"input_schema", {
                {"type", "object"},
                {"properties", {
                    {"pattern", {{"type", "string"}, {"description", "Regex pattern to search for"}}},
                    {"path", {{"type", "string"}, {"description", "Directory or file to search in"}}},
                    {"glob", {{"type", "string"}, {"description", "Optional glob pattern to filter files"}}}
                }},
                {"required", json::array({"pattern"})}
            }}
        }
    };
}

// ============================================================================
// Tool Execution
// ============================================================================

bool FileSystemTools::isFileSystemTool(const std::string& toolName) {
    static const std::vector<std::string> tools = {
        "read_file", "write_file", "edit_file",
        "list_directory", "glob_files", "grep_files"
    };
    return std::find(tools.begin(), tools.end(), toolName) != tools.end();
}

json FileSystemTools::execute(const std::string& toolName, const json& params) {
    try {
        if (toolName == "read_file") return readFile(params);
        if (toolName == "write_file") return writeFile(params);
        if (toolName == "edit_file") return editFile(params);
        if (toolName == "list_directory") return listDirectory(params);
        if (toolName == "glob_files") return globFiles(params);
        if (toolName == "grep_files") return grepFiles(params);
        return makeError("Unknown tool: " + toolName);
    } catch (const std::exception& e) {
        return makeError(std::string("Exception: ") + e.what());
    }
}

// ============================================================================
// Tool Implementations
// ============================================================================

json FileSystemTools::readFile(const json& params) {
    if (!params.contains("path")) {
        return makeError("Missing required parameter: path");
    }

    std::string path = params["path"].get<std::string>();
    std::string canonPath = canonicalizePath(path);

    if (!isPathAllowed(canonPath)) {
        return makeError("Path not allowed: " + path);
    }

    if (isPatternBlocked(canonPath)) {
        return makeError("File pattern blocked for security: " + path);
    }

    if (!fs::exists(canonPath)) {
        return makeError("File not found: " + path);
    }

    if (!fs::is_regular_file(canonPath)) {
        return makeError("Not a regular file: " + path);
    }

    auto fileSize = fs::file_size(canonPath);
    if (fileSize > s_maxReadSize) {
        return makeError("File too large: " + std::to_string(fileSize) + " bytes (max: " + std::to_string(s_maxReadSize) + ")");
    }

    std::ifstream file(canonPath);
    if (!file.is_open()) {
        return makeError("Cannot open file: " + path);
    }

    int offset = params.value("offset", 0);
    int limit = params.value("limit", -1);

    std::vector<std::string> lines;
    std::string line;
    int lineNum = 0;

    while (std::getline(file, line)) {
        if (lineNum >= offset) {
            if (limit > 0 && static_cast<int>(lines.size()) >= limit) {
                break;
            }
            lines.push_back(line);
        }
        lineNum++;
    }

    // Build numbered output like Claude Code
    std::ostringstream content;
    for (size_t i = 0; i < lines.size(); i++) {
        content << (offset + i + 1) << "\t" << lines[i] << "\n";
    }

    return makeSuccess({
        {"content", content.str()},
        {"lines_read", lines.size()},
        {"total_lines", lineNum},
        {"path", canonPath}
    });
}

json FileSystemTools::writeFile(const json& params) {
    if (!params.contains("path") || !params.contains("content")) {
        return makeError("Missing required parameters: path, content");
    }

    std::string path = params["path"].get<std::string>();
    std::string content = params["content"].get<std::string>();
    std::string canonPath = canonicalizePath(path);

    if (!isPathAllowed(canonPath)) {
        return makeError("Path not allowed: " + path);
    }

    if (isPatternBlocked(canonPath)) {
        return makeError("File pattern blocked for security: " + path);
    }

    if (content.size() > s_maxWriteSize) {
        return makeError("Content too large: " + std::to_string(content.size()) + " bytes (max: " + std::to_string(s_maxWriteSize) + ")");
    }

    // Create parent directories if needed
    fs::path filePath(canonPath);
    if (filePath.has_parent_path()) {
        fs::create_directories(filePath.parent_path());
    }

    std::ofstream file(canonPath);
    if (!file.is_open()) {
        return makeError("Cannot create/open file: " + path);
    }

    file << content;
    file.close();

    return makeSuccess({
        {"message", "File written successfully"},
        {"path", canonPath},
        {"bytes_written", content.size()}
    });
}

json FileSystemTools::editFile(const json& params) {
    if (!params.contains("path") || !params.contains("old_string") || !params.contains("new_string")) {
        return makeError("Missing required parameters: path, old_string, new_string");
    }

    std::string path = params["path"].get<std::string>();
    std::string oldString = params["old_string"].get<std::string>();
    std::string newString = params["new_string"].get<std::string>();
    bool replaceAll = params.value("replace_all", false);
    std::string canonPath = canonicalizePath(path);

    if (!isPathAllowed(canonPath)) {
        return makeError("Path not allowed: " + path);
    }

    if (isPatternBlocked(canonPath)) {
        return makeError("File pattern blocked for security: " + path);
    }

    if (!fs::exists(canonPath)) {
        return makeError("File not found: " + path);
    }

    // Read file content
    std::ifstream inFile(canonPath);
    if (!inFile.is_open()) {
        return makeError("Cannot open file: " + path);
    }

    std::ostringstream buffer;
    buffer << inFile.rdbuf();
    std::string content = buffer.str();
    inFile.close();

    // Count occurrences
    size_t count = 0;
    size_t pos = 0;
    while ((pos = content.find(oldString, pos)) != std::string::npos) {
        count++;
        pos += oldString.length();
    }

    if (count == 0) {
        return makeError("String not found in file: " + oldString.substr(0, 50) + (oldString.length() > 50 ? "..." : ""));
    }

    if (count > 1 && !replaceAll) {
        return makeError("String found " + std::to_string(count) + " times. Use replace_all=true to replace all, or provide more context to make it unique.");
    }

    // Replace
    std::string newContent;
    if (replaceAll) {
        newContent = content;
        pos = 0;
        while ((pos = newContent.find(oldString, pos)) != std::string::npos) {
            newContent.replace(pos, oldString.length(), newString);
            pos += newString.length();
        }
    } else {
        pos = content.find(oldString);
        newContent = content.substr(0, pos) + newString + content.substr(pos + oldString.length());
    }

    // Write back
    std::ofstream outFile(canonPath);
    if (!outFile.is_open()) {
        return makeError("Cannot write file: " + path);
    }
    outFile << newContent;
    outFile.close();

    return makeSuccess({
        {"message", "File edited successfully"},
        {"path", canonPath},
        {"replacements", replaceAll ? count : 1}
    });
}

json FileSystemTools::listDirectory(const json& params) {
    if (!params.contains("path")) {
        return makeError("Missing required parameter: path");
    }

    std::string path = params["path"].get<std::string>();
    std::string canonPath = canonicalizePath(path);

    if (!isPathAllowed(canonPath)) {
        return makeError("Path not allowed: " + path);
    }

    if (!fs::exists(canonPath)) {
        return makeError("Directory not found: " + path);
    }

    if (!fs::is_directory(canonPath)) {
        return makeError("Not a directory: " + path);
    }

    json entries = json::array();
    for (const auto& entry : fs::directory_iterator(canonPath)) {
        json item;
        item["name"] = entry.path().filename().string();
        item["type"] = entry.is_directory() ? "directory" : "file";
        if (entry.is_regular_file()) {
            item["size"] = entry.file_size();
        }
        entries.push_back(item);
    }

    return makeSuccess({
        {"path", canonPath},
        {"entries", entries},
        {"count", entries.size()}
    });
}

json FileSystemTools::globFiles(const json& params) {
    if (!params.contains("pattern")) {
        return makeError("Missing required parameter: pattern");
    }

    std::string pattern = params["pattern"].get<std::string>();
    std::string basePath = params.value("path", fs::current_path().string());
    std::string canonBase = canonicalizePath(basePath);

    if (!isPathAllowed(canonBase)) {
        return makeError("Path not allowed: " + basePath);
    }

    if (!fs::exists(canonBase) || !fs::is_directory(canonBase)) {
        return makeError("Base directory not found: " + basePath);
    }

    // Simple glob implementation (supports * and **)
    json matches = json::array();
    bool recursive = pattern.find("**") != std::string::npos;

    // Convert glob to regex
    std::string regexPattern = pattern;
    // Escape special regex chars except * and ?
    for (auto c : {'\\', '.', '+', '^', '$', '|', '(', ')', '[', ']', '{', '}'}) {
        size_t pos = 0;
        while ((pos = regexPattern.find(c, pos)) != std::string::npos) {
            regexPattern.insert(pos, "\\");
            pos += 2;
        }
    }
    // Convert glob wildcards to regex
    size_t pos = 0;
    while ((pos = regexPattern.find("**", pos)) != std::string::npos) {
        regexPattern.replace(pos, 2, ".*");
        pos += 2;
    }
    pos = 0;
    while ((pos = regexPattern.find("*", pos)) != std::string::npos) {
        if (pos == 0 || regexPattern[pos-1] != '.') {
            regexPattern.replace(pos, 1, "[^/]*");
            pos += 5;
        } else {
            pos++;
        }
    }
    pos = 0;
    while ((pos = regexPattern.find("?", pos)) != std::string::npos) {
        regexPattern.replace(pos, 1, ".");
        pos++;
    }

    std::regex rx(regexPattern, std::regex::icase);

    auto iterator = recursive ?
        fs::recursive_directory_iterator(canonBase) :
        fs::recursive_directory_iterator(canonBase);

    for (const auto& entry : fs::recursive_directory_iterator(canonBase)) {
        if (entry.is_regular_file()) {
            std::string relPath = fs::relative(entry.path(), canonBase).string();
            if (std::regex_match(relPath, rx)) {
                matches.push_back(entry.path().string());
                if (matches.size() >= 100) break; // Limit results
            }
        }
    }

    return makeSuccess({
        {"pattern", pattern},
        {"base_path", canonBase},
        {"matches", matches},
        {"count", matches.size()}
    });
}

json FileSystemTools::grepFiles(const json& params) {
    if (!params.contains("pattern")) {
        return makeError("Missing required parameter: pattern");
    }

    std::string pattern = params["pattern"].get<std::string>();
    std::string searchPath = params.value("path", fs::current_path().string());
    std::string glob = params.value("glob", "*");
    std::string canonPath = canonicalizePath(searchPath);

    if (!isPathAllowed(canonPath)) {
        return makeError("Path not allowed: " + searchPath);
    }

    std::regex rx;
    try {
        rx = std::regex(pattern);
    } catch (const std::regex_error& e) {
        return makeError("Invalid regex pattern: " + std::string(e.what()));
    }

    json results = json::array();
    size_t totalMatches = 0;

    auto searchFile = [&](const fs::path& filePath) {
        if (isPatternBlocked(filePath.string())) return;

        std::ifstream file(filePath);
        if (!file.is_open()) return;

        std::string line;
        int lineNum = 0;
        while (std::getline(file, line)) {
            lineNum++;
            if (std::regex_search(line, rx)) {
                results.push_back({
                    {"file", filePath.string()},
                    {"line", lineNum},
                    {"content", line.substr(0, 200)} // Truncate long lines
                });
                totalMatches++;
                if (totalMatches >= 50) return; // Limit results
            }
        }
    };

    if (fs::is_regular_file(canonPath)) {
        searchFile(canonPath);
    } else if (fs::is_directory(canonPath)) {
        for (const auto& entry : fs::recursive_directory_iterator(canonPath)) {
            if (entry.is_regular_file()) {
                // Simple glob check
                std::string filename = entry.path().filename().string();
                bool match = (glob == "*") || (filename.find(glob.substr(1)) != std::string::npos);
                if (match) {
                    searchFile(entry.path());
                    if (totalMatches >= 50) break;
                }
            }
        }
    }

    return makeSuccess({
        {"pattern", pattern},
        {"path", canonPath},
        {"results", results},
        {"total_matches", totalMatches}
    });
}

// ============================================================================
// Security Helpers
// ============================================================================

std::string FileSystemTools::canonicalizePath(const std::string& path) {
    try {
        if (fs::exists(path)) {
            return fs::canonical(path).string();
        }
        // For non-existent paths, normalize as much as possible
        return fs::absolute(path).lexically_normal().string();
    } catch (...) {
        return fs::absolute(path).string();
    }
}

bool FileSystemTools::isPathAllowed(const std::string& path) {
    // If no allowed paths configured, allow all
    if (s_allowedPaths.empty()) {
        return true;
    }

    for (const auto& allowed : s_allowedPaths) {
        std::string canonAllowed = canonicalizePath(allowed);
        if (path.find(canonAllowed) == 0) {
            return true;
        }
    }
    return false;
}

bool FileSystemTools::isPatternBlocked(const std::string& path) {
    fs::path filePath(path);
    std::string filename = filePath.filename().string();

    for (const auto& pattern : s_blockedPatterns) {
        // Simple wildcard matching
        std::string regexPattern = pattern;
        size_t pos = 0;
        while ((pos = regexPattern.find("*", pos)) != std::string::npos) {
            regexPattern.replace(pos, 1, ".*");
            pos += 2;
        }
        try {
            std::regex rx(regexPattern, std::regex::icase);
            if (std::regex_match(filename, rx)) {
                return true;
            }
        } catch (...) {
            // Invalid pattern, skip
        }
    }
    return false;
}

// ============================================================================
// Configuration
// ============================================================================

void FileSystemTools::setAllowedPaths(const std::vector<std::string>& paths) {
    s_allowedPaths = paths;
}

void FileSystemTools::setBlockedPatterns(const std::vector<std::string>& patterns) {
    s_blockedPatterns = patterns;
}

void FileSystemTools::setMaxReadSize(size_t bytes) {
    s_maxReadSize = bytes;
}

void FileSystemTools::setMaxWriteSize(size_t bytes) {
    s_maxWriteSize = bytes;
}

// ============================================================================
// Helpers
// ============================================================================

json FileSystemTools::makeError(const std::string& message) {
    return {
        {"success", false},
        {"error", message}
    };
}

json FileSystemTools::makeSuccess(const json& content) {
    json result = content;
    result["success"] = true;
    return result;
}

} // namespace celuna::tools
