<?php
declare(strict_types=1);

/**
 * Audita plugins/apps locales por fecha de modificacion y huella de ficheros.
 *
 * Uso:
 *   php tools/audit_plugin_freshness.php
 *   php tools/audit_plugin_freshness.php --json
 *   php tools/audit_plugin_freshness.php --csv=var/plugin-freshness.csv
 *   php tools/audit_plugin_freshness.php --root=/var/www/html/intranet
 *   php tools/audit_plugin_freshness.php --hash
 *   php tools/audit_plugin_freshness.php --hash --only=claves,permisos
 */

function audit_arg_value(array $args, string $name, ?string $default = null): ?string
{
    $prefix = $name . '=';
    foreach ($args as $arg) {
        if ($arg === $name) {
            return '';
        }
        if (strpos($arg, $prefix) === 0) {
            return substr($arg, strlen($prefix));
        }
    }
    return $default;
}

function audit_has_arg(array $args, string $name): bool
{
    foreach ($args as $arg) {
        if ($arg === $name || strpos($arg, $name . '=') === 0) {
            return true;
        }
    }
    return false;
}

function audit_only_allows(array $onlyIds, string ...$ids): bool
{
    if (empty($onlyIds)) {
        return true;
    }

    foreach ($ids as $id) {
        $id = strtolower(trim($id));
        if ($id !== '' && isset($onlyIds[$id])) {
            return true;
        }
    }

    return false;
}

function audit_rel(string $path, string $root): string
{
    $path = str_replace('\\', '/', $path);
    $root = rtrim(str_replace('\\', '/', $root), '/');
    if (strpos($path, $root . '/') === 0) {
        return substr($path, strlen($root) + 1);
    }
    return $path;
}

function audit_read_json_file(string $path): array
{
    if (!is_file($path)) {
        return [];
    }

    $data = json_decode((string) @file_get_contents($path), true);
    return is_array($data) ? $data : [];
}

function audit_should_skip_path(string $relativePath): bool
{
    $relativePath = trim(str_replace('\\', '/', $relativePath), '/');
    if ($relativePath === '') {
        return false;
    }

    $parts = explode('/', $relativePath);
    $skipNames = [
        '.git' => true,
        '.svn' => true,
        '__pycache__' => true,
        'node_modules' => true,
        'vendor' => true,
        'uploads' => true,
        'upload' => true,
        'cache' => true,
        'tmp' => true,
        'temp' => true,
        'logs' => true,
        'log' => true,
        'output' => true,
        'backups' => true,
        'backup' => true,
        'data' => true,
        'models' => true,
        '.venv' => true,
        'venv' => true,
        'env' => true,
    ];

    foreach ($parts as $part) {
        if (isset($skipNames[strtolower($part)])) {
            return true;
        }
    }

    $base = strtolower(basename($relativePath));
    if ($base === 'thumbs.db' || $base === '.ds_store') {
        return true;
    }

    return false;
}

function audit_scan_dir(string $dir, bool $withHash = true): array
{
    $result = [
        'exists' => is_dir($dir),
        'file_count' => 0,
        'latest_mtime' => 0,
        'latest_file' => '',
        'fingerprint' => '',
    ];

    if (!is_dir($dir)) {
        return $result;
    }

    $dir = rtrim(str_replace('\\', '/', realpath($dir) ?: $dir), '/');
    $fileHashes = [];

    $stack = [$dir];
    while (!empty($stack)) {
        $currentDir = array_pop($stack);
        if (!is_string($currentDir) || !is_dir($currentDir)) {
            continue;
        }

        $items = @scandir($currentDir);
        if (!is_array($items)) {
            continue;
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $currentDir . '/' . $item;
            $rel = substr($path, strlen($dir) + 1);
            if (audit_should_skip_path($rel)) {
                continue;
            }

            if (is_dir($path)) {
                $stack[] = $path;
                continue;
            }
            if (!is_file($path)) {
                continue;
            }

            $mtimeRaw = @filemtime($path);
            $mtime = is_int($mtimeRaw) ? $mtimeRaw : 0;
            $result['file_count']++;
            if ($mtime > (int) $result['latest_mtime']) {
                $result['latest_mtime'] = $mtime;
                $result['latest_file'] = $rel;
            }

            if ($withHash) {
                $hash = @hash_file('sha256', $path);
                if (is_string($hash) && $hash !== '') {
                    $fileHashes[$rel] = $hash;
                }
            }
        }
    }

    if ($withHash) {
        ksort($fileHashes, SORT_STRING);
        $result['fingerprint'] = hash('sha256', json_encode($fileHashes, JSON_UNESCAPED_SLASHES));
    }

    return $result;
}

function audit_format_time(int $mtime): string
{
    return $mtime > 0 ? date('Y-m-d H:i:s', $mtime) : '';
}

function audit_row_latest_mtime(array $row): int
{
    return max((int) ($row['plugin_latest_mtime'] ?? 0), (int) ($row['app_latest_mtime'] ?? 0));
}

function audit_print_table(array $rows, array $meta): void
{
    echo 'Intranet: ' . ($meta['root'] ?? '') . PHP_EOL;
    echo 'Generado: ' . ($meta['generated_at'] ?? '') . PHP_EOL;
    echo str_repeat('-', 132) . PHP_EOL;
    printf(
        "%-10s %-28s %-12s %-19s %-45s %-12s %-10s\n",
        'Tipo',
        'ID',
        'Version',
        'Ultima fecha',
        'Ultimo fichero',
        'Origen',
        'Hash'
    );
    echo str_repeat('-', 132) . PHP_EOL;

    foreach ($rows as $row) {
        $latestMtime = audit_row_latest_mtime($row);
        $latestFile = (string) ($row['plugin_latest_file'] ?? '');
        $origin = 'plugin';
        if ((int) ($row['app_latest_mtime'] ?? 0) > (int) ($row['plugin_latest_mtime'] ?? 0)) {
            $latestFile = (string) ($row['app_latest_file'] ?? '');
            $origin = 'apps';
        }

        printf(
            "%-10s %-28s %-12s %-19s %-45s %-12s %-10s\n",
            substr((string) ($row['kind'] ?? ''), 0, 10),
            substr((string) ($row['id'] ?? ''), 0, 28),
            substr((string) ($row['version'] ?? ''), 0, 12),
            audit_format_time($latestMtime),
            substr($latestFile, 0, 45),
            $origin,
            substr((string) ($row['fingerprint'] ?? ''), 0, 10)
        );
    }
}

function audit_write_csv(string $path, array $rows): void
{
    $dir = dirname($path);
    if ($dir !== '' && $dir !== '.' && !is_dir($dir)) {
        @mkdir($dir, 0775, true);
    }

    $fh = fopen($path, 'wb');
    if ($fh === false) {
        throw new RuntimeException('No se pudo escribir el CSV: ' . $path);
    }

    fputcsv($fh, [
        'kind',
        'id',
        'name',
        'version',
        'plugin_path',
        'plugin_latest_at',
        'plugin_latest_file',
        'app_path',
        'app_latest_at',
        'app_latest_file',
        'latest_at',
        'fingerprint',
    ]);

    foreach ($rows as $row) {
        fputcsv($fh, [
            $row['kind'] ?? '',
            $row['id'] ?? '',
            $row['name'] ?? '',
            $row['version'] ?? '',
            $row['plugin_path'] ?? '',
            audit_format_time((int) ($row['plugin_latest_mtime'] ?? 0)),
            $row['plugin_latest_file'] ?? '',
            $row['app_path'] ?? '',
            audit_format_time((int) ($row['app_latest_mtime'] ?? 0)),
            $row['app_latest_file'] ?? '',
            audit_format_time(audit_row_latest_mtime($row)),
            $row['fingerprint'] ?? '',
        ]);
    }

    fclose($fh);
}

$args = array_slice($argv ?? [], 1);
$root = audit_arg_value($args, '--root', dirname(__DIR__));
$root = rtrim(str_replace('\\', '/', realpath((string) $root) ?: (string) $root), '/');
$jsonMode = audit_has_arg($args, '--json');
$csvPath = audit_arg_value($args, '--csv', null);
$withHash = audit_has_arg($args, '--hash') && !audit_has_arg($args, '--no-hash');
$debug = audit_has_arg($args, '--debug');
$onlyRaw = (string) audit_arg_value($args, '--only', '');
$onlyIds = [];
foreach (preg_split('/\s*,\s*/', $onlyRaw, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $onlyId) {
    $onlyIds[strtolower($onlyId)] = true;
}

$pluginsDir = $root . '/plugins';
$appsDir = $root . '/apps';
$rows = [];
$pluginAppFolders = [];

if (is_dir($pluginsDir)) {
    $items = @scandir($pluginsDir) ?: [];
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') {
            continue;
        }

        $pluginDir = $pluginsDir . '/' . $item;
        if (!is_dir($pluginDir)) {
            continue;
        }

        $manifest = audit_read_json_file($pluginDir . '/plugin.json');
        $id = (string) ($manifest['id'] ?? $item);
        $appFolder = (string) ($manifest['app_folder'] ?? ($manifest['app']['folder'] ?? ''));
        if (!audit_only_allows($onlyIds, $id, $item, $appFolder)) {
            continue;
        }
        if ($appFolder !== '') {
            $pluginAppFolders[$appFolder] = true;
        }

        if ($debug) {
            fwrite(STDERR, 'Escaneando plugin: ' . $item . PHP_EOL);
        }
        $pluginScan = audit_scan_dir($pluginDir, $withHash);
        $appDir = $appFolder !== '' ? $appsDir . '/' . $appFolder : '';
        if ($debug && $appDir !== '') {
            fwrite(STDERR, 'Escaneando app enlazada: ' . $appFolder . PHP_EOL);
        }
        $appScan = $appDir !== '' ? audit_scan_dir($appDir, $withHash) : audit_scan_dir('', false);

        $fingerprintParts = [
            'plugin' => $pluginScan['fingerprint'] ?? '',
            'app' => $appScan['fingerprint'] ?? '',
        ];

        $rows[] = [
            'kind' => 'plugin',
            'id' => $id,
            'name' => (string) ($manifest['name'] ?? $id),
            'version' => (string) ($manifest['version'] ?? ''),
            'plugin_path' => audit_rel($pluginDir, $root),
            'plugin_latest_mtime' => (int) ($pluginScan['latest_mtime'] ?? 0),
            'plugin_latest_file' => (string) ($pluginScan['latest_file'] ?? ''),
            'plugin_file_count' => (int) ($pluginScan['file_count'] ?? 0),
            'app_path' => $appDir !== '' && is_dir($appDir) ? audit_rel($appDir, $root) : '',
            'app_latest_mtime' => (int) ($appScan['latest_mtime'] ?? 0),
            'app_latest_file' => (string) ($appScan['latest_file'] ?? ''),
            'app_file_count' => (int) ($appScan['file_count'] ?? 0),
            'app_newer_than_plugin' => (int) ($appScan['latest_mtime'] ?? 0) > (int) ($pluginScan['latest_mtime'] ?? 0),
            'fingerprint' => $withHash ? hash('sha256', json_encode($fingerprintParts, JSON_UNESCAPED_SLASHES)) : '',
        ];
    }
}

if (is_dir($appsDir)) {
    $items = @scandir($appsDir) ?: [];
    foreach ($items as $item) {
        if ($item === '.' || $item === '..' || isset($pluginAppFolders[$item])) {
            continue;
        }

        $appDir = $appsDir . '/' . $item;
        if (!is_dir($appDir)) {
            continue;
        }

        $manifest = audit_read_json_file($appDir . '/app.json');
        $id = (string) ($manifest['plugin_id'] ?? ($manifest['source_app'] ?? $item));
        if (!audit_only_allows($onlyIds, $id, $item)) {
            continue;
        }
        if ($debug) {
            fwrite(STDERR, 'Escaneando app suelta: ' . $item . PHP_EOL);
        }
        $appScan = audit_scan_dir($appDir, $withHash);
        $rows[] = [
            'kind' => 'app-suelta',
            'id' => $id,
            'name' => (string) ($manifest['name'] ?? $id),
            'version' => (string) ($manifest['version'] ?? ''),
            'plugin_path' => '',
            'plugin_latest_mtime' => 0,
            'plugin_latest_file' => '',
            'plugin_file_count' => 0,
            'app_path' => audit_rel($appDir, $root),
            'app_latest_mtime' => (int) ($appScan['latest_mtime'] ?? 0),
            'app_latest_file' => (string) ($appScan['latest_file'] ?? ''),
            'app_file_count' => (int) ($appScan['file_count'] ?? 0),
            'app_newer_than_plugin' => true,
            'fingerprint' => (string) ($appScan['fingerprint'] ?? ''),
        ];
    }
}

usort($rows, static function (array $a, array $b): int {
    $cmp = audit_row_latest_mtime($b) <=> audit_row_latest_mtime($a);
    if ($cmp !== 0) {
        return $cmp;
    }
    return strcmp((string) ($a['id'] ?? ''), (string) ($b['id'] ?? ''));
});

$meta = [
    'root' => $root,
    'generated_at' => date('c'),
    'hash_enabled' => $withHash,
    'only' => array_keys($onlyIds),
    'excluded_dirs' => [
        'uploads',
        'cache',
        'tmp',
        'logs',
        'output',
        'backups',
        'vendor',
        'node_modules',
        'data',
        'models',
        '.venv',
        'venv',
        'env',
    ],
];

if ($csvPath !== null) {
    $csvPath = $csvPath !== '' ? (string) $csvPath : ($root . '/var/plugin-freshness.csv');
    if (!preg_match('~^(?:[A-Za-z]:)?[\\\\/]~', $csvPath)) {
        $csvPath = $root . '/' . ltrim(str_replace('\\', '/', $csvPath), '/');
    }
    audit_write_csv($csvPath, $rows);
    fwrite(STDERR, 'CSV escrito en: ' . $csvPath . PHP_EOL);
}

if ($jsonMode) {
    echo json_encode(['meta' => $meta, 'items' => $rows], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

audit_print_table($rows, $meta);
