#include "auth.h"

#include <iostream>
#include <memory>

namespace mediadb {

AuthService::AuthService(const std::string& authdb_url,
                         const std::string& client_name,
                         const std::string& client_secret)
    : authdb_url_(authdb_url),
      client_name_(client_name),
      client_secret_(client_secret) {}

void AuthService::try_init_policies(authdb::client::Client& client, const std::string& domain_key) {
    {
        std::lock_guard<std::mutex> lk(gpo_mutex_);
        if (gpo_initialized_domains_.count(domain_key)) return;
    }

    std::cerr << "[mediadb] GPO init: checking policies for domain '" << (domain_key.empty() ? "default" : domain_key) << "'..." << "\n";
    const std::vector<std::string> default_members = { "Domain Admins" };

    for (const auto& gpo : ALL_GPOS) {
        try {
            uuid::uuid gpoid(gpo.id);
            bool exists = false;
            try {
                exists = client.GPOexits(gpoid);
            } catch (...) {
                exists = false;
            }
            if (!exists) {
                std::cerr << "[mediadb] GPO creating: " << gpo.name << " (" << gpo.id << ")" << "\n";
                std::string result = client.GPOadd(gpoid, gpo.name, gpo.description, true, default_members);
                std::cerr << "[mediadb] GPO created: " << gpo.name << " -> " << result << "\n";
            } else {
                std::cerr << "[mediadb] GPO exists: " << gpo.name << " (" << gpo.id << ")" << "\n";
            }
        } catch (const authdb::AuthBackendError& e) {
            std::cerr << "[mediadb] GPO error for " << gpo.name << ": " << e.what() << "\n";
        } catch (const std::exception& e) {
            std::cerr << "[mediadb] GPO error for " << gpo.name << ": " << e.what() << "\n";
        }
    }
    {
        std::lock_guard<std::mutex> lk(gpo_mutex_);
        gpo_initialized_domains_.insert(domain_key);
    }
    std::cerr << "[mediadb] GPO init complete for domain '" << (domain_key.empty() ? "default" : domain_key) << "' (" << (sizeof(ALL_GPOS)/sizeof(ALL_GPOS[0])) << " policies)" << "\n";
}

bool AuthService::check_cache(const std::string& token) const {
    std::lock_guard<std::mutex> lk(cache_mutex_);
    auto it = auth_cache_.find(token);
    if (it == auth_cache_.end()) return false;
    if (std::chrono::steady_clock::now() > it->second) {
        auth_cache_.erase(it);
        return false;
    }
    return true;
}

void AuthService::cache_token(const std::string& token) const {
    std::lock_guard<std::mutex> lk(cache_mutex_);
    auth_cache_[token] = std::chrono::steady_clock::now()
                       + std::chrono::seconds(CACHE_TTL_SECONDS);
    // Evict expired entries periodically (keep map bounded)
    if (auth_cache_.size() > 1000) {
        auto now = std::chrono::steady_clock::now();
        for (auto ci = auth_cache_.begin(); ci != auth_cache_.end(); ) {
            if (now > ci->second) ci = auth_cache_.erase(ci);
            else ++ci;
        }
    }
}

bool AuthService::is_authorized(const HttpRequest& req) const {
    auto it = req.headers.find("authorization");
    if (it == req.headers.end()) return false;

    const std::string& value = it->second;
    const std::string prefix = "Bearer ";
    if (value.size() <= prefix.size() ||
        value.compare(0, prefix.size(), prefix) != 0)
        return false;

    const std::string token = value.substr(prefix.size());

    // Check cache first — avoid authdb round-trip
    if (check_cache(token)) return true;

    try {
        uuid::uuid session_id(token);

        authdb::client::ClientConnection con;
        con.setUrl(authdb_url_);
        con.setClientName(client_name_);
        con.setClientSecret(client_secret_);
        con.setSessionID(session_id);

        std::unique_ptr<authdb::client::Client> client;
        try {
            client = std::make_unique<authdb::client::Client>(con);
        } catch (...) {
            con.reConnect();
            client = std::make_unique<authdb::client::Client>(con);
        }

        bool ok = client->ClientAuth();
        if (ok) cache_token(token);
        return ok;
    } catch (const authdb::AuthBackendError&) {
        return false;
    } catch (...) {
        return false;
    }
}

bool AuthService::check_gpo(const HttpRequest& req, const char* gpo_id) const {
    auto it = req.headers.find("authorization");
    if (it == req.headers.end()) return false;

    const std::string& value = it->second;
    const std::string prefix = "Bearer ";
    if (value.size() <= prefix.size() ||
        value.compare(0, prefix.size(), prefix) != 0)
        return false;

    const std::string token = value.substr(prefix.size());

    try {
        uuid::uuid session_id(token);
        uuid::uuid gpoid(gpo_id);

        authdb::client::ClientConnection con;
        con.setUrl(authdb_url_);
        con.setClientName(client_name_);
        con.setClientSecret(client_secret_);
        con.setSessionID(session_id);

        std::unique_ptr<authdb::client::Client> client;
        try {
            client = std::make_unique<authdb::client::Client>(con);
        } catch (...) {
            con.reConnect();
            client = std::make_unique<authdb::client::Client>(con);
        }

        if (!client->ClientAuth()) return false;
        return client->GPOcheck(gpoid);
    } catch (...) {
        return false;
    }
}

} // namespace mediadb
