#pragma once
#include <SQLiteCpp/Database.h>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <exception>
#include <filesystem>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#include <concurrencpp/concurrencpp.h>
#include "Core/Base/JSON.h"


class Global;

struct External_Database_Row {
    std::map<std::string, std::optional<std::string>> columns;
    External_Database_Row() = default;
    External_Database_Row(const External_Database_Row&) = default;
    External_Database_Row& operator=(const External_Database_Row&) = default;
    External_Database_Row(External_Database_Row&&) noexcept = default;
    External_Database_Row& operator=(External_Database_Row&&) noexcept = default;

    [[nodiscard]] std::optional<std::string> get(const std::string& column) const;
    [[nodiscard]] Psc::JSON to_json() const;
};

using External_Database_Row_Map = std::map<std::string, std::shared_ptr<External_Database_Row>>;
struct External_Resource_Status {
    std::string name;
    std::size_t row_count{};
    bool downloaded{};
    bool imported{};
    std::string message;
};

class External_Resources {
public:
    External_Resources(std::string name,
                       std::string cache_file_name,
                       std::string download_url,
                       std::string description,
                       std::uint64_t update_interval_seconds);
    virtual ~External_Resources() = default;



    [[nodiscard]] std::size_t row_count(SQLite::Database& db) const;

    virtual void create_table(SQLite::Database& db) const = 0;
    External_Resource_Status update(SQLite::Database& db,
                                    const std::filesystem::path& cache_pos,
                                    bool force_download,
                                    bool only_when_empty);
    External_Resource_Status import_offline(SQLite::Database& db,
                                             const std::filesystem::path& source_file);
    External_Resource_Status clear_table(SQLite::Database& db,
                                         const std::filesystem::path& cache_pos);
    [[nodiscard]] std::optional<External_Database_Row> query_one(
        SQLite::Database& db,
        const std::vector<std::string>& primary_key_values) const;
    void init(const Psc::JSON* that_json)
    {
        Get_J(name)
        Get_J(download_url)
        Get_J(description)
        Get_J(update_interval_seconds)
        Get_J(last_updated_at)
    }
    [[nodiscard]] Psc::JSON to_json() const
    {
        Psc::JSON ret = Psc::JSON::object();
        Ret_J(name)
        Ret_J(download_url)
        Ret_J(description)
        Ret_J(update_interval_seconds)
        Ret_J(last_updated_at)
        return ret;
    }
    std::string name;
    std::string cache_file_name;
    std::string download_url;
    std::string description;
    std::uint64_t update_interval_seconds{};
    std::time_t last_updated_at{};
protected:
    [[nodiscard]] virtual std::filesystem::path source_file(
        const std::filesystem::path& cache_pos) const;
    virtual bool fetch(const std::filesystem::path& cache_pos,
                       bool force_download) const;
    virtual std::size_t import_file(SQLite::Database& db,
                                    const std::filesystem::path& source_file) const = 0;
    [[nodiscard]] virtual const std::vector<std::string>& primary_key_columns() const = 0;
    void mark_updated();
    void clear_last_updated_at();


};

class External_Resources_Manager {
public:
    using Status_Callback = std::function<void(std::exception_ptr, External_Resource_Status)>;
    using Status_List_Callback = std::function<void(std::exception_ptr, std::vector<External_Resource_Status>)>;
    using Row_Callback = std::function<void(std::exception_ptr, std::optional<External_Database_Row>)>;
    using Row_Map_Callback = std::function<void(
        std::exception_ptr,
        External_Database_Row_Map)>;

    External_Resources_Manager();

    void init(const Psc::JSON* that_json);
    [[nodiscard]] Psc::JSON to_json() const;
    void server(Global* g);
    void load_sqlite_db(std::string path);
    std::vector<External_Resource_Status> refresh_external_databases(bool force_download = true);
    External_Resource_Status refresh_external_database(const std::string& resource_name,
                                                        bool force_download = true);
    External_Resource_Status import_external_database(
        const std::string& resource_name,
        const std::filesystem::path& source_file);
    External_Resource_Status clear_external_database_table(const std::string& resource_name);
    [[nodiscard]] std::optional<External_Database_Row> query_external_database(
        const std::string& resource_name,
        const std::vector<std::string>& primary_key_values) const;
    [[nodiscard]] std::map<std::string, std::shared_ptr<External_Database_Row>>
    query_aircraft_external_databases(const std::string& icao24) const;
    [[nodiscard]] std::map<std::string, std::shared_ptr<External_Database_Row>>
    query_callsign_external_databases(
        const std::optional<std::string>& callsign) const;
    [[nodiscard]] std::vector<External_Resource_Status> external_database_status() const;

    void async_refresh_external_databases(bool force_download,
                                          Status_List_Callback callback);
    void async_refresh_external_database(std::string resource_name,
                                         bool force_download,
                                         Status_Callback callback);
    void async_import_external_database(std::string resource_name,
                                        std::filesystem::path source_file,
                                        Status_Callback callback);
    void async_clear_external_database_table(std::string resource_name,
                                             Status_Callback callback);
    void async_query_external_database(std::string resource_name,
                                       std::vector<std::string> primary_key_values,
                                       Row_Callback callback) const;
    void async_query_aircraft_external_databases(std::string icao24,
                                                Row_Map_Callback callback) const;
    void async_query_callsign_external_databases(std::optional<std::string> callsign,
                                                Row_Map_Callback callback) const;
    void async_external_database_status(Status_List_Callback callback) const;

    [[nodiscard]] concurrencpp::result<std::vector<External_Resource_Status>>
    refresh_external_databases_coro(bool force_download = true);
    [[nodiscard]] concurrencpp::result<External_Resource_Status>
    refresh_external_database_coro(std::string resource_name,
                                   bool force_download = true);
    [[nodiscard]] concurrencpp::result<External_Resource_Status>
    import_external_database_coro(std::string resource_name,
                                  std::filesystem::path source_file);
    [[nodiscard]] concurrencpp::result<External_Resource_Status>
    clear_external_database_table_coro(std::string resource_name);
    [[nodiscard]] concurrencpp::result<std::optional<External_Database_Row>>
    query_external_database_coro(std::string resource_name,
                                 std::vector<std::string> primary_key_values) const;
    [[nodiscard]] concurrencpp::result<std::shared_ptr<External_Database_Row_Map>>
    query_aircraft_external_databases_coro(std::string icao24) const;
    [[nodiscard]] concurrencpp::result<std::shared_ptr<External_Database_Row_Map>>
    query_callsign_external_databases_coro(std::optional<std::string> callsign) const;
    [[nodiscard]] concurrencpp::result<std::vector<External_Resource_Status>>
    external_database_status_coro() const;

    void register_external_resource(std::unique_ptr<External_Resources> external_res);
    void initialize(SQLite::Database& db, std::filesystem::path cache_pos);
    std::vector<External_Resource_Status> sync_missing(SQLite::Database& db);
    std::vector<External_Resource_Status> refresh_all(SQLite::Database& db,
                                                       bool force_download = true);
    External_Resource_Status refresh_one(SQLite::Database& db,
                                          const std::string& resource_name,
                                          bool force_download = true);
    External_Resource_Status import_offline(SQLite::Database& db,
                                              const std::string& resource_name,
                                              const std::filesystem::path& source_file);
    External_Resource_Status clear_table(SQLite::Database& db,
                                          const std::string& resource_name);
    [[nodiscard]] std::optional<External_Database_Row> query_one(
        SQLite::Database& db,
        const std::string& resource_name,
        const std::vector<std::string>& primary_key_values) const;
    [[nodiscard]] std::map<std::string, std::shared_ptr<External_Database_Row>>
    query_aircraft(SQLite::Database& db, const std::string& icao24) const;
    [[nodiscard]] std::map<std::string, std::shared_ptr<External_Database_Row>>
    query_callsign(SQLite::Database& db,
                   const std::optional<std::string>& callsign) const;
    [[nodiscard]] std::vector<External_Resource_Status> status(SQLite::Database& db) const;
    std::string  ecap_sqlite_path;

    std::filesystem::path cache_pos_;
private:
    [[nodiscard]] SQLite::Database& require_db() const;
    External_Resources& get_resource(const std::string& resource_name) const;
    std::vector<External_Resource_Status> update_all(SQLite::Database& db,
                                                     bool force_download,
                                                     bool only_when_empty);
    std::unique_ptr<SQLite::Database> db_;
    std::vector<std::unique_ptr<External_Resources>> resources_;
    mutable std::mutex mtx_;
};
