#include <stdexcept>
#include <sstream>
#include <set>
#include <iostream>
#include <sqlite3.h>

#include "database.hpp"
#include "utils.hpp"

namespace simple_object_storage {

bool Database::createObjectsTable() {
    const char* create_table_sql = 
        "CREATE TABLE IF NOT EXISTS objects ("
        "hash TEXT PRIMARY KEY,"
        "labeltags TEXT NOT NULL," // JSON array of label:tag pairs
        "metadata TEXT NOT NULL"
        ");";
    
    char* err_msg = nullptr;
    int rc = sqlite3_exec(db_, create_table_sql, nullptr, nullptr, &err_msg);
    if (rc != SQLITE_OK) {
        std::string error = err_msg;
        sqlite3_free(err_msg);
        return false;
    }
    return true;
}

bool Database::createVersionTable() {
    const char* sql = 
        "CREATE TABLE IF NOT EXISTS version_info ("
        "version INTEGER NOT NULL"
        ");";
    
    char* err_msg = nullptr;
    int rc = sqlite3_exec(db_, sql, nullptr, nullptr, &err_msg);
    if (rc != SQLITE_OK) {
        std::string error = err_msg;
        sqlite3_free(err_msg);
        return false;
    }

    // Check if we need to insert initial version
    sqlite3_stmt* stmt;
    sql = "SELECT COUNT(*) FROM version_info;";
    if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    bool needs_initial_version = false;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        needs_initial_version = sqlite3_column_int(stmt, 0) == 0;
    }
    sqlite3_finalize(stmt);

    if (needs_initial_version) {
        sql = "INSERT INTO version_info (version) VALUES (?);";
        if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
            return false;
        }
        sqlite3_bind_int(stmt, 1, CURRENT_VERSION);
        bool success = sqlite3_step(stmt) == SQLITE_DONE;
        sqlite3_finalize(stmt);
        return success;
    }

    return true;
}

bool Database::getVersion(int& version) {
    const char* sql = "SELECT version FROM version_info;";
    sqlite3_stmt* stmt;
    
    if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    if (sqlite3_step(stmt) != SQLITE_ROW) {
        sqlite3_finalize(stmt);
        return false;
    }

    version = sqlite3_column_int(stmt, 0);
    sqlite3_finalize(stmt);
    return true;
}

bool Database::setVersion(int version) {
    const char* sql = "UPDATE version_info SET version = ?;";
    sqlite3_stmt* stmt;
    
    if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    sqlite3_bind_int(stmt, 1, version);
    bool success = sqlite3_step(stmt) == SQLITE_DONE;
    sqlite3_finalize(stmt);
    return success;
}

bool Database::migrate(int from_version, int to_version) {
    if (from_version < 3 && to_version == 3) {
        // Drop old table
        const char* drop_sql = "DROP TABLE IF EXISTS objects;";
        char* err_msg = nullptr;
        int rc = sqlite3_exec(db_, drop_sql, nullptr, nullptr, &err_msg);
        if (rc != SQLITE_OK) {
            std::string error = err_msg;
            sqlite3_free(err_msg);
            return false;
        }

        // Create new table with updated schema
        return createObjectsTable();
    }
    return false;
}

Database::Database(const std::filesystem::path& path) : path_(path) {
    int rc = sqlite3_open(path.string().c_str(), &db_);
    if (rc != SQLITE_OK) {
        throw std::runtime_error("Cannot open database: " + std::string(sqlite3_errmsg(db_)));
    }

    // Create version table and set initial version
    if (!createVersionTable()) {
        throw std::runtime_error("Failed to create version table");
    }

    // Check current version and migrate if needed
    int current_version;
    if (!getVersion(current_version)) {
        throw std::runtime_error("Failed to get database version");
    }

    if (current_version != CURRENT_VERSION) {
        if (!migrate(current_version, CURRENT_VERSION)) {
            throw std::runtime_error("Failed to migrate database");
        }
        if (!setVersion(CURRENT_VERSION)) {
            throw std::runtime_error("Failed to update database version");
        }
    }

    // Create objects table if it doesn't exist
    if (!createObjectsTable()) {
        throw std::runtime_error("Failed to create objects table");
    }
}

Database::~Database() {
    if (db_) {
        sqlite3_close(db_);
    }
}

// bool Database::remove(const std::string& labeltag) {
//     std::string sql = "DELETE FROM objects WHERE labeltag = ?;";
//     sqlite3_stmt* stmt;
    
//     if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
//         return false;
//     }

//     sqlite3_bind_text(stmt, 1, labeltag.c_str(), -1, SQLITE_STATIC);
//     bool success = sqlite3_step(stmt) == SQLITE_DONE;
//     sqlite3_finalize(stmt);
//     return success;
// }

bool Database::remove_by_hash(const std::string& hash) {
    std::string sql = "DELETE FROM objects WHERE hash = ?;";
    sqlite3_stmt* stmt;
    
    if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    sqlite3_bind_text(stmt, 1, hash.c_str(), -1, SQLITE_STATIC);
    bool success = sqlite3_step(stmt) == SQLITE_DONE;
    sqlite3_finalize(stmt);
    return success;
}

bool Database::run_sql_text(const std::string& sql, const std::string& bind_text, dbEntry& entry) {
    sqlite3_stmt* stmt;
    if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    sqlite3_bind_text(stmt, 1, bind_text.c_str(), -1, SQLITE_STATIC);
    
    if (sqlite3_step(stmt) != SQLITE_ROW) {
        sqlite3_finalize(stmt);
        return false;
    }

    entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
    std::string labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
    std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));

    entry.labeltags = nlohmann::json::parse(labeltags_str).get<std::vector<std::string>>();
    entry.metadata = nlohmann::json::parse(metadata_str);

    sqlite3_finalize(stmt);
    return true;
}


bool is_sha256_hash(const std::string& s) {
    // SHA-256 hashes are 64 hex characters
    if (s.length() != 64) return false;
    for (char c : s) {
        if (!std::isxdigit(static_cast<unsigned char>(c))) return false;
    }
    return true;
}

bool Database::get(const std::string& hash_or_labeltag, dbEntry& entry) {
    std::string key=trim(hash_or_labeltag);

    if (hash_or_labeltag.find(':') != std::string::npos) {
        return (run_sql_text("SELECT hash, labeltags, metadata FROM objects WHERE json_array_length(labeltags) > 0 AND EXISTS (SELECT 1 FROM json_each(labeltags) WHERE value = ?);", hash_or_labeltag, entry));
    }

    if (is_sha256_hash(hash_or_labeltag)) {
        if (run_sql_text("SELECT hash, labeltags, metadata FROM objects WHERE hash = ?;", hash_or_labeltag, entry))
            return true;
    }

    // try with a :latest tag
    std::string with_latest = hash_or_labeltag + ":latest";
    return (run_sql_text("SELECT hash, labeltags, metadata FROM objects WHERE json_array_length(labeltags) > 0 AND EXISTS (SELECT 1 FROM json_each(labeltags) WHERE value = ?);", with_latest, entry));
}

bool Database::list(std::vector<dbEntry>& entries) {
    std::string sql = "SELECT hash, labeltags, metadata FROM objects;";
    sqlite3_stmt* stmt;

    if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    entries.clear();
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        dbEntry entry;
        entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
        std::string labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
        std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));

        entry.labeltags = nlohmann::json::parse(labeltags_str).get<std::vector<std::string>>();
        entry.metadata = nlohmann::json::parse(metadata_str);
        entries.push_back(entry);
    }

    sqlite3_finalize(stmt);
    return true;
}

bool Database::list_by_label(const std::string& label, std::vector<dbEntry>& entries) {
    // Find all entries that have at least one labeltag starting with "label:"
    std::string sql = "SELECT hash, labeltags, metadata FROM objects WHERE labeltags LIKE ?;";
    sqlite3_stmt* stmt;

    if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    std::string pattern = "%\"" + label + ":%";
    sqlite3_bind_text(stmt, 1, pattern.c_str(), -1, SQLITE_STATIC);

    entries.clear();
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        dbEntry entry;
        entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
        std::string labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
        std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));

        entry.labeltags = nlohmann::json::parse(labeltags_str).get<std::vector<std::string>>();
        entry.metadata = nlohmann::json::parse(metadata_str);
        entries.push_back(entry);
    }

    sqlite3_finalize(stmt);
    return true;
}

bool Database::merge_existing_entry(const dbEntry& existing, const dbEntry& new_entry, dbEntry& merged) {
    // Merge label:tag pairs
    std::set<std::string> merged_labeltags(existing.labeltags.begin(), existing.labeltags.end());
    merged_labeltags.insert(new_entry.labeltags.begin(), new_entry.labeltags.end());
    
    // Create merged entry
    merged = new_entry; // Start with new entry's data
    merged.labeltags = std::vector<std::string>(merged_labeltags.begin(), merged_labeltags.end());
    
    // Update metadata - preserve fields from existing entry that aren't in new entry
    merged.metadata = existing.metadata; // Start with existing metadata
    for (const auto& [key, value] : new_entry.metadata.items()) {
        merged.metadata[key] = value; // Override with new values
    }
    
    // Ensure required fields are set correctly
    merged.metadata["labeltags"] = merged.labeltags;
    merged.metadata["hash"] = merged.hash;
    
    // Update database
    std::string sql = "UPDATE objects SET labeltags = ?, metadata = ? WHERE hash = ?;";
    sqlite3_stmt* stmt;
    
    if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    std::string labeltags_str = nlohmann::json(merged.labeltags).dump();
    std::string metadata_str = merged.metadata.dump();
    
    sqlite3_bind_text(stmt, 1, labeltags_str.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, metadata_str.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, merged.hash.c_str(), -1, SQLITE_STATIC);

    bool success = sqlite3_step(stmt) == SQLITE_DONE;
    sqlite3_finalize(stmt);
    return success;
}

bool Database::insert_new_entry(const dbEntry& entry) {
    std::string sql = "INSERT INTO objects (hash, labeltags, metadata) VALUES (?, ?, ?);";
    sqlite3_stmt* stmt;
    
    if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
        return false;
    }

    // Update metadata to include labeltags and hash
    nlohmann::json metadata = entry.metadata;
    metadata["labeltags"] = entry.labeltags;
    metadata["hash"] = entry.hash;

    std::string labeltags_str = nlohmann::json(entry.labeltags).dump();
    std::string metadata_str = metadata.dump();
    
    sqlite3_bind_text(stmt, 1, entry.hash.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, labeltags_str.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, metadata_str.c_str(), -1, SQLITE_STATIC);

    bool success = sqlite3_step(stmt) == SQLITE_DONE;
    sqlite3_finalize(stmt);
    return success;
}

bool Database::handle_tag_conflicts(const dbEntry& entry) {
    for (const auto& labeltag : entry.labeltags) {
        // Find all entries with this exact label:tag pair
        std::string find_sql = "SELECT hash, labeltags, metadata FROM objects WHERE labeltags LIKE ?;";
        sqlite3_stmt* stmt;
        if (sqlite3_prepare_v2(db_, find_sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
            return false;
        }

        std::string labeltag_pattern = "%\"" + labeltag + "\"%";
        sqlite3_bind_text(stmt, 1, labeltag_pattern.c_str(), -1, SQLITE_STATIC);

        while (sqlite3_step(stmt) == SQLITE_ROW) {
            std::string other_hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
            if (other_hash == entry.hash) continue; // Skip our own entry

            std::string other_labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
            std::string other_metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));

            // Parse the other entry
            dbEntry other;
            other.hash = other_hash;
            other.labeltags = nlohmann::json::parse(other_labeltags_str).get<std::vector<std::string>>();
            other.metadata = nlohmann::json::parse(other_metadata_str);

            // Remove the exact label:tag pair
            std::vector<std::string> new_labeltags;
            for (const auto& other_labeltag : other.labeltags) {
                if (other_labeltag != labeltag) {
                    new_labeltags.push_back(other_labeltag);
                }
            }

            // Update the other entry if it had the label:tag pair removed
            if (new_labeltags.size() != other.labeltags.size()) {
                other.labeltags = new_labeltags;
                other.metadata["labeltags"] = new_labeltags; // Update metadata to match

                std::string update_sql = "UPDATE objects SET labeltags = ?, metadata = ? WHERE hash = ?;";
                sqlite3_stmt* update_stmt;
                if (sqlite3_prepare_v2(db_, update_sql.c_str(), -1, &update_stmt, nullptr) != SQLITE_OK) {
                    sqlite3_finalize(stmt);
                    return false;
                }

                std::string new_labeltags_str = nlohmann::json(new_labeltags).dump();
                std::string new_metadata_str = other.metadata.dump();
                
                sqlite3_bind_text(update_stmt, 1, new_labeltags_str.c_str(), -1, SQLITE_STATIC);
                sqlite3_bind_text(update_stmt, 2, new_metadata_str.c_str(), -1, SQLITE_STATIC);
                sqlite3_bind_text(update_stmt, 3, other.hash.c_str(), -1, SQLITE_STATIC);

                bool update_success = sqlite3_step(update_stmt) == SQLITE_DONE;
                sqlite3_finalize(update_stmt);
                if (!update_success) {
                    sqlite3_finalize(stmt);
                    return false;
                }
            }
        }
        sqlite3_finalize(stmt);
    }
    return true;
}

bool Database::update_or_insert(const dbEntry& entry) {
    // First try to get existing entry by hash
    dbEntry existing;
    bool exists = get(entry.hash, existing);
    
    if (exists) {
        // Merge with existing entry
        dbEntry merged;
        if (!merge_existing_entry(existing, entry, merged)) {
            return false;
        }
        
        // Handle tag conflicts
        return handle_tag_conflicts(merged);
    } else {
        // Insert new entry
        if (!insert_new_entry(entry)) {
            return false;
        }
        
        // Handle tag conflicts
        return handle_tag_conflicts(entry);
    }
}

} // namespace simple_object_storage
