#!/usr/bin/env php
<?php

declare(strict_types=1);

use MintyPHP\App\AppContainer;
use MintyPHP\App\Bootstrap\EnvValidator;
use MintyPHP\DB;
use MintyPHP\Http\RequestContext;
use MintyPHP\Service\Access\AuthorizationService;
use MintyPHP\Service\Access\PermissionService;
use MintyPHP\Service\Access\UiAccessService;
use MintyPHP\Service\Audit\SystemAuditService;
use MintyPHP\Service\Auth\AuthService;

chdir(__DIR__ . '/..');

error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
ini_set('display_errors', '0');

require 'vendor/autoload.php';
require 'config/config.php';
require 'lib/Support/helpers.php';

/** @var AppContainer $container */
$container = require 'lib/App/registerContainer.php';
setAppContainer($container);
RequestContext::start([
    'channel' => 'cli',
    'method' => 'CLI',
    'path' => 'bin/doctor.php',
]);
$startedAt = microtime(true);

/** @var list<array{status: string, name: string, message: string}> $results */
$results = [];
$failCount = 0;
$warnCount = 0;

/**
 * @param callable():array{status: string, message: string} $check
 */
$runCheck = static function (string $name, callable $check) use (&$results, &$failCount, &$warnCount): void {
    try {
        $result = $check();
    } catch (Throwable $exception) {
        $result = [
            'status' => 'fail',
            'message' => $exception->getMessage(),
        ];
    }

    $status = strtolower(trim((string) ($result['status'] ?? 'fail')));
    if (!in_array($status, ['ok', 'warn', 'fail'], true)) {
        $status = 'fail';
    }

    $message = trim((string) ($result['message'] ?? ''));
    if ($message === '') {
        $message = 'no details';
    }

    if ($status === 'fail') {
        $failCount++;
    } elseif ($status === 'warn') {
        $warnCount++;
    }

    $results[] = [
        'status' => $status,
        'name' => $name,
        'message' => $message,
    ];
};

$runCheck('Environment validation', static function (): array {
        EnvValidator::validate();
        return [
            'status' => 'ok',
            'message' => 'all required env keys and formats are valid',
        ];
    });

    $runCheck('App container bootstrap', static function () use ($container): array {
        if (!$container instanceof AppContainer) {
            return [
                'status' => 'fail',
                'message' => 'registerContainer.php did not return AppContainer',
            ];
        }

        app(AuthService::class);
        app(AuthorizationService::class);
        app(UiAccessService::class);

        return [
            'status' => 'ok',
            'message' => 'core services resolved successfully',
        ];
    });

    $runCheck('Database connectivity', static function (): array {
        $pong = DB::selectValue('select 1');
        if ((int) $pong !== 1) {
            return [
                'status' => 'fail',
                'message' => 'select 1 did not return expected value',
            ];
        }

        return [
            'status' => 'ok',
            'message' => 'connection established',
        ];
    });

    $runCheck('Database schema basics', static function (): array {
        $requiredTables = [
            'users',
            'roles',
            'permissions',
            'user_roles',
            'role_permissions',
            'tenants',
            'departments',
            'settings',
            'scheduler_runtime_status',
        ];

        $rows = DB::select(
            'select table_name from information_schema.tables where table_schema = database() and table_name in (???)',
            $requiredTables
        );

        $present = [];
        foreach ((array) $rows as $row) {
            $table = (string) (($row['tables']['table_name'] ?? $row['table_name'] ?? ''));
            if ($table !== '') {
                $present[] = $table;
            }
        }
        $present = array_values(array_unique($present));
        sort($present, SORT_STRING);

        $missing = array_values(array_diff($requiredTables, $present));
        if ($missing) {
            return [
                'status' => 'fail',
                'message' => 'missing tables: ' . implode(', ', $missing),
            ];
        }

        return [
            'status' => 'ok',
            'message' => sprintf('%d core tables present', count($requiredTables)),
        ];
    });

    $runCheck('Storage path writeability', static function (): array {
        $storagePath = defined('APP_STORAGE_PATH') && APP_STORAGE_PATH
            ? rtrim((string) APP_STORAGE_PATH, '/')
            : rtrim(dirname(__DIR__) . '/storage', '/');

        if (!is_dir($storagePath)) {
            return [
                'status' => 'fail',
                'message' => "storage directory not found: {$storagePath}",
            ];
        }
        if (!is_writable($storagePath)) {
            return [
                'status' => 'fail',
                'message' => "storage directory not writable: {$storagePath}",
            ];
        }

        $probeFile = $storagePath . '/.doctor-write-probe-' . uniqid('', true);
        $written = @file_put_contents($probeFile, 'ok');
        if ($written === false) {
            return [
                'status' => 'fail',
                'message' => "write probe failed in: {$storagePath}",
            ];
        }
        @unlink($probeFile);

        return [
            'status' => 'ok',
            'message' => "storage path is writable ({$storagePath})",
        ];
    });

    $runCheck('RBAC baseline permissions', static function (): array {
        $requiredPermissions = [
            PermissionService::USERS_VIEW,
            PermissionService::TENANTS_VIEW,
            PermissionService::DEPARTMENTS_VIEW,
            PermissionService::ROLES_VIEW,
            PermissionService::PERMISSIONS_VIEW,
            PermissionService::SETTINGS_VIEW,
        ];

        $rows = DB::select(
            'select `key` from permissions where active = 1 and `key` in (???)',
            $requiredPermissions
        );

        $present = [];
        foreach ((array) $rows as $row) {
            $key = (string) (($row['permissions']['key'] ?? $row['key'] ?? ''));
            if ($key !== '') {
                $present[] = $key;
            }
        }
        $present = array_values(array_unique($present));
        sort($present, SORT_STRING);

        $missing = array_values(array_diff($requiredPermissions, $present));
        if ($missing) {
            return [
                'status' => 'fail',
                'message' => 'missing active permissions: ' . implode(', ', $missing),
            ];
        }

        return [
            'status' => 'ok',
            'message' => sprintf('%d baseline permissions active', count($requiredPermissions)),
        ];
    });

    $runCheck('Admin role assignment', static function (): array {
        $count = (int) (DB::selectValue(
            'select count(distinct ur.user_id) from user_roles ur join roles r on r.id = ur.role_id and r.active = 1 where r.description in (?, ?) or r.id = 1',
            'Admin',
            'Administrator'
        ) ?? 0);

        if ($count <= 0) {
            return [
                'status' => 'fail',
                'message' => 'no active user assigned to Admin/Administrator role',
            ];
        }

        return [
            'status' => 'ok',
            'message' => sprintf('%d admin user(s) assigned', $count),
        ];
    });

$runCheck('Scheduler heartbeat', static function (): array {
        $row = DB::selectOne('select last_heartbeat_at, last_result, last_error_code from scheduler_runtime_status where id = 1 limit 1');
        $status = is_array($row) ? ($row['scheduler_runtime_status'] ?? $row) : null;
        if (!is_array($status)) {
            return [
                'status' => 'warn',
                'message' => 'no scheduler runtime status row found yet',
            ];
        }

        $heartbeat = trim((string) ($status['last_heartbeat_at'] ?? ''));
        $result = trim((string) ($status['last_result'] ?? 'unknown'));
        $errorCode = trim((string) ($status['last_error_code'] ?? ''));
        if ($heartbeat === '') {
            return [
                'status' => 'warn',
                'message' => 'scheduler heartbeat is empty',
            ];
        }

        $seconds = time() - strtotime($heartbeat . ' UTC');
        if ($seconds < 0) {
            $seconds = 0;
        }

        if ($seconds > 300) {
            return [
                'status' => 'warn',
                'message' => "last heartbeat {$seconds}s ago (result={$result}" . ($errorCode !== '' ? ", error={$errorCode}" : '') . ')',
            ];
        }

        return [
            'status' => 'ok',
            'message' => "last heartbeat {$seconds}s ago (result={$result}" . ($errorCode !== '' ? ", error={$errorCode}" : '') . ')',
        ];
    });

fwrite(STDOUT, "Minty Doctor Report\n");
fwrite(STDOUT, "===================\n");
foreach ($results as $item) {
    $statusLabel = strtoupper($item['status']);
    fwrite(STDOUT, sprintf("[%s] %s: %s\n", $statusLabel, $item['name'], $item['message']));
}

$okCount = count($results) - $warnCount - $failCount;
fwrite(STDOUT, sprintf("\nSummary: ok=%d warn=%d fail=%d\n", $okCount, $warnCount, $failCount));

$exitCode = $failCount > 0 ? 1 : 0;
$resultLabel = $failCount > 0 ? 'fail' : ($warnCount > 0 ? 'warn' : 'ok');

try {
    app(SystemAuditService::class)->record(
        'cli.command',
        $failCount > 0 ? 'failed' : 'success',
        [
            'metadata' => [
                'command' => 'bin/doctor.php',
                'exit_code' => $exitCode,
                'duration_ms' => max(0, (int) round((microtime(true) - $startedAt) * 1000)),
                'result' => $resultLabel,
                'warn_count' => $warnCount,
                'fail_count' => $failCount,
            ],
        ]
    );
} catch (Throwable) {
    // fail-open
}

DB::close();
exit($exitCode);
