#pragma clang diagnostic push
#pragma ide diagnostic ignored "performance-unnecessary-value-param"
#pragma ide diagnostic ignored "readability-make-member-function-const"
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"

#include "controllers/controllers.h"
#include "dto/dto.h"

const std::string GITLAB_ID = "98bcbad78cb1f880d1d1de62291d70a791251a7bea077bfe7df111ef3c115760";
const std::string GITLAB_SECRET = "7ee01d2b204aff3a05f9d028f004d169b6d381ec873e195f314b3935fa150959";
const std::string GITLAB_URL = "https://gitlab.mattv.de";
const std::string GITLAB_API_URL = "https://ssh.gitlab.mattv.de";

std::string get_redirect_uri(req_type req) {
    auto host_header = req->headers().find("host");
    std::stringstream ss;
    ss << (req->path().starts_with("127") ? (req->isOnSecureConnection() ? "https" : "http") : "https")
       << "://"
       << (host_header != req->headers().end() ? host_header->second : "127.0.0.1:2345")
       << "/api/auth/gitlab_callback";
    return drogon::utils::urlEncode(ss.str());
}

const drogon::HttpClientPtr& get_gitlab_client() {
    static drogon::HttpClientPtr client = drogon::HttpClient::newHttpClient(GITLAB_API_URL, drogon::app().getLoop(), false, false);
    return client;
}


namespace api {
    std::optional<auth::gitlab_tokens> auth::get_gitlab_tokens(req_type req, const std::string& code_or_token, bool token) {
        std::stringstream ss;
        ss << "/oauth/token"
           << "?redirect_uri=" << get_redirect_uri(req)
           << "&client_id=" << GITLAB_ID
           << "&client_secret=" << GITLAB_SECRET
           << (token ? "&refresh_token=" : "&code=") << code_or_token
           << "&grant_type=" << (token ? "refresh_token" : "authorization_code");
        auto gitlab_req = drogon::HttpRequest::newHttpRequest();
        gitlab_req->setPathEncode(false);
        gitlab_req->setPath(ss.str());
        gitlab_req->setMethod(drogon::HttpMethod::Post);
        auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req);
        auto res = res_tuple.second;
        if ((res->statusCode() != drogon::HttpStatusCode::k200OK) && (res->statusCode() != drogon::HttpStatusCode::k201Created))
            return std::nullopt;
        auto json = *res->jsonObject();
        return std::make_optional<gitlab_tokens>(
                json["access_token"].as<std::string>(),
                json["refresh_token"].as<std::string>()
        );
    }

    std::optional<auth::gitlab_user> auth::get_gitlab_user(const std::string& at) {
        auto gitlab_req = drogon::HttpRequest::newHttpRequest();
        gitlab_req->setPath("/api/v4/user");
        gitlab_req->addHeader("Authorization", "Bearer " + at);
        gitlab_req->setMethod(drogon::HttpMethod::Get);
        auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req);
        auto res = res_tuple.second;
        if (res->statusCode() != drogon::HttpStatusCode::k200OK)
            return std::nullopt;
        auto json = *res->jsonObject();
        return std::make_optional<gitlab_user>(
                json["username"].as<std::string>(),
                json.get("is_admin", false).as<bool>()
        );
    }

    void auth::gitlab(req_type req, cbk_type cbk) {
        std::stringstream ss;
        ss << GITLAB_URL << "/oauth/authorize"
           << "?redirect_uri=" << get_redirect_uri(req)
           << "&client_id=" << GITLAB_ID
           << "&scope=read_user&response_type=code";
        cbk(drogon::HttpResponse::newRedirectionResponse(ss.str()));
    }

    void auth::gitlab_callback(req_type req, cbk_type cbk, std::string code) {
        auto tokens =  get_gitlab_tokens(req, code, false);
        if (!tokens.has_value())
            return cbk(dto::Responses::get_unauth_res("Invalid code"));
        auto info =  get_gitlab_user(tokens->at);
        if (!info.has_value())
            return cbk(dto::Responses::get_unauth_res("Invalid code"));

        db::MapperUser user_mapper(drogon::app().getDbClient());
        auto db_users =  user_mapper.findBy(
                db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, info->name) &&
                db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 1)
        );

        if (db_users.empty()) {
            db::User new_user;
            new_user.setName(info->name);
            new_user.setPassword("");
            new_user.setGitlab(1);
            new_user.setRole(info->is_admin ? db::UserRole::ADMIN : db::UserRole::DISABLED);
            new_user.setRootId(0);
            new_user.setTfaType(db::tfaTypes::NONE);

            user_mapper.insert(new_user);
            generate_root(new_user);
            db_users.push_back(new_user);
        }
        db::User& db_user = db_users.at(0);
        db_user.setGitlabAt(tokens->at);
        db_user.setGitlabRt(tokens->rt);
        user_mapper.update(db_user);

        const std::string& token = get_token(db_user);
        cbk(drogon::HttpResponse::newRedirectionResponse("/set_token?token="+token));
    }
}

#pragma clang diagnostic pop