#include "parser.h"
#include <iostream>
#include <regex>
#include <ctime>
#include <iomanip>
#include <functional>

int API::main(int argc, char* argv[]) {
    if (argc < 5) {
        std::cerr << "Usage: plugin_gen <plugins_dir> <bindings_output> <sources_output> <registry_output>" << std::endl;
        return 1;
    }

    std::string plugins_dir = argv[1];
    std::string bindings_output = argv[2];
    std::string sources_output = argv[3];
    std::string registry_output = argv[4];

    auto modules = parse_all_cp_files(plugins_dir);
    if (modules.empty()) {
        std::cerr << "Warning: No modules found in " << plugins_dir << std::endl;
        return 0;
    }

    if (!write_files(modules, bindings_output, sources_output)) {
        std::cerr << "Failed to write output files!" << std::endl;
        return 1;
    }

    std::string registry_json = generate_registry_json(modules, plugins_dir);
    std::ofstream registry_file(registry_output);
    if (!registry_file.is_open()) {
        std::cerr << "ERROR: Cannot open registry file for writing: " << registry_output << std::endl;
        return 1;
    }

    registry_file << registry_json;

    if (registry_file.fail() || registry_file.bad()) {
        std::cerr << "ERROR: Failed to write to registry file: " << registry_output << std::endl;
        registry_file.close();
        return 1;
    }
    registry_file.close();

    std::cout << "Generated bindings for " << modules.size() << " module(s)" << std::endl;
    return 0;
}

std::vector<ModuleDescriptor> API::parse_all_cp_files(const std::string& plugins_dir) {
    std::vector<ModuleDescriptor> modules;

    if (!std::filesystem::exists(plugins_dir)) {
        std::cerr << "Plugins directory does not exist: " << plugins_dir << std::endl;
        return modules;
    }

    for (const auto& entry : std::filesystem::directory_iterator(plugins_dir)) {
        if (entry.path().extension() == ".cp") {
            try {
                modules.push_back(parse_cp_file(entry.path().string()));
                std::cout << "Parsed: " << entry.path().filename() << std::endl;
            } catch (const std::exception& e) {
                std::cerr << "Error parsing " << entry.path() << ": " << e.what() << std::endl;
            }
        }
    }

    return modules;
}

void API::validate_module_name(const std::string& name, int line_num) {
    if (name.empty()) {
        throw std::runtime_error("Line " + std::to_string(line_num) +
                                ": Empty module name");
    }

    if (!std::isalpha(static_cast<unsigned char>(name[0])) && name[0] != '_') {
        throw std::runtime_error("Line " + std::to_string(line_num) +
                                ": Invalid module name '" + name +
                                "' (must start with letter or underscore)");
    }

    for (char c : name) {
        if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
            throw std::runtime_error("Line " + std::to_string(line_num) +
                                    ": Invalid character '" + std::string(1, c) +
                                    "' in module name");
        }
    }
}

bool API::validate_bindings_code(const std::string& code) {
    int brace_count = 0;
    for (char c : code) {
        if (c == '{') brace_count++;
        if (c == '}') brace_count--;
        if (brace_count < 0) {
            std::cerr << "ERROR: Unbalanced braces (too many closing braces)" << std::endl;
            return false;
        }
    }

    if (brace_count != 0) {
        std::cerr << "ERROR: Unbalanced braces (missing " << brace_count << " closing braces)" << std::endl;
        return false;
    }

    if (code.find("#include <pybind11/pybind11.h>") == std::string::npos) {
        std::cerr << "ERROR: Missing required include: pybind11/pybind11.h" << std::endl;
        return false;
    }

    if (code.find("PYBIND11_MODULE(api, m)") == std::string::npos) {
        std::cerr << "ERROR: Missing PYBIND11_MODULE(api, m) definition" << std::endl;
        return false;
    }

    if (code.find(";;") != std::string::npos) {
        std::cerr << "WARNING: Double semicolon detected (possible syntax error)" << std::endl;
    }

    return true;
}

ModuleDescriptor API::parse_cp_file(const std::string& filepath) {
    ModuleDescriptor desc;
    desc.has_header = false;
    desc.expose_all = false;

    std::ifstream file(filepath);
    if (!file.is_open()) {
        throw std::runtime_error("Cannot open file: " + filepath);
    }

    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();

    auto lines = split(content, '\n');

    bool in_public_block = false;
    std::string public_content;
    int line_num = 0;

    for (const auto& line_raw : lines) {
        line_num++;
        std::string line = trim(line_raw);
        if (line.empty() || line[0] == '#') continue;

#ifdef VSRAM_DEBUG
        std::cout << "DEBUG: Processing line " << line_num << ": " << line << std::endl;
#endif

        if (!in_public_block) {
            if (starts_with(line, "SOURCE")) {
                size_t source_start = line.find('(');
                size_t source_end = line.find(')');
                if (source_start != std::string::npos && source_end != std::string::npos) {
                    desc.source_path = trim(line.substr(source_start + 1, source_end - source_start - 1));
                }

                size_t header_pos = line.find("HEADER");
                if (header_pos != std::string::npos) {
                    desc.has_header = true;
                    size_t header_start = line.find('(', header_pos);
                    size_t header_end = line.find(')', header_pos);
                    if (header_start != std::string::npos && header_end != std::string::npos) {
                        desc.header_path = trim(line.substr(header_start + 1, header_end - header_start - 1));
                    }
                }

                size_t last_space = line.rfind(' ');
                if (last_space != std::string::npos) {
                    desc.module_name = trim(line.substr(last_space + 1));
                    validate_module_name(desc.module_name, line_num);
                }
            }
            else if (starts_with(line, "PUBLIC")) {
                in_public_block = true;
                size_t paren = line.find('(');
                if (paren != std::string::npos) {
                    public_content += line.substr(paren + 1);
                }
            }
        }
        else {
            public_content += "\n" + line;
        }
    }

    size_t last_paren = public_content.rfind(')');
    if (last_paren != std::string::npos) {
        public_content = public_content.substr(0, last_paren);
    }

    public_content = trim(public_content);

#ifdef VSRAM_DEBUG
    std::cout << "DEBUG: public_content = [" << public_content << "]" << std::endl;
#endif

    if (trim(public_content) == "ALL") {
        desc.expose_all = true;
    }
    else {
        auto public_lines = split(public_content, '\n');
#ifdef VSRAM_DEBUG
        std::cout << "DEBUG: Found " << public_lines.size() << " lines in PUBLIC block" << std::endl;
#endif

        for (size_t i = 0; i < public_lines.size(); ++i) {
            std::string cleaned = trim(public_lines[i]);
            if (cleaned.empty()) continue;

            size_t and_pos = cleaned.find("&&");
            if (and_pos != std::string::npos) {
                cleaned = trim(cleaned.substr(0, and_pos));
            }

            if (cleaned.find("FUNC") != std::string::npos) {
                FunctionBinding fb;
                auto parts = split(cleaned, ' ');
                if (parts.size() >= 2) {
                    fb.module_name = parts[0];

                    size_t func_start = cleaned.find('(');
                    size_t func_end = cleaned.find(')');
                    fb.function_name = safe_extract_between(cleaned, func_start, func_end, "FUNC");
                    desc.functions.push_back(fb);
                }
            }
            else if (cleaned.find("CLASS") != std::string::npos) {
                ClassBinding cb;
                cb.auto_bind_all = false;
                auto parts = split(cleaned, ' ');
                if (parts.size() >= 2) {
                    cb.module_name = parts[0];

                    size_t class_start = cleaned.find('(');
                    size_t class_end = cleaned.find(')');
                    cb.class_name = safe_extract_between(cleaned, class_start, class_end, "CLASS");

                    // Check for METHOD/FIELD block: CLASS(Name) { ... }
                    size_t brace_open = cleaned.find('{');
                    if (brace_open != std::string::npos) {
                        // Collect all lines until closing brace
                        std::string method_block;
                        bool found_close = false;

                        // Check if closing brace on same line
                        size_t brace_close = cleaned.find('}', brace_open);
                        if (brace_close != std::string::npos) {
                            method_block = cleaned.substr(brace_open + 1, brace_close - brace_open - 1);
                            found_close = true;
                        } else {
                            // Collect from next lines
                            for (size_t j = i + 1; j < public_lines.size(); ++j) {
                                std::string next_line = trim(public_lines[j]);
                                size_t close_pos = next_line.find('}');

                                if (close_pos != std::string::npos) {
                                    method_block += next_line.substr(0, close_pos);
                                    i = j;  // Skip these lines in main loop
                                    found_close = true;
                                    break;
                                } else {
                                    method_block += next_line + "\n";
                                }
                            }
                        }

                        // Parse METHOD(...) and FIELD(...) entries
                        auto method_lines = split(method_block, '\n');
                        for (const auto& mline : method_lines) {
                            std::string mtrim = trim(mline);
                            if (mtrim.empty()) continue;

                            if (mtrim.find("METHOD") != std::string::npos) {
                                size_t m_start = mtrim.find('(');
                                size_t m_end = mtrim.find(')');
                                if (m_start != std::string::npos && m_end != std::string::npos) {
                                    std::string method_name = safe_extract_between(mtrim, m_start, m_end, "METHOD");
                                    cb.methods.push_back(method_name);
                                }
                            }
                            else if (mtrim.find("FIELD") != std::string::npos) {
                                size_t f_start = mtrim.find('(');
                                size_t f_end = mtrim.find(')');
                                if (f_start != std::string::npos && f_end != std::string::npos) {
                                    std::string field_name = safe_extract_between(mtrim, f_start, f_end, "FIELD");
                                    cb.fields.push_back(field_name);
                                }
                            }
                        }
                    }

                    desc.classes.push_back(cb);
                }
            }
            else if (cleaned.find("VAR") != std::string::npos) {
                VariableBinding vb;
                auto parts = split(cleaned, ' ');
                if (parts.size() >= 2) {
                    vb.module_name = parts[0];

                    size_t var_start = cleaned.find('(');
                    size_t var_end = cleaned.find(')');
                    vb.variable_name = safe_extract_between(cleaned, var_start, var_end, "VAR");
                    desc.variables.push_back(vb);
                }
            }
        }
    }

    return desc;
}

std::string API::generate_class_bindings(const ClassBinding& cls, const ModuleDescriptor& mod) {
    std::ostringstream code;

    code << "    py::class_<" << cls.class_name << ">("
         << mod.module_name << "_module, \"" << cls.class_name << "\")\n";
    code << "        .def(py::init<>())";

    // Bind methods
    for (const auto& method : cls.methods) {
        code << "\n        .def(\"" << method << "\", &"
             << cls.class_name << "::" << method << ")";
    }

    // Bind fields (read-write access)
    for (const auto& field : cls.fields) {
        code << "\n        .def_readwrite(\"" << field
             << "\", &" << cls.class_name << "::" << field << ")";
    }

    code << ";\n\n";
    return code.str();
}

std::string API::generate_pybind11_code(const std::vector<ModuleDescriptor>& modules) {
    std::ostringstream code;

    code << "#include <pybind11/pybind11.h>\n";
    code << "#include <pybind11/stl.h>\n";
    code << "#include <pybind11/operators.h>\n";
    code << "#include <pybind11/functional.h>\n\n";

    for (const auto& mod : modules) {
        if (mod.has_header) {
            std::string header_path = replace_all(mod.header_path, "\\", "/");
            code << "#include \"" << header_path << "\"\n";
        }
    }

    code << "\nusing namespace includecpp;\n";
    code << "namespace py = pybind11;\n\n";
    code << "PYBIND11_MODULE(api, m) {\n";
    code << "    m.doc() = \"Auto-generated C++ API bindings\";\n\n";

    for (const auto& mod : modules) {
        code << "    py::module_ " << mod.module_name << "_module = ";
        code << "m.def_submodule(\"" << mod.module_name << "\", \"";
        code << mod.module_name << " module\");\n\n";

        for (const auto& cls : mod.classes) {
            code << generate_class_bindings(cls, mod);
        }

        for (const auto& func : mod.functions) {
            code << "    " << mod.module_name << "_module.def(\"";
            code << func.function_name << "\", &" << func.function_name << ");\n";
        }

        for (const auto& var : mod.variables) {
            code << "    " << mod.module_name << "_module.attr(\"";
            code << var.variable_name << "\") = " << var.variable_name << ";\n";
        }

        code << "\n";
    }

    code << "}\n";

    return code.str();
}

bool API::write_files(const std::vector<ModuleDescriptor>& modules,
                     const std::string& bindings_path,
                     const std::string& sources_path) {

    std::string bindings_code = generate_pybind11_code(modules);

    // Validate generated code
    if (!validate_bindings_code(bindings_code)) {
        std::cerr << "ERROR: Generated bindings code validation failed!" << std::endl;
        return false;
    }

    std::ofstream bindings_file(bindings_path);
    if (!bindings_file.is_open()) {
        std::cerr << "ERROR: Cannot open file for writing: " << bindings_path << std::endl;
        return false;
    }

    bindings_file << bindings_code;

    if (bindings_file.fail() || bindings_file.bad()) {
        std::cerr << "ERROR: Failed to write to file: " << bindings_path << std::endl;
        bindings_file.close();
        return false;
    }
    bindings_file.close();

    std::ofstream sources_file(sources_path);
    if (!sources_file.is_open()) {
        std::cerr << "ERROR: Cannot open file for writing: " << sources_path << std::endl;
        return false;
    }

    for (const auto& mod : modules) {
        std::string source_path = replace_all(mod.source_path, "\\", "/");
        sources_file << source_path << "\n";
    }

    if (sources_file.fail() || sources_file.bad()) {
        std::cerr << "ERROR: Failed to write to file: " << sources_path << std::endl;
        sources_file.close();
        return false;
    }
    sources_file.close();

    return true;
}

std::string API::compute_file_hash(const std::string& filepath) {
    if (!std::filesystem::exists(filepath)) {
        return "0";
    }

    std::ifstream file(filepath, std::ios::binary);
    if (!file.is_open()) {
        return "0";
    }

    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();

    std::hash<std::string> hasher;
    size_t hash_value = hasher(content);

    std::ostringstream hash_str;
    hash_str << std::hex << hash_value;
    return hash_str.str();
}

std::string API::generate_registry_json(const std::vector<ModuleDescriptor>& modules, const std::string& plugins_dir) {
    std::ostringstream json;
    json << "{\n";

    for (size_t i = 0; i < modules.size(); ++i) {
        const auto& mod = modules[i];

        json << "  \"" << mod.module_name << "\": {\n";
        json << "    \"sources\": [";

        std::string source_path = replace_all(mod.source_path, "\\", "/");
        json << "\"" << source_path << "\"";

        json << "],\n";

        if (mod.has_header) {
            std::string header_path = replace_all(mod.header_path, "\\", "/");
            json << "    \"header\": \"" << header_path << "\",\n";
        } else {
            json << "    \"header\": null,\n";
        }

        std::filesystem::path cp_path = std::filesystem::path(plugins_dir) / (mod.module_name + ".cp");
        std::string cp_file = cp_path.string();
        cp_file = replace_all(cp_file, "\\", "/");
        json << "    \"cp_file\": \"" << cp_file << "\",\n";

        json << "    \"hashes\": {\n";
        json << "      \"" << mod.module_name << ".cpp\": \"" << compute_file_hash(mod.source_path) << "\"";

        if (mod.has_header) {
            json << ",\n      \"" << mod.module_name << ".h\": \"" << compute_file_hash(mod.header_path) << "\"";
        }

        json << ",\n      \"" << mod.module_name << ".cp\": \"" << compute_file_hash(cp_file) << "\"";
        json << "\n    },\n";

        auto t = std::time(nullptr);
        std::tm tm_buffer;
#ifdef _WIN32
        localtime_s(&tm_buffer, &t);
#else
        localtime_r(&t, &tm_buffer);
#endif
        std::ostringstream timestamp;
        timestamp << std::put_time(&tm_buffer, "%Y-%m-%dT%H:%M:%S");
        json << "    \"last_built\": \"" << timestamp.str() << "\"\n";

        json << "  }";
        if (i < modules.size() - 1) {
            json << ",";
        }
        json << "\n";
    }

    json << "}\n";
    return json.str();
}

std::string API::trim(const std::string& str) {
    size_t start = 0;
    while (start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) start++;

    size_t end = str.length();
    while (end > start && std::isspace(static_cast<unsigned char>(str[end - 1]))) end--;

    return str.substr(start, end - start);
}

std::vector<std::string> API::split(const std::string& str, char delimiter) {
    std::vector<std::string> tokens;
    std::stringstream ss(str);
    std::string token;

    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(token);
    }

    return tokens;
}

std::string API::extract_between(const std::string& str, char open, char close) {
    size_t start = str.find(open);
    size_t end = str.find(close, start);

    if (start == std::string::npos || end == std::string::npos) {
        return "";
    }

    return str.substr(start + 1, end - start - 1);
}

std::string API::safe_extract_between(const std::string& str,
                                      size_t start_pos,
                                      size_t end_pos,
                                      const std::string& context) {
    if (start_pos == std::string::npos || end_pos == std::string::npos) {
        throw std::runtime_error("Parse error: missing parenthesis in " + context);
    }
    if (start_pos >= end_pos) {
        throw std::runtime_error("Parse error: invalid syntax in " + context +
                                " (closing parenthesis before opening)");
    }
    if (end_pos > str.length()) {
        throw std::runtime_error("Parse error: position out of bounds in " + context);
    }
    return trim(str.substr(start_pos + 1, end_pos - start_pos - 1));
}

bool API::starts_with(const std::string& str, const std::string& prefix) {
    return str.size() >= prefix.size() &&
           str.compare(0, prefix.size(), prefix) == 0;
}

std::string API::normalize_path(const std::string& path) {
    std::string p = trim(path);
    std::replace(p.begin(), p.end(), '/', '\\');
    return p;
}

std::string API::replace_all(std::string str, const std::string& from, const std::string& to) {
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length();
    }
    return str;
}

int main(int argc, char* argv[]) {
    return API::main(argc, argv);
}
