#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),
      pool_(authdb_url, client_name, 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";
    // Admin pattern (matches WebCommander): default=false means the policy is
    // denied by default and granted to members of the allowed group(s). authdb
    // inverts the default for allowed-group members, so default=false + the
    // admin group => members get the right, everyone else does not.
    //
    // NOTE: authdb expects group *IDs* (UUIDs) in gpomembers, not names — so we
    // must resolve the "SuperAdmin" group to its GID before registering.
    std::vector<std::string> default_members;
    try {
        for (const auto& g : client.listGroups()) {
            if (g.name == "SuperAdmin") {
                default_members.push_back(g.gid);
                break;
            }
        }
    } catch (const std::exception& e) {
        std::cerr << "[mediadb] GPO init: failed to resolve SuperAdmin group: " << e.what() << "\n";
    }
    if (default_members.empty())
        std::cerr << "[mediadb] GPO init: WARNING SuperAdmin group not found; policies will have no allowed group\n";

    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, false, 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::ensure_policies() {
    try {
        authdb::client::ClientConnection con;
        con.setUrl(authdb_url_);
        con.setClientName(client_name_);
        con.setClientSecret(client_secret_);

        authdb::client::Client client(con);
        if (!client.ClientAuth()) {
            std::cerr << "[mediadb] GPO init: client authentication failed\n";
            return false;
        }

        try_init_policies(client);
        return true;
    } catch (const authdb::AuthBackendError& e) {
        std::cerr << "[mediadb] GPO init failed: " << e.what() << "\n";
        return false;
    } catch (const std::exception& e) {
        std::cerr << "[mediadb] GPO init failed: " << e.what() << "\n";
        return false;
    } catch (...) {
        std::cerr << "[mediadb] GPO init failed (unknown)\n";
        return false;
    }
}

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);

        auto pooled = pool_.acquire();
        pooled.setSessionID(session_id);

        bool ok = pooled.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);

        auto pooled = pool_.acquire();
        pooled.setSessionID(session_id);

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

} // namespace mediadb
