#include "pam_auth.h"

#ifdef ENABLE_PAM_AUTH
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <pwd.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#endif

PamAuth::PamAuth(const std::string& serviceName)
    : m_serviceName(serviceName)
    , m_initialized(false)
{
}

PamAuth::~PamAuth() {
}

bool PamAuth::initialize() {
#ifdef ENABLE_PAM_AUTH
    m_initialized = true;
    return true;
#else
    m_initialized = false;
    return false;
#endif
}

PamAuthResult PamAuth::authenticate(const std::string& username, const std::string& password) {
    PamAuthResult result;
    result.success = false;

    if (!m_initialized) {
        result.errorMessage = "PAM authentication not initialized";
        result.errorCode = "PAM_NOT_INITIALIZED";
        return result;
    }

#ifdef ENABLE_PAM_AUTH
    return authenticateInternal(username, password);
#else
    result.errorMessage = "PAM authentication not available (compiled without PAM support)";
    result.errorCode = "PAM_NOT_AVAILABLE";
    return result;
#endif
}

bool PamAuth::isAvailable() const {
    return m_initialized;
}

std::string PamAuth::getServiceName() const {
    return m_serviceName;
}

void PamAuth::setServiceName(const std::string& serviceName) {
    m_serviceName = serviceName;
}

#ifdef ENABLE_PAM_AUTH
// Structure to hold authentication data for PAM conversation
struct PamAuthData {
    std::string username;
    std::string password;
    PamAuthResult* result;
};

int PamAuth::conversationFunction(int num_msg, const struct pam_message** msg,
                                  struct pam_response** resp, void* appdata_ptr) {
    if (num_msg <= 0 || msg == NULL || resp == NULL || appdata_ptr == NULL) {
        return PAM_CONV_ERR;
    }

    PamAuthData* authData = static_cast<PamAuthData*>(appdata_ptr);

    // Allocate response array
    struct pam_response* response = static_cast<struct pam_response*>(
        calloc(num_msg, sizeof(struct pam_response)));
    if (response == NULL) {
        return PAM_CONV_ERR;
    }

    for (int i = 0; i < num_msg; i++) {
        const struct pam_message* message = msg[i];

        switch (message->msg_style) {
            case PAM_PROMPT_ECHO_OFF:
                // Password prompt - return the password
                response[i].resp = strdup(authData->password.c_str());
                response[i].resp_retcode = 0;
                break;

            case PAM_PROMPT_ECHO_ON:
                // Username prompt - return the username
                response[i].resp = strdup(authData->username.c_str());
                response[i].resp_retcode = 0;
                break;

            case PAM_ERROR_MSG:
                // Error message - log it
                if (authData->result) {
                    authData->result->errorMessage = message->msg;
                }
                response[i].resp = NULL;
                response[i].resp_retcode = 0;
                break;

            case PAM_TEXT_INFO:
                // Information message - ignore
                response[i].resp = NULL;
                response[i].resp_retcode = 0;
                break;

            default:
                // Unknown message type
                for (int j = 0; j <= i; j++) {
                    if (response[j].resp != NULL) {
                        free(response[j].resp);
                    }
                }
                free(response);
                return PAM_CONV_ERR;
        }
    }

    *resp = response;
    return PAM_SUCCESS;
}

PamAuthResult PamAuth::authenticateInternal(const std::string& username, const std::string& password) {
    PamAuthResult result;
    result.success = false;
    result.username = username;

    // Prepare authentication data
    PamAuthData authData;
    authData.username = username;
    authData.password = password;
    authData.result = &result;

    // PAM conversation structure
    struct pam_conv pam_conv;
    pam_conv.conv = conversationFunction;
    pam_conv.appdata_ptr = &authData;

    pam_handle_t* pamh = NULL;
    int pam_status;

    // Start PAM transaction
    pam_status = pam_start(m_serviceName.c_str(), username.c_str(), &pam_conv, &pamh);
    if (pam_status != PAM_SUCCESS) {
        result.errorMessage = pamErrorToString(pam_status);
        result.errorCode = pamErrorToErrorCode(pam_status);
        return result;
    }

    // Authenticate user
    pam_status = pam_authenticate(pamh, PAM_SILENT);
    if (pam_status != PAM_SUCCESS) {
        result.errorMessage = pamErrorToString(pam_status);
        result.errorCode = pamErrorToErrorCode(pam_status);
        pam_end(pamh, pam_status);
        return result;
    }

    // Check account validity
    pam_status = pam_acct_mgmt(pamh, PAM_SILENT);
    if (pam_status != PAM_SUCCESS) {
        result.errorMessage = pamErrorToString(pam_status);
        result.errorCode = pamErrorToErrorCode(pam_status);
        pam_end(pamh, pam_status);
        return result;
    }

    // Get user information
    struct passwd* pw = getpwnam(username.c_str());
    if (pw != NULL) {
        // Use system UID as user ID
        result.userId = std::to_string(pw->pw_uid);
    } else {
        // Fallback: use username as ID
        result.userId = username;
    }

    result.success = true;
    result.errorMessage = "";
    result.errorCode = "";

    // End PAM transaction
    pam_end(pamh, PAM_SUCCESS);

    return result;
}

std::string PamAuth::pamErrorToString(int pamError) {
    switch (pamError) {
        case PAM_SUCCESS:
            return "Success";
        case PAM_ABORT:
            return "General failure";
        case PAM_AUTH_ERR:
            return "Authentication failure";
        case PAM_CRED_INSUFFICIENT:
            return "Insufficient credentials";
        case PAM_AUTHINFO_UNAVAIL:
            return "Authentication information unavailable";
        case PAM_USER_UNKNOWN:
            return "Unknown user";
        case PAM_MAXTRIES:
            return "Maximum number of tries exceeded";
        case PAM_NEW_AUTHTOK_REQD:
            return "New authentication token required";
        case PAM_ACCT_EXPIRED:
            return "User account has expired";
        case PAM_SESSION_ERR:
            return "Session error";
        case PAM_CRED_ERR:
            return "Credential error";
        case PAM_CRED_UNAVAIL:
            return "Credential unavailable";
        case PAM_CRED_EXPIRED:
            return "Credential expired";
        case PAM_NO_MODULE_DATA:
            return "No module data available";
        case PAM_CONV_ERR:
            return "Conversation error";
        case PAM_AUTHTOK_ERR:
            return "Authentication token error";
        case PAM_AUTHTOK_RECOVERY_ERR:
            return "Authentication token recovery error";
        case PAM_AUTHTOK_LOCK_BUSY:
            return "Authentication token lock busy";
        case PAM_AUTHTOK_DISABLE_AGING:
            return "Authentication token aging disabled";
        case PAM_TRY_AGAIN:
            return "Try again";
        case PAM_IGNORE:
            return "Ignore";
        case PAM_MODULE_UNKNOWN:
            return "Unknown module";
        case PAM_BAD_ITEM:
            return "Bad item";
        case PAM_CONV_AGAIN:
            return "Conversation again";
        case PAM_INCOMPLETE:
            return "Incomplete";
        default:
            return "Unknown PAM error";
    }
}

std::string PamAuth::pamErrorToErrorCode(int pamError) {
    switch (pamError) {
        case PAM_SUCCESS:
            return "SUCCESS";
        case PAM_AUTH_ERR:
        case PAM_CRED_INSUFFICIENT:
            return "AUTHENTICATION_FAILED";
        case PAM_USER_UNKNOWN:
            return "USER_NOT_FOUND";
        case PAM_CRED_EXPIRED:
            return "CREDENTIAL_EXPIRED";
        case PAM_ACCT_EXPIRED:
            return "ACCOUNT_EXPIRED";
        case PAM_NEW_AUTHTOK_REQD:
            return "PASSWORD_CHANGE_REQUIRED";
        case PAM_MAXTRIES:
            return "MAX_TRIES_EXCEEDED";
        case PAM_AUTHINFO_UNAVAIL:
        case PAM_CRED_UNAVAIL:
            return "AUTHENTICATION_UNAVAILABLE";
        default:
            return "PAM_ERROR";
    }
}

#else
// Stub implementations when PAM is not enabled
PamAuthResult PamAuth::authenticateInternal(const std::string& username, const std::string& password) {
    PamAuthResult result;
    result.success = false;
    result.errorMessage = "PAM authentication not available (compiled without PAM support)";
    result.errorCode = "PAM_NOT_AVAILABLE";
    return result;
}

std::string PamAuth::pamErrorToString(int pamError) {
    return "PAM not available";
}

std::string PamAuth::pamErrorToErrorCode(int pamError) {
    return "PAM_NOT_AVAILABLE";
}
#endif