<?php
$initialDir = getcwd();
chdir(__DIR__ . '/../');

require_once __DIR__ . '/../vendor/autoload.php';

use Ubnt\UcrmPluginSdk\Service\UcrmApi;
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use GuzzleHttp\Client;

// -- Configuration --
$customAttributeKey = 'passwordAntenaCliente';
$siteAttributeKey = 'site';
$antenaSectorialAttributeKey = 'antenaSectorial';
$logFile = __DIR__ . '/audit_passwords.log';

// -- Argument Parsing --
$fixOption = null;
foreach ($argv as $arg) {
    if (strpos($arg, '--fix=') === 0) {
        $fixOption = substr($arg, 6);
    }
}

$fixLimit = 0;
if ($fixOption === 'all') {
    $fixLimit = PHP_INT_MAX;
} elseif (is_numeric($fixOption)) {
    $fixLimit = (int)$fixOption;
}

// -- Helpers --
function logMessage($message)
{
    $logFile = __DIR__ . '/audit_passwords.log';
    $timestamp = date('Y-m-d H:i:s');
    file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND);
    // Only write to STDERR if running in CLI mode, not web server (optional, but good practice)
    if (php_sapi_name() === 'cli') {
        fwrite(STDERR, "[$timestamp] $message\n");
    }
}

function isTestEnvironment($config)
{
    $ipServer = $config['ipserver'] ?? '';
    return ($ipServer === '172.16.5.134' || $ipServer === 'venus.siip.mx' || $ipServer === 'pruebas.internet.mx');
}

// -- Initialization --
$config = PluginConfigManager::create()->loadConfig();
$ipServer = $config['ipserver'] ?? 'localhost';
$apiUrl = "https://$ipServer/crm/api/v1.0/";
$token = $config['apitoken'] ?? '';

if (empty($token)) {
    logMessage("Error: API Token is missing in plugin configuration.");
    exit(1);
}

// Initialize UCRM Client
$client = new Client([
    'base_uri' => $apiUrl,
    'verify' => false,
    'headers' => [
        'X-Auth-App-Key' => $token,
        'Content-Type' => 'application/json',
    ],
]);
$ucrmApi = new UcrmApi($client, $token);

// Initialize UNMS Client
$unmsClient = new Client([
    'base_uri' => "https://{$ipServer}/nms/api/v2.1/",
    'verify' => false,
    'headers' => [
        'X-Auth-Token' => $config['unmsApiToken'] ?? ''
    ]
]);

function fetchAllClients($client)
{
    $allClients = [];
    $page = 1;
    $limit = 500;

    do {
        try {
            $response = $client->get('clients', [
                'query' => [
                    'limit' => $limit,
                    'offset' => ($page - 1) * $limit,
                    'isArchived' => 0,
                ]
            ]);

            $data = json_decode($response->getBody()->getContents(), true);

            if (empty($data)) break;
            $allClients = array_merge($allClients, $data);
            if (count($data) < $limit) break;
            $page++;
        } catch (\Exception $e) {
            logMessage("Error fetching page $page: " . $e->getMessage());
            break;
        }
    } while (true);

    return $allClients;
}

function generateStrongPassword($length = 16)
{
    $lower = 'abcdefghijkmnopqrstuvwxyz';
    $upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
    $digits = '23456789';
    $symbols = '!@#$%&*()-_=+[]{};:,.<>?';

    $all = $lower . $upper . $digits . $symbols;
    $pwChars = [];

    $pwChars[] = $lower[random_int(0, strlen($lower) - 1)];
    $pwChars[] = $upper[random_int(0, strlen($upper) - 1)];
    $pwChars[] = $digits[random_int(0, strlen($digits) - 1)];
    $pwChars[] = $symbols[random_int(0, strlen($symbols) - 1)];

    for ($i = count($pwChars); $i < $length; $i++) {
        $pwChars[] = $all[random_int(0, strlen($all) - 1)];
    }

    shuffle($pwChars);
    return implode('', $pwChars);
}

/**
 * Fix client password AND extract network data (Site, Antena/Sectorial).
 * Returns an associative array with 'password', 'siteName', 'deviceInfo'.
 *
 * Edge cases handled:
 *  - Client with tag 'NS REPETIDOR' → password = 'Este cliente funciona como Repetidor'
 *  - Service status Ended(2) or Obsolete(5) → password = 'Servicio Finalizado'
 *  - Device disconnected → password = 'Antena desconectada al momento de obtener la contraseña'
 */
function fixClientData($clientId, $ucrmApi, $unmsClient, $attributeIds, $config, $currentPassword, $logger = null)
{
    $passwordAttributeId = $attributeIds['password'];
    $siteAttributeId = $attributeIds['site'] ?? null;
    $antenaSectorialAttributeId = $attributeIds['antenaSectorial'] ?? null;

    $result = [
        'password' => '',
        'siteName' => null,
        'deviceInfo' => null,
    ];

    // Local helper for unified logging
    $logMsg = function ($msg, $level = 'info') use ($logger, $clientId) {
        $fullMsg = "[Client $clientId] " . $msg;
        logMessage($fullMsg);
        if ($logger) {
            $logger->log($level, $fullMsg);
        }
    };

    try {
        $isTest = isTestEnvironment($config);

        // Fetch client attributes from CRM to check current site & antenna details
        $currentSite = null;
        $currentAntenna = null;
        try {
            $clientData = $ucrmApi->get("clients/$clientId");
            $clientTags = $clientData['tags'] ?? [];
            
            foreach ($clientData['attributes'] ?? [] as $attr) {
                if ($attr['key'] === 'site' || $attr['customAttributeId'] == $siteAttributeId) {
                    $currentSite = trim((string)$attr['value']);
                }
                if ($attr['key'] === 'antenaSectorial' || $attr['customAttributeId'] == $antenaSectorialAttributeId) {
                    $currentAntenna = trim((string)$attr['value']);
                }
            }

            // ── Edge Case 1: Check client tags for "NS REPETIDOR" ──
            foreach ($clientTags as $tag) {
                if (stripos($tag['name'] ?? '', 'NS REPETIDOR') !== false) {
                    $msg = 'Este cliente funciona como Repetidor';
                    $result['password'] = $msg;
                    $result['deviceInfo'] = 'REPETIDOR';
                    
                    $attrUpdates = [];
                    if ($currentPassword !== $msg) {
                        $attrUpdates[$passwordAttributeId] = $msg;
                    }
                    if ($antenaSectorialAttributeId && $currentAntenna !== 'REPETIDOR') {
                        $attrUpdates[$antenaSectorialAttributeId] = 'REPETIDOR';
                    }

                    if (!empty($attrUpdates)) {
                        patchClientAttributes($ucrmApi, $clientId, $attrUpdates);
                        $result['changed'] = true;
                    } else {
                        $result['changed'] = false;
                    }
                    $logMsg("Tag NS REPETIDOR detected → skipping");
                    return $result;
                }
            }
        } catch (\Exception $e) {
            $logMsg("Warning: Could not fetch client details/tags: " . $e->getMessage(), 'warning');
        }

        // ── Get Services ──
        $svcs = $ucrmApi->get('clients/services', ['clientId' => $clientId]);

        if (empty($svcs)) {
            $msg = '⚠️ Cliente sin servicios/antenas';
            $result['password'] = $msg;
            if ($currentPassword !== $msg) {
                patchClientAttributes($ucrmApi, $clientId, [
                    $passwordAttributeId => $msg,
                ]);
                $result['changed'] = true;
            } else {
                $result['changed'] = false;
            }
            $logMsg($msg, 'warning');
            return $result;
        }

        $allServicePasswords = [];
        $numServices = count($svcs);
        $siteName = null;
        $deviceInfo = null;

        foreach ($svcs as $index => $svc) {
            $label = ($numServices > 1) ? "Servicio " . ($index + 1) . ":" : "";
            $siteId = $svc['unmsClientSiteId'] ?? null;
            $serviceStatus = $svc['status'] ?? null;
            $passwordValue = "";

            // ── Edge Case 2: Service Ended (2) or Obsolete (5) ──
            if ($serviceStatus === 2 || $serviceStatus === 5) {
                $passwordValue = 'Servicio Finalizado';
                $logMsg("Service " . ($index + 1) . " has status $serviceStatus (Ended/Obsolete)");
                $allServicePasswords[] = trim("$label $passwordValue");
                continue;
            }

            if (!$siteId) {
                $passwordValue = "⚠️ Sin sitio";
                $logMsg("Service " . ($index + 1) . " has no unmsClientSiteId", 'warning');
            } else {
                if ($isTest) {
                    // Test Env Logic: Preserve existing if valid
                    $foundInCRM = false;
                    if (!empty($currentPassword)) {
                        if ($numServices > 1) {
                            if (preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $currentPassword, $matches)) {
                                $passwordValue = $matches[1];
                                $foundInCRM = true;
                            }
                        } else {
                            if (strpos($currentPassword, '⚠️') === false && strpos($currentPassword, 'Servicio') === false) {
                                $passwordValue = trim($currentPassword);
                                $foundInCRM = true;
                            }
                        }
                    }

                    if (!$foundInCRM) {
                        $passwordValue = generateStrongPassword(20);
                        // Static Test Data
                        $siteName = 'VENUS';
                        $deviceInfo = 'Sectorial de pruebas 172.16.5.134';
                        $logMsg("Test Env: Generated new password (Service $index) and set static network data.");
                    } else {
                        $siteName = 'VENUS';
                        $deviceInfo = 'Sectorial de pruebas 172.16.5.134';
                        $logMsg("Test Env: Preserved existing password but ensured static network data.");
                    }
                } else {
                    // Production Logic
                    try {
                        $respDev = $unmsClient->get("devices?siteId=$siteId");
                        $devs = json_decode($respDev->getBody()->getContents(), true);

                        if (empty($devs)) {
                            $passwordValue = "⚠️ Sin antena";
                            $logMsg("No devices found for siteId: $siteId", 'warning');
                        } else {
                             $passVault = null;
                            
                             // Find first client-like device or fallback to first device
                             $firstDev = null;
                             foreach ($devs as $d) {
                                 $role = $d['identification']['role'] ?? '';
                                 if ($role === 'station' || $role === 'cpe' || $role === 'client') {
                                     $firstDev = $d;
                                     break;
                                 }
                             }
                             if ($firstDev === null) {
                                 $firstDev = $devs[0];
                             }

                             // Extract network data (always execute for first device if index is 0)
                             if ($firstDev && $index === 0) {
                                 $siteName = $firstDev['identification']['site']['parent']['name'] ?? null;
                                 $apDeviceName = $firstDev['attributes']['apDevice']['name'] ?? null;
                                 $apDeviceId = $firstDev['attributes']['apDevice']['id'] ?? null;

                                 if ($apDeviceName && $apDeviceId) {
                                     try {
                                         $respApDev = $unmsClient->get("devices/$apDeviceId");
                                         $apDevData = json_decode($respApDev->getBody()->getContents(), true);
                                         $apDeviceIP = $apDevData['ipAddress'] ?? '';
                                         $deviceInfo = trim("$apDeviceName $apDeviceIP");
                                         
                                         // AP Device Site Name Fallback:
                                         if (empty($siteName)) {
                                             $siteName = $apDevData['identification']['site']['name'] ?? null;
                                         }
                                     } catch (\Exception $e) {
                                         $deviceInfo = $apDeviceName;
                                         $logMsg("Warning: Could not fetch AP device IP for $apDeviceId: " . $e->getMessage(), 'warning');
                                     }
                                 } elseif (!$apDeviceName) {
                                     $deviceInfo = 'REPETIDOR';
                                 }
                             }

                             // ── Extraer contraseña previa de este servicio en el CRM si existe ──
                             $prevServicePassword = null;
                             $hasValidPrevPassword = false;
                             if (!empty($currentPassword)) {
                                 if ($numServices > 1) {
                                     if (preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $currentPassword, $matches)) {
                                         $tempPass = trim($matches[1]);
                                         // Validar que no sea una leyenda de error conocida o un emoji
                                         if (stripos($tempPass, 'desconectada') === false && 
                                             stripos($tempPass, 'Finalizado') === false && 
                                             stripos($tempPass, 'Error') === false &&
                                             stripos($tempPass, 'Repetidor') === false &&
                                             stripos($tempPass, 'sin') === false) {
                                             $prevServicePassword = $tempPass;
                                             $hasValidPrevPassword = true;
                                         }
                                     }
                                 } else {
                                     $tempPass = trim($currentPassword);
                                     if (strpos($tempPass, '⚠️') === false && 
                                         strpos($tempPass, 'Servicio') === false &&
                                         stripos($tempPass, 'desconectada') === false && 
                                         stripos($tempPass, 'Finalizado') === false && 
                                         stripos($tempPass, 'Error') === false &&
                                         stripos($tempPass, 'Repetidor') === false &&
                                         stripos($tempPass, 'sin') === false) {
                                         $prevServicePassword = $tempPass;
                                         $hasValidPrevPassword = true;
                                     }
                                 }
                             }

                             // ── Edge Case 3: Check if device is disconnected ──
                             $deviceStatus = $firstDev['overview']['status'] ?? 'unknown';
                             if ($deviceStatus === 'disconnected') {
                                 if ($hasValidPrevPassword) {
                                     // Preservamos la contraseña real para que el instalador pueda usarla
                                     $passwordValue = $prevServicePassword;
                                     $logMsg("Device is disconnected (siteId=$siteId), but preserved existing valid password from CRM: '$passwordValue'.");
                                 } else {
                                     $passwordValue = 'Antena desconectada al momento de obtener la contraseña';
                                     $logMsg("Device is disconnected (siteId=$siteId) and no valid password was found in CRM.", 'warning');
                                 }
                                 $allServicePasswords[] = trim("$label $passwordValue");
                                 continue; // Skip vault check for this service
                             }

                            $firstDeviceId = null;
                            foreach ($devs as $dev) {
                                $deviceId = $dev['identification']['id'] ?? null;
                                if (!$deviceId) continue;
                                if (!$firstDeviceId) $firstDeviceId = $deviceId;

                                try {
                                    $respVault = $unmsClient->get("vault/$deviceId/credentials");
                                    $vault = json_decode($respVault->getBody()->getContents(), true);
                                    if (isset($vault['credentials'][0]['password'])) {
                                        $passVault = $vault['credentials'][0]['password'];
                                        break;
                                    }
                                } catch (\Exception $e) {
                                    $logMsg("Could not fetch credentials from device $deviceId: " . $e->getMessage(), 'debug');
                                    continue;
                                }
                            }

                            if ($passVault) {
                                $passwordValue = $passVault;
                            } elseif ($firstDeviceId) {
                                // Regenerate on Device
                                $newPass = generateStrongPassword(16);
                                try {
                                    $unmsClient->post("vault/$firstDeviceId/credentials/regenerate", [
                                        'json' => [['username' => 'ubnt', 'password' => $newPass, 'readOnly' => true]]
                                    ]);
                                    $passwordValue = $newPass;
                                    $logMsg("Regenerated password on UNMS Device $firstDeviceId");
                                } catch (\Exception $e) {
                                    $passwordValue = "⚠️ Error Regenerating: " . $e->getMessage();
                                    $logMsg("Error regenerating password on device $firstDeviceId: " . $e->getMessage(), 'error');
                                }
                            } else {
                                $passwordValue = "⚠️ Sin dispositivo válido";
                                $logMsg("No valid device ID for credentials", 'warning');
                            }
                        }
                    } catch (\Exception $e) {
                        $passwordValue = "⚠️ Error API UNMS: " . $e->getMessage();
                        $logMsg("Exception during UNMS API query: " . $e->getMessage(), 'error');
                    }
                }
            }
            $allServicePasswords[] = trim("$label $passwordValue");
        }

        $finalValue = implode(' ', $allServicePasswords);
        $result['password'] = $finalValue;
        $result['siteName'] = $siteName;
        $result['deviceInfo'] = $deviceInfo;

        // Build attribute updates (always patch all available fields)
        $attrUpdates = [];

        // Password: only if changed
        if ($finalValue !== $currentPassword) {
            $attrUpdates[$passwordAttributeId] = $finalValue;
        }

        // Site: patch if we got data and it is different
        if ($siteAttributeId) {
            if ($siteName !== null) {
                if (!empty($siteName) || empty($currentSite)) {
                    if ($siteName !== $currentSite) {
                        $attrUpdates[$siteAttributeId] = $siteName;
                        $logMsg("Site to update resolved: '$siteName' (previous was: '" . ($currentSite ?: 'empty') . "')");
                    } else {
                        $logMsg("Site name matches current CRM value '$currentSite'. Patch skipped.");
                    }
                } else {
                    $logMsg("Site name resolved from UISP is empty and CRM already has a value '$currentSite'. Prevented overwriting with empty value.");
                }
            } else {
                $logMsg("UISP did not return a parent site name (siteName is null).");
            }
        } else {
            $logMsg("Site attribute ID ('site') was not resolved.");
        }

        // Antena/Sectorial: patch if we got data and it is different
        if ($antenaSectorialAttributeId) {
            if ($deviceInfo !== null) {
                $isNewValueRepeater = (strtoupper($deviceInfo) === 'REPETIDOR');
                $isCurrentValueValuable = (!empty($currentAntenna) && strtoupper($currentAntenna) !== 'REPETIDOR' && stripos($currentAntenna, '⚠️') === false);
                
                // Only update if we're not overwriting a valid sector name with a generic 'REPETIDOR'
                if (!($isNewValueRepeater && $isCurrentValueValuable)) {
                    if ($deviceInfo !== $currentAntenna) {
                        $attrUpdates[$antenaSectorialAttributeId] = $deviceInfo;
                        $logMsg("Antenna/Sectorial to update resolved: '$deviceInfo' (previous was: '" . ($currentAntenna ?: 'empty') . "')");
                    } else {
                        $logMsg("Antenna info matches current CRM value '$currentAntenna'. Patch skipped.");
                    }
                } else {
                    $logMsg("Prevented overwriting valuable CRM antenna sector '$currentAntenna' with generic 'REPETIDOR'.");
                }
            } else {
                $logMsg("UISP did not return antenna info (deviceInfo is null).");
            }
        } else {
            $logMsg("Antenna attribute ID ('antenaSectorial') was not resolved.");
        }

        if (!empty($attrUpdates)) {
            patchClientAttributes($ucrmApi, $clientId, $attrUpdates);
            $logMsg("Patched " . count($attrUpdates) . " attribute(s): " . json_encode($attrUpdates));
        }

        $result['changed'] = ($finalValue !== $currentPassword) || !empty($attrUpdates);

        return $result;
    } catch (\Exception $e) {
        $logMsg("Fatal error in fixClientData: " . $e->getMessage(), 'error');
        $result['password'] = "Error fixing: " . $e->getMessage();
        return $result;
    }
}

/**
 * Patch multiple client custom attributes in a single API call.
 */
function patchClientAttributes($ucrmApi, $clientId, array $attributeUpdates)
{
    $attributes = [];
    foreach ($attributeUpdates as $attrId => $value) {
        if ($attrId && $value !== null) {
            $attributes[] = ['customAttributeId' => (int)$attrId, 'value' => (string)$value];
        }
    }
    if (!empty($attributes)) {
        $ucrmApi->patch("clients/$clientId", ['attributes' => $attributes]);
    }
}


/**
 * Resolves UCRM custom attribute IDs based on keys.
 */
function resolveAttributeIds($ucrmApi, $customAttributeKey, $siteAttributeKey, $antenaSectorialAttributeKey)
{
    $customAttributeId = null;
    $siteAttributeId = null;
    $antenaSectorialAttributeId = null;

    try {
        $attributes = $ucrmApi->get('custom-attributes', ['attributeType' => 'client']);
        foreach ($attributes as $attr) {
            switch ($attr['key']) {
                case $customAttributeKey:
                    $customAttributeId = $attr['id'];
                    break;
                case $siteAttributeKey:
                    $siteAttributeId = $attr['id'];
                    break;
                case $antenaSectorialAttributeKey:
                    $antenaSectorialAttributeId = $attr['id'];
                    break;
            }
        }
    } catch (\Exception $e) {
        logMessage("Error fetching attributes: " . $e->getMessage());
        return null; // Return null on failure
    }

    if (!$customAttributeId) {
        logMessage("Error: Custom attribute '$customAttributeKey' not found.");
        return null;
    }

    // Log resolved IDs
    logMessage("Resolved attribute IDs: password=$customAttributeId, site=" . ($siteAttributeId ?? 'N/A') . ", antenaSectorial=" . ($antenaSectorialAttributeId ?? 'N/A'));

    return [
        'password' => $customAttributeId,
        'site' => $siteAttributeId,
        'antenaSectorial' => $antenaSectorialAttributeId,
    ];
}

// -- Main Execution --

if (!defined('INCLUDED_AS_LIBRARY')) {
    // Initialization
    $config = PluginConfigManager::create()->loadConfig();
    $ipServer = $config['ipserver'] ?? 'localhost';
    $apiUrl = "https://$ipServer/crm/api/v1.0/";
    $token = $config['apitoken'] ?? '';

    if (empty($token)) {
        logMessage("Error: API Token is missing in plugin configuration.");
        exit(1);
    }

    // Initialize UCRM Client
    $client = new Client([
        'base_uri' => $apiUrl,
        'verify' => false,
        'headers' => [
            'X-Auth-App-Key' => $token,
            'Content-Type' => 'application/json',
        ],
    ]);
    $ucrmApi = new UcrmApi($client, $token);

    // Initialize UNMS Client
    $unmsClient = new Client([
        'base_uri' => "https://{$ipServer}/nms/api/v2.1/",
        'verify' => false,
        'headers' => [
            'X-Auth-Token' => $config['unmsApiToken'] ?? ''
        ]
    ]);

    // Resolve IDs
    $attributeIds = resolveAttributeIds($ucrmApi, $customAttributeKey, $siteAttributeKey, $antenaSectorialAttributeKey);
    if (!$attributeIds) {
        exit(1);
    }

    if ($fixOption === null) {
        // Check for --file argument
        $fileOption = null;
        foreach ($argv as $arg) {
            if (strpos($arg, '--file=') === 0) {
                $fileOption = substr($arg, 7);
            }
        }

        if ($fileOption) {
            if ($fileOption[0] !== '/') {
                $fileOption = $initialDir . '/' . $fileOption;
            }

            if (!file_exists($fileOption)) {
                logMessage("Error: File not found: $fileOption");
                exit(1);
            }

            logMessage("Processing from file: $fileOption");

            // Read all lines
            $lines = file($fileOption, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

            // Process lines
            $originalCount = count($lines);
            $processedCount = 0;

            // Helper to rewrite file
            $rewriteFile = function ($remainingLines) use ($fileOption) {
                file_put_contents($fileOption, implode("\n", $remainingLines) . "\n");
            };

            $header = null;
            if (!empty($lines)) {
                $firstLine = $lines[0];
                $parts = explode(',', $firstLine);
                if (!is_numeric($parts[0])) {
                    $header = array_shift($lines); // Remove header from processing list
                }
            }

            $queue = $lines; // Working copy

            while (!empty($queue)) {
                $line = array_shift($queue); // Take next
                if (empty(trim($line))) continue;

                $clientId = trim($line);

                if (!is_numeric($clientId)) {
                    logMessage("Skipping invalid line: $line");
                    continue;
                }

                $clientId = (int)$clientId;
                logMessage("Processing ID from file: $clientId");

                try {
                    $clientDataResponse = $ucrmApi->get("clients/$clientId");
                    $currentAttributes = $clientDataResponse['attributes'] ?? [];
                    $currentPass = null;
                    foreach ($currentAttributes as $attr) {
                        if ($attr['key'] === $customAttributeKey) {
                            $currentPass = $attr['value'];
                            break;
                        }
                    }

                    $fixResult = fixClientData($clientId, $ucrmApi, $unmsClient, $attributeIds, $config, $currentPass);
                    $newPass = $fixResult['password'];
                    $siteInfo = $fixResult['siteName'] ? " | Site: {$fixResult['siteName']}" : '';
                    $devInfo = $fixResult['deviceInfo'] ? " | Antena: {$fixResult['deviceInfo']}" : '';
                    logMessage("Client $clientId result: $newPass$siteInfo$devInfo");
                } catch (\Exception $e) {
                    logMessage("Error processing Client $clientId: " . $e->getMessage());
                }

                // Rewrite file
                $validLines =  $queue;
                if ($header) {
                    array_unshift($validLines, $header);
                }
                $rewriteFile($validLines);
            }

            logMessage("File processing complete.");
            exit(0);
        }
    }

    if ($fixLimit > 0) {
        logMessage("Fix mode enabled. Limit: " . ($fixOption === 'all' ? 'ALL' : $fixLimit));
    }


    // 1. Fetch all clients
    logMessage("Fetching clients...");
    $clients = fetchAllClients($client);
    logMessage("Total active clients fetched: " . count($clients));

    // 2. Filter & Fix
    $results = [];
    $fixedCount = 0;

    foreach ($clients as $clientData) {
        $password = null;
        $attributes = $clientData['attributes'] ?? [];

        foreach ($attributes as $attr) {
            if ($attr['key'] === $customAttributeKey) {
                $password = $attr['value'];
                break;
            }
        }

        // Validation Logic
        $isValid = true;
        $reason = "";

        // Detect explicit error states that should be retried (e.g. "⚠️ Error Regenerating...")
        $isError = (stripos((string)$password, 'Error') !== false);

        $isManaged = (strpos((string)$password, 'Servicio') !== false || strpos((string)$password, '⚠️') !== false);

        if (empty($password)) {
            $isValid = false;
            $reason = "Missing";
        } elseif ($isError) {
            $isValid = false;
            $reason = "Error (Retry)";
        } elseif (!$isManaged) {
            $len = strlen($password);
            if ($len < 8 || $len > 20) {
                $isValid = false;
                $reason = "Length ($len)";
            } elseif (strpos($password, ' ') !== false) {
                $isValid = false;
                $reason = "Spaces";
            }
        }


        if (!$isValid) {
            $newPassword = "";
            $actionTaken = "None";

            // Fix Logic
            if ($fixLimit > 0 && $fixedCount < $fixLimit) {
                logMessage("Fixing client {$clientData['id']}...");
                $fixResult = fixClientData($clientData['id'], $ucrmApi, $unmsClient, $attributeIds, $config, $password);
                $newPassword = $fixResult['password'];

                // Basic check if fix resulted in a change/value
                if ($fixResult['changed'] ?? false) {
                    $actionTaken = "Updated";
                    $fixedCount++;
                } else {
                    $actionTaken = "Attempted (No Change/Error)";
                }
            }

            $results[] = [
                'clientId' => $clientData['id'],
                'name' => ($clientData['firstName'] ?? '') . ' ' . ($clientData['lastName'] ?? ''),
                'userIdent' => $clientData['userIdent'] ?? '',
                'currentPassword' => $password ?? '',
                'action' => $actionTaken,
                'newPassword' => $newPassword
            ];
        }
    }

    // 3. Output CSV
    logMessage("Found " . count($results) . " clients with invalid passwords.");
    if ($fixLimit > 0) {
        logMessage("Fixed $fixedCount clients.");
    }

    echo "clientId,name,userIdent,currentPassword,action,newPassword\n";

    foreach ($results as $row) {
        echo sprintf(
            "%s,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
            $row['clientId'],
            str_replace('"', '""', $row['name']),
            str_replace('"', '""', $row['userIdent']),
            str_replace('"', '""', $row['currentPassword']),
            $row['action'],
            str_replace('"', '""', $row['newPassword'])
        );
    }

    logMessage("Done.");
}
