<?php

/**
 * SSO User Provisioning Functions
 * 
 * Functions for creating and updating XI users from AAD data
 * 
 * @package SSO
 */

// require_once(dirname(__FILE__) . '/sso-utilities.php');
// require_once(dirname(__FILE__) . '/sso-storage.php');
// require_once(dirname(__FILE__) . '/sso-tenant-manager.php');
// require_once(dirname(__FILE__) . '/sso-aad-client.php');

/**
 * Process naming template with placeholders
 * Supports: <first_name>, <last_name>, <tenant_name>, <group_name>, <email>
 * Supports length specifiers: <field:N> format where N is number of characters
 * 
 * @param string $template The template string with placeholders
 * @param array $user User data array
 * @param string $tenant_name Tenant name
 * @param string $group_name Optional group name
 * @return string Processed name string
 */
function process_naming_template($template, $user, $tenant_name = '', $group_name = '') {
    if (empty($template)) {
        return '';
    }
    
    // Extract user information
    $displayName = $user['name'] ?? $user['displayName'] ?? 'User';
    $parts = explode(' ', $displayName, 2);
    $firstName = $parts[0] ?? 'User';
    $lastName = $parts[1] ?? '';
    $email = $user['email'] ?? '';
    
    $result = $template;
    
    // Process placeholders with length specifier first (e.g., <first_name:5>)
    $result = preg_replace_callback('/<(\w+):(\d+)>/', function($matches) use ($firstName, $lastName, $tenant_name, $group_name, $email) {
        $field = $matches[1];
        $length = intval($matches[2]);
        
        if ($length <= 0) {
            return $matches[0]; // Return original if invalid length
        }
        
        $value = '';
        switch ($field) {
            case 'first_name':
                $value = substr($firstName, 0, $length);
                break;
            case 'last_name':
                $value = substr($lastName, 0, $length);
                break;
            case 'tenant_name':
                $value = substr($tenant_name, 0, $length);
                break;
            case 'group_name':
                $value = substr($group_name, 0, $length);
                break;
            case 'email':
                $value = substr($email, 0, $length);
                break;
            default:
                return $matches[0]; // Return original if unknown field
        }
        
        return $value;
    }, $result);
    
    // Process placeholders without length specifier
    $result = str_replace('<first_name>', $firstName, $result);
    $result = str_replace('<last_name>', $lastName, $result);
    $result = str_replace('<tenant_name>', $tenant_name, $result);
    $result = str_replace('<group_name>', $group_name, $result);
    $result = str_replace('<email>', $email, $result);
    
    return $result;
}

// Gets the current SSO configuration for a specific AAD tenant
function get_current_sso_configuration_aad()
{
    global $db_tables;

    $tenant_client_id = grab_request_var('client_id', '');
    if (empty($tenant_client_id)) {
        echo json_encode(array('status' => 'error', 'message' => 'Client ID is required.'));
        exit();
    }

    $table = $db_tables[DB_NAGIOSXI]['sso_mappings'];
    $users_table = $db_tables[DB_NAGIOSXI]['users'];
    $client_sql = escape_sql_param($tenant_client_id, DB_NAGIOSXI);
    $db_type = get_nagiosxi_db_type();
    $quote = ($db_type === 'pgsql') ? '' : '`';

    $sql = ($db_type === 'pgsql'
        ? "SELECT m.object_id, m.user_id, m.source, m.priority, m.source_group_id\n"
          . "FROM {$table} m\n"
          . "JOIN {$users_table} u ON m.user_id = u.user_id\n"
          . "WHERE m.client_id = '{$client_sql}'"
        : "SELECT m.`object_id`, m.`user_id`, m.`source`, m.`priority`, m.`source_group_id`\n"
          . "FROM `{$table}` m\n"
          . "JOIN `{$users_table}` u ON m.`user_id` = u.`user_id`\n"
          . "WHERE m.`client_id` = '{$client_sql}'");

    $rs = exec_sql_query(DB_NAGIOSXI, $sql, true, false);
    $results = array();

    if ($rs) {
        while (!$rs->EOF) {
            $results[] = array(
                'user_id_xi' => (int)$rs->fields['user_id'],
                'object_id' => $rs->fields['object_id'],
                'source' => $rs->fields['source'],
                'priority' => $rs->fields['priority'],
                'source_group_id' => $rs->fields['source_group_id'],
            );
            $rs->MoveNext();
        }
    }

    echo json_encode(array('status' => 'success', 'data' => $results));
    exit();
}

function save_users_to_XI()
{
    check_nagios_session_protector();

    $usersAAD = json_decode(grab_request_var('users', '[]'), true);

    if (!is_array($usersAAD) || empty($usersAAD)) {
        echo json_encode(array('status' => 'error', 'message' => 'No users to save.'));
        exit();
    }

    $existing_users = get_users();
    $user_ids_xi = array_map('intval', array_column($existing_users, 'user_id'));

    $successful = 0;
    $failed = 0;
    $skipped = 0;
    $failures = array();

    foreach ($usersAAD as $entry) {
        if (!is_array($entry)) {
            $skipped++;
            continue;
        }

        // Group configurations should be submitted via save_groups_to_XI
        if (array_key_exists('group_ids', $entry) || array_key_exists('account_mode', $entry) || array_key_exists('__group_configuration', $entry)) {
            $skipped++;
            continue;
        }

        $display_name = grab_array_var($entry, 'displayName', grab_array_var($entry, 'name', 'Unknown'));
        $object_id = grab_array_var($entry, 'user_id_aad', 'Unknown');

        try {
            $xi_user_id = process_individual_sso_request($entry, $user_ids_xi);
            if ($xi_user_id === 'none') {
                // "none" means user mapping was removed - this is a successful operation
                $successful++;
            } else if ($xi_user_id) {
                // User was created or assigned to existing user - successful
                $successful++;
            } else {
                // Shouldn't happen, but count as skipped if no result
                $skipped++;
            }
        } catch (Exception $e) {
            $failed++;
            $failures[] = array(
                'user' => $display_name,
                'aad_id' => $object_id,
                'reason' => $e->getMessage()
            );
            log_sso('Failed to save AAD user "' . $display_name . '" (ID: ' . $object_id . '): ' . $e->getMessage(), 'error');
        }
    }

    $total_attempted = $successful + $failed;
    
    if ($total_attempted === 0) {
        echo json_encode(array(
            'status' => 'error',
            'message' => 'No valid user data provided.',
            'successful' => 0,
            'failed' => 0,
            'skipped' => $skipped
        ));
        exit();
    }

    // Determine overall status
    if ($failed === 0) {
        $status = 'success';
        $message = 'Successfully processed ' . $successful . ' user' . ($successful !== 1 ? 's' : '') . '.';
    } else if ($successful === 0) {
        $status = 'error';
        $message = 'Failed to process all ' . $failed . ' user' . ($failed !== 1 ? 's' : '') . '.';
    } else {
        $status = 'warning';
        $message = 'Partially successful: ' . $successful . ' user' . ($successful !== 1 ? 's' : '') . ' processed, ' . $failed . ' failed.';
    }

    echo json_encode(array(
        'status' => $status,
        'message' => $message,
        'successful' => $successful,
        'failed' => $failed,
        'skipped' => $skipped,
        'failures' => $failures
    ));
    exit();
}

/*
 * For the given XI user, update their user meta and attributes with the newly set data from the form
 */
function set_new_user_meta_helper($user_meta, $user_attributes)
{
    foreach ($user_meta as $field => $value) {
        if (!empty($value)) {
            set_user_meta($user_attributes['user_id'], $field, $value);
        }
    }

    foreach ($user_attributes as $field => $value) {
        if (!is_null($value) && $value !== '') {
            change_user_attr($user_attributes['user_id'], $field, $value);
        }
    }
}

function remove_user_meta_aad_helper($user_id_aad)
{
    remove_sso_mappings_for_object($user_id_aad);
}

function remove_user_meta_aad_helper_by_client_id($client_id)
{
    global $db_tables;

    $table = $db_tables[DB_NAGIOSXI]['sso_mappings'];
    $client_id_sql = escape_sql_param($client_id, DB_NAGIOSXI);
    $db_type = get_nagiosxi_db_type();
    $quote = ($db_type === 'pgsql') ? '' : '`';

    // Get user_id and created_via_sso before deleting (for cleanup)
    $select_sql = ($db_type === 'pgsql'
        ? "SELECT user_id, created_via_sso FROM {$table} WHERE client_id = '{$client_id_sql}'"
        : "SELECT `user_id`, `created_via_sso` FROM `{$table}` WHERE `client_id` = '{$client_id_sql}'");
    $rs = exec_sql_query(DB_NAGIOSXI, $select_sql, true, false);
    
    $affected_users = array();
    if ($rs && $rs->RecordCount() > 0) {
        while (!$rs->EOF) {
            $user_id = (int)$rs->fields['user_id'];
            $created_via_sso = isset($rs->fields['created_via_sso']) ? (int)$rs->fields['created_via_sso'] : 0;
            if (!isset($affected_users[$user_id])) {
                $affected_users[$user_id] = $created_via_sso;
            } else {
                // If any mapping for this user has created_via_sso=1, mark it
                if ($created_via_sso == 1) {
                    $affected_users[$user_id] = 1;
                }
            }
            $rs->MoveNext();
        }
    }

    // Delete all mappings for this client
    $sql = ($db_type === 'pgsql'
        ? "DELETE FROM {$table} WHERE client_id = '{$client_id_sql}'"
        : "DELETE FROM `{$table}` WHERE `client_id` = '{$client_id_sql}'");
    exec_sql_query(DB_NAGIOSXI, $sql);

    // Check and cleanup orphaned SSO-created users
    // Only call cleanup if the deleted mapping had created_via_sso=1
    foreach ($affected_users as $user_id => $was_sso_created) {
        if ($was_sso_created == 1) {
            check_and_delete_orphaned_sso_user($user_id);
        }
    }
}

function process_individual_sso_request(array $user, array &$user_ids_xi)
{
    $object_id = grab_array_var($user, 'user_id_aad', '');
    $client_id = grab_array_var($user, 'client_id', '');

    if (empty($object_id) || empty($client_id)) {
        throw new Exception('Missing Entra ID user identifier or client ID.');
    }

    $tenant_details = get_tenants_credentials_helper($client_id);
    $tenant_id = grab_array_var($tenant_details, 'tenant_id', '');
    if (empty($tenant_id)) {
        throw new Exception('Failed to determine tenant ID for client: ' . $client_id);
    }

    $target_user_id = grab_array_var($user, 'user_id', 'none');
    
    // Log entry with key information for debugging user import behavior
    $display_name = grab_array_var($user, 'displayName', grab_array_var($user, 'name', 'Unknown'));
    $email = grab_array_var($user, 'email', 'N/A');
    log_sso('process_individual_sso_request: Processing AAD user "' . $display_name . '" (Email: ' . $email . ', AAD ID: ' . $object_id . ') - Received user_id: "' . $target_user_id . '"', 'info');
    
    // If user is set to "No User" (none), remove the AAD mapping
    if ($target_user_id === 'none') {
        log_sso('process_individual_sso_request: Removing SSO mapping for "' . $display_name . '" (AAD ID: ' . $object_id . ')', 'info');
        remove_sso_mapping($object_id, $client_id);
        return "none";
    }
    
    $xi_user_id = null;
    $user_was_created = false;

    // Determine action based on user_id value:
    // 1. If user_id equals AAD ID (object_id) → Create new user (frontend convention)
    // 2. If user_id is valid positive integer → Use that existing XI user
    // 3. If user_id is invalid format → Throw error (DO NOT create user)
    // 4. If user_id is empty → Create new user
    
    if (!empty($target_user_id)) {
        // Check if user_id matches AAD ID - this is the frontend's way of saying "create new user"
        if ($target_user_id === $object_id) {
            log_sso('process_individual_sso_request: user_id matches AAD ID for "' . $display_name . '" - Will create new user', 'info');
            // Leave $xi_user_id as null to trigger new user creation below
        }
        // Check if it's a valid positive integer (XI user ID)
        else if (ctype_digit($target_user_id) && $target_user_id > 0) {
            $candidate = (int)$target_user_id;
            if (in_array($candidate, $user_ids_xi, true)) {
                $xi_user_id = $candidate;
                $user_was_created = false;
                log_sso('process_individual_sso_request: Assigning to existing XI user_id ' . $xi_user_id . ' for "' . $display_name . '"', 'info');
            } else {
                log_sso('process_individual_sso_request: XI user_id ' . $candidate . ' not found for "' . $display_name . '"', 'error');
                throw new Exception('Invalid XI user_id ' . $candidate . ' - user does not exist');
            }
        }
        // Invalid format - not a proper XI user ID
        else {
            log_sso('process_individual_sso_request: Invalid user_id format "' . $target_user_id . '" for "' . $display_name . '"', 'error');
            throw new Exception('Invalid user_id format: "' . $target_user_id . '" - expected numeric XI user ID or AAD ID');
        }
    }

    // Create new user if no existing XI user was selected
    if (!$xi_user_id) {
        $xi_user_id = create_xi_user_from_form($user, $user_ids_xi);
        if ($xi_user_id) {
            $user_was_created = true;
        } else {
            log_sso('process_individual_sso_request: User creation FAILED for "' . $display_name . '" (AAD ID: ' . $object_id . ')', 'error');
            throw new Exception('Unable to create Nagios XI user for AAD object: ' . $object_id);
        }
    }

    // Re-verify that the user_id exists before linking
    if (!$user_was_created) {
        if (!in_array($xi_user_id, $user_ids_xi, true)) {
            log_sso('process_individual_sso_request: CRITICAL ERROR - XI user_id ' . $xi_user_id . ' does not exist in database for "' . $display_name . '" (AAD ID: ' . $object_id . ')', 'error');
            throw new Exception('XI user_id ' . $xi_user_id . ' does not exist in database - user may have been deleted');
        }
    }

    $source_group_id = grab_array_var($user, 'source_group_id');
    log_sso('process_individual_sso_request: Linking AAD user "' . $display_name . '" to XI user_id ' . $xi_user_id . ' (' . ($user_was_created ? 'newly created' : 'existing') . ') - created_via_sso flag: ' . ($user_was_created ? '1 (YES)' : '0 (NO)'), 'info');
    link_aad_user_to_xi_user($object_id, $client_id, $tenant_id, $xi_user_id, 'individual', 'individual', $source_group_id, $user_was_created);

    return $xi_user_id;
}

function create_xi_user_from_form(array $user, array &$user_ids_xi)
{
    $client_id = grab_array_var($user, 'client_id', '');
    $tenant_name = grab_array_var($user, 'tenant_name', '');
    
    // Get user details for logging
    $display_name = grab_array_var($user, 'displayName', grab_array_var($user, 'name', 'Unknown'));
    $object_id = grab_array_var($user, 'user_id_aad', 'Unknown');
    $email = grab_array_var($user, 'email', 'N/A');

    if (empty($tenant_name) && !empty($client_id)) {
        try {
            $tenant_details = get_tenants_credentials_helper($client_id);
            $tenant_name = grab_array_var($tenant_details, 'name', 'SSO');
        } catch (Exception $e) {
            log_sso('Failed to get tenant name: ' . $e->getMessage(), 'warning');
            $tenant_name = 'SSO';
        }
    }

    $name_field = grab_array_var($user, 'name', '');
    $processed_name = $name_field;
    if (!empty($name_field) && preg_match('/<(\w+)(?::\d+)?>/', $name_field)) {
        $processed_name = process_naming_template($name_field, $user, $tenant_name);
    }

    if (empty($processed_name)) {
        $processed_name = $tenant_name . ' -- ' . grab_array_var($user, 'displayName', 'User');
    }

    $username = sanitize_username_for_nagios($processed_name, $tenant_name . ' -- User');
    $processed_name = $username;
    
    log_sso('create_xi_user_from_form: Creating XI user "' . $username . '" for AAD user "' . $display_name . '"', 'info');
    
    $password1 = bin2hex(random_bytes(16));
    $email = grab_array_var($user, 'email', '');
    $level = (strcasecmp(grab_array_var($user, 'auth_level', 'User'), 'Admin') === 0) ? 255 : 1;
    $forcechangepass = 0;
    $add_contact = normalize_boolean_flag(grab_array_var($user, 'add_contact', 0));
    $api_enabled = normalize_boolean_flag(grab_array_var($user, 'api_enabled', 0));
    $errmsg = array();

    $new_user_id = add_user_account($username, $password1, $processed_name, $email, $level, $forcechangepass, $add_contact, $api_enabled, $errmsg);

    if ($errmsg && !empty($errmsg)) {
        $is_duplicate_error = false;
        foreach ($errmsg as $error_msg) {
            if (stripos($error_msg, 'already exists') !== false || stripos($error_msg, 'username already') !== false) {
                $is_duplicate_error = true;
                break;
            }
        }

        if ($is_duplicate_error) {
            log_sso('create_xi_user_from_form: Username "' . $username . '" already exists for "' . $display_name . '" - Trying variants', 'info');
            $base_username = sanitize_username_for_nagios($username, $tenant_name . ' -- User', false);
            $suffix = 1;
            $max_attempts = 100;
            while ($suffix <= $max_attempts) {
                $candidate_username = $base_username . '-' . $suffix;
                $candidate_username = sanitize_username_for_nagios($candidate_username, $tenant_name . ' -- User', false);
                $errmsg = array();
                $test_user_id = add_user_account($candidate_username, $password1, $candidate_username, $email, $level, $forcechangepass, $add_contact, $api_enabled, $errmsg);
                if ($test_user_id && empty($errmsg)) {
                    $username = $candidate_username;
                    $processed_name = $candidate_username;
                    $new_user_id = $test_user_id;
                    log_sso('create_xi_user_from_form: Created user with variant username "' . $username . '" (XI user_id: ' . $new_user_id . ') for "' . $display_name . '"', 'info');
                    break;
                }
                $suffix++;
            }

            if (!$new_user_id) {
                log_sso('create_xi_user_from_form: Failed to create unique username after ' . $max_attempts . ' attempts for "' . $display_name . '"', 'error');
                throw new Exception('Failed to create unique username for user: ' . $base_username);
            }
        } else {
            $error_details = implode(', ', $errmsg);
            log_sso('create_xi_user_from_form: Error creating user "' . $username . '" for "' . $display_name . '": ' . $error_details, 'error');
            throw new Exception('Error creating user ' . $username . ': ' . implode(', ', $errmsg));
        }
    }

    if (!$new_user_id) {
        log_sso('create_xi_user_from_form: User creation returned null for "' . $display_name . '" with no errors', 'error');
        return null;
    }

    $user_meta = array(
        'name'                             => $processed_name,
        'mobile_number'                    => grab_array_var($user, 'phone'),
        'enable_notifications'             => normalize_boolean_flag(grab_array_var($user, 'enable_notifications', 0)),
        'language'                         => grab_array_var($user, 'language'),
        'date_format'                      => grab_array_var($user, 'date_format'),
        'number_format'                    => grab_array_var($user, 'number_format'),
        'week_format'                      => grab_array_var($user, 'week_format'),
        'userlevel'                        => $level,
        'authorized_for_all_objects'       => normalize_boolean_flag(grab_array_var($user, 'see_hosts_services', 0)),
        'authorized_for_all_object_commands' => normalize_boolean_flag(grab_array_var($user, 'control_hosts_services', 0)),
        'authorized_to_configure_objects'  => normalize_boolean_flag(grab_array_var($user, 'configure_hosts_services', 0)),
        'advanced_user'                    => normalize_boolean_flag(grab_array_var($user, 'advanced_user', 0)),
        'authorized_for_monitoring_system' => normalize_boolean_flag(grab_array_var($user, 'monitoring_engine', 0)),
        'readonly_user'                    => normalize_boolean_flag(grab_array_var($user, 'read_only', 0)),
        'api_enabled'                      => normalize_boolean_flag(grab_array_var($user, 'api_enabled', 0)),
        'autodeploy_access'                => normalize_boolean_flag(grab_array_var($user, 'autodeploy_access', 0)),
        'nagios_mod_gearman_access'        => normalize_boolean_flag(grab_array_var($user, 'nagios_mod_gearman_access', 0)),
        'ccm_access'                       => grab_array_var($user, 'core_config_manager_access', 0),
        'tours'                            => serialize(array('new_user' => 1)),
    );

    $user_attributes = array(
        'user_id' => $new_user_id,
        'enabled' => normalize_boolean_flag(grab_array_var($user, 'enable_account', 1)),
    );

    set_new_user_meta_helper($user_meta, $user_attributes);

    $user_ids_xi[] = (int)$new_user_id;

    return (int)$new_user_id;
}

function check_update_deltas()
{
    /*
     * This function will check for updates between the SSO users and the AAD users.
     * It will update the SSO users based on the deltas.
     * TODO: THIS IS WRONG AND AI -- MAKE IT RIGHT
     */
    $deltas = get_deltas_from_AAD();
    
    // If no deltas, exit early
    if (empty($deltas)) {
        return;
    }
    
    foreach ($deltas as $delta) {
        $type = grab_array_var($delta, 'type');
        $user = grab_array_var($delta, 'user', []);

        if (empty($type) || !is_array($user)) {
            log_sso('Invalid delta format in check_update_deltas(): ' . json_encode($delta), 'warning');
            continue;
        }

        $object_id = grab_array_var($user, 'user_id_aad');
        $client_id = grab_array_var($user, 'client_id');
        $tenant_id = grab_array_var($user, 'tenant_id', '');
        $xi_user_id = grab_array_var($user, 'user_id');

        switch ($type) {
            case 'new':
            case 'updated':
                if (empty($object_id) || empty($client_id) || empty($xi_user_id)) {
                    log_sso('Missing required user fields in delta: ' . json_encode($user), 'warning');
                    continue 2;
                }
                // For deltas, we can't determine if user was newly created, so set created_via_sso=false to be safe
                link_aad_user_to_xi_user($object_id, $client_id, $tenant_id, (int)$xi_user_id, 'individual', 'individual', grab_array_var($user, 'source_group_id'), false);
                break;

            case 'deleted':
                if (empty($object_id)) {
                    log_sso('Missing object ID in deleted delta: ' . json_encode($user), 'warning');
                    continue 2;
                }
                if (!empty($client_id)) {
                    remove_sso_mapping($object_id, $client_id);
                } else {
                    remove_sso_mappings_for_object($object_id);
                }
                break;

            default:
                log_sso('Unknown delta type: ' . $type, 'warning');
                break;
        }
    }
}

/**
 * Clear all individual user SSO configurations for a given client_id
 * AJAX handler function
 */
function clear_all_individual_users()
{
    check_nagios_session_protector();

    $client_id = grab_request_var('client_id', '');
    $nsp = grab_request_var('nsp', '');

    if (empty($client_id)) {
        echo json_encode(array('status' => 'error', 'message' => 'Client ID is required.'));
        exit();
    }

    try {
        $result = clear_all_individual_configs_for_client($client_id);

        if (isset($result['error'])) {
            echo json_encode(array(
                'status' => 'error',
                'message' => $result['error']
            ));
            exit();
        }

        $deleted_count = isset($result['deleted_count']) ? (int)$result['deleted_count'] : 0;
        $updated_count = isset($result['updated_count']) ? (int)$result['updated_count'] : 0;
        $total_affected = isset($result['total_affected']) ? (int)$result['total_affected'] : 0;

        $message = '';
        if ($total_affected === 0) {
            $message = 'No individual user configurations found to clear.';
        } else {
            $parts = array();
            if ($deleted_count > 0) {
                $parts[] = $deleted_count . ' ' . ($deleted_count === 1 ? 'configuration was' : 'configurations were') . ' deleted';
            }
            if ($updated_count > 0) {
                $parts[] = $updated_count . ' ' . ($updated_count === 1 ? 'configuration was' : 'configurations were') . ' updated to use group settings';
            }
            $message = 'Successfully cleared individual user configurations: ' . implode(', ', $parts) . '.';
        }

        echo json_encode(array(
            'status' => 'success',
            'message' => $message,
            'data' => array(
                'deleted_count' => $deleted_count,
                'updated_count' => $updated_count,
                'total_affected' => $total_affected
            )
        ));
    } catch (Exception $e) {
        log_sso('Error clearing individual user configurations: ' . $e->getMessage(), 'error');
        echo json_encode(array(
            'status' => 'error',
            'message' => 'Failed to clear individual user configurations: ' . $e->getMessage()
        ));
    }

    exit();
}

