<?php

/**
 * SSO Tenant Management Functions
 * 
 * Functions for managing OAuth client/tenant configuration
 * 
 * @package SSO
 */

// require_once(dirname(__FILE__) . '/../createprovider.php');
// require_once(dirname(__FILE__) . '/sso-utilities.php');

use League\OAuth2\Client\Provider\Exception\IdentityProviderException;

/**
 * Read app registration displayName from Microsoft Graph (single token use).
 *
 * @param object $provider AzurePKCE from getProvider('azure', ...)
 * @param array $graph From sso_azure_graph_settings()
 * @param \League\OAuth2\Client\Token\AccessTokenInterface $token
 * @param string $client_id Application (client) ID
 * @return string displayName
 * @throws Exception
 */
function aad_graph_get_application_display_name($provider, array $graph, $token, $client_id)
{
    $url = $graph['base'] . '/v1.0/applications(appId=\'' . $client_id . '\')?$select=displayName';

    try {
        $response = $provider->request('GET', $url, $token);
    } catch (IdentityProviderException $e) {
        $detail = trim($e->getMessage());
        $hint = ' Verify the Application (client) ID and Directory (tenant) ID, and that the Azure cloud (Commercial vs US Government) matches where this app is registered.';
        throw new Exception(
            ($detail !== '' ? $detail : 'Microsoft Graph request failed.') . $hint
        );
    }

    if (!is_array($response) || !isset($response['displayName']) || $response['displayName'] === '') {
        throw new Exception(
            'Application registration was not found in Microsoft Graph, or the returned data was invalid.'
        );
    }

    return $response['displayName'];
}

/**
 * Validate Entra ID (Formerly Azure AD) app registration credentials and return API-ready status data. 
 *
 * @param array $tenant Must include client_id, tenant_id, client_secret; optional azure_environment
 * @param bool $log_on_error
 * @param bool $verify_graph If true (default), require a successful Microsoft Graph read of the app registration
 *                          (same check as tenant display-name lookup). Catches wrong Azure cloud vs directory.
 * @return array{status: string, message: string, expired: bool, name?: string}
 */
function validate_aad_client_credentials(array $tenant, $log_on_error = true, $verify_graph = true)
{
    require_once(dirname(__FILE__) . '/../sso-azure-params.php');
    foreach (array('client_id', 'tenant_id', 'client_secret', 'azure_environment') as $field) {
        if (empty($tenant[$field])) {
            $message = 'Missing ' . $field . ' in tenant details.';
            if ($log_on_error && $message !== '') {
                log_sso($message, 'error');
            }
            return array('status' => 'error', 'message' => $message, 'expired' => false);
        }
    }

    try {
        require_once(dirname(__FILE__) . '/../createprovider.php');
        $params = sso_azure_provider_params($tenant);
        $provider = getProvider('azure', $params);
        $graph = sso_azure_graph_settings($tenant);
        $token = $provider->getAccessToken('client_credentials', array('scope' => $graph['scope']));

        if (strlen($token->getToken()) === 0) {
            $message = 'Failed to authenticate with Entra ID.';
            log_sso($message, 'error');
            return array('status' => 'error', 'expired' => false, 'message' => $message);
        }

        $result = array(
            'status' => 'success',
            'expired' => false,
            'message' => 'Successfully authenticated with Entra ID.',
        );
        if ($verify_graph) {
            $cid = isset($tenant['client_id']) ? $tenant['client_id'] : '';
            $result['name'] = aad_graph_get_application_display_name($provider, $graph, $token, $cid);
        }
        return $result;
    } catch (Exception $e) {
        $error_message = $e->getMessage();
        $is_expired = (strpos($error_message, 'AADSTS7000222') !== false)
            || (strpos($error_message, 'expired') !== false && strpos($error_message, 'client secret') !== false);
        if ($log_on_error && $error_message !== '') {
            log_sso($error_message, 'error');
        }
        return array('status' => 'error', 'expired' => $is_expired, 'message' => $error_message);
    }
}

function get_tenants()
{
    try {
        require_once(dirname(__FILE__) . '/../sso-azure-params.php');
        $tenants = get_tenants_credentials_helper();

        foreach ($tenants as $tenant_name => $tenant) {
            $client_secret = isset($tenant['client_secret']) ? $tenant['client_secret'] : '';
            unset($tenants[$tenant_name]['client_secret']);

            $azure_environment = sso_azure_environment($tenant, true);
            if ($azure_environment === null) {
                $tenants[$tenant_name]['credentials_valid'] = false;
                continue;
            }
            $tenant['azure_environment'] = $azure_environment;

            if (!empty($tenant['client_id']) && !empty($tenant['tenant_id']) && !empty($client_secret)) {
                // Token-only: avoids one Microsoft Graph round-trip per tenant on every list refresh.
                // Full Graph verification runs on validate/save.
                $validation = validate_aad_client_credentials($tenant, false, false);
                $tenants[$tenant_name]['credentials_valid'] = ($validation['status'] === 'success');
            } else {
                $tenants[$tenant_name]['credentials_valid'] = false;
            }
        }

        echo json_encode(array('status' => 'success', 'data' => $tenants));
        exit();
    } catch (Exception $e) {
        $msg = 'Failed to get tenants.';
        log_sso($msg . ' ' . $e->getMessage(), 'error');
        echo json_encode(array('status' => 'error', 'message' => $msg));
        exit();
    }
}

function ajax_validate_aad_credentials($client_id = '', $tenant_id = '', $client_secret = '')
{
    require_once(dirname(__FILE__) . '/../sso-azure-params.php');
    $client_id = grab_request_var('client_id', $client_id);
    $tenant_id = grab_request_var('tenant_id', $tenant_id);
    $client_secret = grab_request_var('client_secret', $client_secret);
    $azure_environment_raw = grab_request_var('azure_environment', '');
    $azure_environment = sso_azure_environment($azure_environment_raw, true);

    if ($azure_environment === null) {
        echo json_encode(array(
            'status' => 'error',
            'message' => 'Invalid azure_environment. Use commercial or us_government.'
        ));
        exit();
    }

    $tenant_data = array(
        'client_id' => $client_id,
        'tenant_id' => $tenant_id,
        'client_secret' => $client_secret,
        'azure_environment' => $azure_environment
    );

    $validation = validate_aad_client_credentials($tenant_data);
    echo json_encode($validation);
    exit();
}

function save_tenant()
{
    try {
        require_once(dirname(__FILE__) . '/../sso-azure-params.php');
        $tenant = grab_request_var('tenant', array());
        $tenant = json_decode($tenant, true);
        if (!is_array($tenant)) {
            echo json_encode(array('status' => 'error', 'message' => 'Invalid tenant payload.'));
            exit();
        }

        $client_id = isset($tenant['client_id']) ? $tenant['client_id'] : '';
        if ($client_id === '') {
            echo json_encode(array('status' => 'error', 'message' => 'Missing Application (Client) ID.'));
            exit();
        }

        $current_tenants = get_tenants_credentials_helper();
        $existing = (isset($current_tenants[$client_id]) && is_array($current_tenants[$client_id]))
            ? $current_tenants[$client_id]
            : array();

        $secret_in_request = isset($tenant['client_secret']) && $tenant['client_secret'] !== '';
        $tenant_id_in_request = isset($tenant['tenant_id']) && $tenant['tenant_id'] !== '';
        $secret = $secret_in_request
            ? $tenant['client_secret']
            : (isset($existing['client_secret']) ? $existing['client_secret'] : '');
        $tenant_id = $tenant_id_in_request
            ? $tenant['tenant_id']
            : (isset($existing['tenant_id']) ? $existing['tenant_id'] : '');

        $env_raw = null;
        if (array_key_exists('azure_environment', $tenant)) {
            $env_raw = $tenant['azure_environment'];
        } elseif (isset($existing['azure_environment'])) {
            $env_raw = $existing['azure_environment'];
        }
        $azure_environment = sso_azure_environment($env_raw, true);
        if ($azure_environment === null) {
            echo json_encode(array(
                'status' => 'error',
                'message' => 'Invalid azure_environment. Use commercial or us_government.'
            ));
            exit();
        }

        $tenant_for_validation = array(
            'client_id' => $client_id,
            'tenant_id' => $tenant_id,
            'client_secret' => $secret,
            'azure_environment' => $azure_environment,
        );

        $validation = validate_aad_client_credentials($tenant_for_validation);
        if ($validation['status'] !== 'success') {
            echo json_encode($validation);
            exit();
        }

        // Never replace a stored secret with an empty value (UI often omits secret on partial updates)
        if (isset($tenant['client_secret']) && $tenant['client_secret'] === '') {
            unset($tenant['client_secret']);
        }

        $merged = array_merge($existing, $tenant);
        $merged['client_id'] = $client_id;
        $merged['azure_environment'] = $azure_environment;
        if (!empty($validation['name'])) {
            $merged['name'] = $validation['name'];
        }
        $current_tenants[$client_id] = $merged;

        if (!array_key_exists('enabled', $current_tenants[$client_id])) {
            $current_tenants[$client_id]['enabled'] = 1;
        }
        // remove empty keys that may have arisen from initial setup - if I had more time, I'd figure out why this occasionally happens, but for now, this is a quick fix
        if (array_key_exists('', $current_tenants)) {
            unset($current_tenants['']);
        }

        $tenants_encrypted = encrypt_data(json_encode($current_tenants));

        $file_path = '/usr/local/nagiosxi/etc/components/oauth2/providers/AAD/tenants';
        if (!file_exists(dirname($file_path))) {
            mkdir(dirname($file_path), 0755, true);
        }
        file_put_contents($file_path, $tenants_encrypted);
        echo json_encode(array('status' => 'success', 'message' => 'Successfully saved tenant credentials.'));
        exit();
    } catch (Exception $e) {
        log_sso('Failed to save tenant credentials: ' . $e->getMessage());
        echo json_encode(array('status' => 'error', 'message' => 'Failed to save tenant credentials.'));
        exit();
    }
}

/**
 * Get tenant details from the file -- if client_id is passed, only return the details for that tenant
 */
function get_tenants_credentials_helper($client_id = '')
{
    try {
        if (!file_exists('/usr/local/nagiosxi/etc/components/oauth2/providers/AAD/tenants')) {
            file_put_contents('/usr/local/nagiosxi/etc/components/oauth2/providers/AAD/tenants', encrypt_data(json_encode(array())));
        }

        $tenant_details_encrypted = file_get_contents('/usr/local/nagiosxi/etc/components/oauth2/providers/AAD/tenants');
        $tenant_details = json_decode(decrypt_data($tenant_details_encrypted), true);
        if ($client_id) {
            return $tenant_details[$client_id];
        }
        return $tenant_details;
    } catch (Exception $e) {
        throw new Exception('Failed to get tenant details.');
    }
}

function delete_tenant($client_id)
{
    try {
        $tenants = get_tenants_credentials_helper();
        if (!array_key_exists($client_id, $tenants)) {
            echo json_encode(array('status' => 'error', 'message' => 'Tenant not found.'));
            exit();
        }
        unset($tenants[$client_id]);
        $tenants_encrypted = encrypt_data(json_encode($tenants));
        file_put_contents('/usr/local/nagiosxi/etc/components/oauth2/providers/AAD/tenants', $tenants_encrypted);
        delete_option($client_id . '_sso_enabled');
        remove_user_meta_aad_helper_by_client_id($client_id);
        echo json_encode(array('status' => 'success', 'message' => 'Successfully deleted tenant.'));
        exit();
    } catch (Exception $e) {
        log_sso('Failed to delete tenant: ' . $e->getMessage());
        echo json_encode(array('status' => 'error', 'message' => 'Something went wrong while deleting the tenant.'));
        exit();
    }
}

