<?php

namespace MintyPHP\Service\System;

use MintyPHP\Repository\System\SystemHealthRepositoryInterface;
use MintyPHP\Service\Access\PermissionService;

class SystemHealthService
{
    private const REQUIRED_TABLES = [
        'users',
        'roles',
        'permissions',
        'user_roles',
        'role_permissions',
        'tenants',
        'departments',
        'settings',
        'scheduler_runtime_status',
    ];

    private const REQUIRED_PERMISSIONS = [
        PermissionService::USERS_VIEW,
        PermissionService::TENANTS_VIEW,
        PermissionService::DEPARTMENTS_VIEW,
        PermissionService::ROLES_VIEW,
        PermissionService::PERMISSIONS_VIEW,
        PermissionService::SETTINGS_VIEW,
    ];

    private const SCHEDULER_STALE_THRESHOLD_SECONDS = 300;

    public function __construct(
        private readonly SystemHealthRepositoryInterface $repository
    ) {
    }

    /**
     * @return list<array{status: string, name: string, message: string, label: string, description: string}>
     */
    public function runAll(): array
    {
        return [
            $this->checkDatabase(),
            $this->checkDatabaseSchema(),
            $this->checkStorageWriteability(),
            $this->checkRbacBaseline(),
            $this->checkAdminRoleAssignment(),
            $this->checkSchedulerHeartbeat(),
        ];
    }

    /**
     * @return array{status: string, name: string, message: string, label: string, description: string}
     */
    public function checkDatabase(): array
    {
        $label = 'Database';
        $okDescription = 'Connection established';

        try {
            $ok = $this->repository->checkDatabaseConnectivity();

            return [
                'status' => $ok ? 'ok' : 'fail',
                'name' => 'Database connectivity',
                'message' => $ok ? 'connection established' : 'select 1 did not return expected value',
                'label' => $label,
                'description' => $ok ? $okDescription : 'Database did not return the expected response',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'name' => 'Database connectivity',
                'message' => 'check failed',
                'label' => $label,
                'description' => 'Database check could not be executed',
            ];
        }
    }

    /**
     * @return array{status: string, name: string, message: string, label: string, description: string}
     */
    public function checkDatabaseSchema(): array
    {
        $label = 'Database schema';

        try {
            $present = $this->repository->listPresentTables(self::REQUIRED_TABLES);
            sort($present, SORT_STRING);
            $missing = array_values(array_diff(self::REQUIRED_TABLES, $present));

            if ($missing) {
                return [
                    'status' => 'fail',
                    'name' => 'Database schema',
                    'message' => 'missing tables: ' . implode(', ', $missing),
                    'label' => $label,
                    'description' => 'Required core tables are missing',
                ];
            }

            return [
                'status' => 'ok',
                'name' => 'Database schema',
                'message' => sprintf('%d core tables present', count(self::REQUIRED_TABLES)),
                'label' => $label,
                'description' => 'All required core tables are present',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'name' => 'Database schema',
                'message' => 'check failed',
                'label' => $label,
                'description' => 'Schema check could not be executed',
            ];
        }
    }

    /**
     * @return array{status: string, name: string, message: string, label: string, description: string}
     */
    public function checkStorageWriteability(): array
    {
        $label = 'Storage';

        try {
            $storagePath = defined('APP_STORAGE_PATH') && APP_STORAGE_PATH
                ? rtrim((string) APP_STORAGE_PATH, '/')
                : rtrim(dirname(__DIR__, 3) . '/storage', '/');

            if (!is_dir($storagePath)) {
                return [
                    'status' => 'fail',
                    'name' => 'Storage writeability',
                    'message' => 'storage directory not found',
                    'label' => $label,
                    'description' => 'Storage directory is missing',
                ];
            }
            if (!is_writable($storagePath)) {
                return [
                    'status' => 'fail',
                    'name' => 'Storage writeability',
                    'message' => 'storage directory not writable',
                    'label' => $label,
                    'description' => 'Storage directory is not writable',
                ];
            }

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

            return [
                'status' => 'ok',
                'name' => 'Storage writeability',
                'message' => 'storage path is writable',
                'label' => $label,
                'description' => 'Storage path is writable',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'name' => 'Storage writeability',
                'message' => 'check failed',
                'label' => $label,
                'description' => 'Storage check could not be executed',
            ];
        }
    }

    /**
     * @return array{status: string, name: string, message: string, label: string, description: string}
     */
    public function checkRbacBaseline(): array
    {
        $label = 'Permissions';

        try {
            $present = $this->repository->listActivePermissionKeys(self::REQUIRED_PERMISSIONS);
            sort($present, SORT_STRING);
            $missing = array_values(array_diff(self::REQUIRED_PERMISSIONS, $present));

            if ($missing) {
                return [
                    'status' => 'fail',
                    'name' => 'RBAC baseline',
                    'message' => 'missing active permissions: ' . implode(', ', $missing),
                    'label' => $label,
                    'description' => 'Baseline permissions are missing',
                ];
            }

            return [
                'status' => 'ok',
                'name' => 'RBAC baseline',
                'message' => sprintf('%d baseline permissions active', count(self::REQUIRED_PERMISSIONS)),
                'label' => $label,
                'description' => 'Baseline permissions are present',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'name' => 'RBAC baseline',
                'message' => 'check failed',
                'label' => $label,
                'description' => 'Permission check could not be executed',
            ];
        }
    }

    /**
     * @return array{status: string, name: string, message: string, label: string, description: string}
     */
    public function checkAdminRoleAssignment(): array
    {
        $label = 'Administrators';

        try {
            $count = $this->repository->countAdminUsers();

            if ($count <= 0) {
                return [
                    'status' => 'fail',
                    'name' => 'Admin role assignment',
                    'message' => 'no active user assigned to Admin/Administrator role',
                    'label' => $label,
                    'description' => 'No active user has the administrator role',
                ];
            }

            return [
                'status' => 'ok',
                'name' => 'Admin role assignment',
                'message' => sprintf('%d admin user(s) assigned', $count),
                'label' => $label,
                'description' => 'At least one administrator is active',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'name' => 'Admin role assignment',
                'message' => 'check failed',
                'label' => $label,
                'description' => 'Administrator check could not be executed',
            ];
        }
    }

    /**
     * @return array{status: string, name: string, message: string, label: string, description: string}
     */
    public function checkSchedulerHeartbeat(): array
    {
        $label = 'Scheduler';

        try {
            $status = $this->repository->getSchedulerStatus();

            if ($status === null) {
                return [
                    'status' => 'warn',
                    'name' => 'Scheduler heartbeat',
                    'message' => 'no scheduler runtime status row found yet',
                    'label' => $label,
                    'description' => 'Scheduler has not reported yet',
                ];
            }

            $heartbeat = $status['last_heartbeat_at'];
            $result = $status['last_result'];
            $errorCode = $status['last_error_code'];

            if ($heartbeat === '') {
                return [
                    'status' => 'warn',
                    'name' => 'Scheduler heartbeat',
                    'message' => 'scheduler heartbeat is empty',
                    'label' => $label,
                    'description' => 'Scheduler heartbeat is empty',
                ];
            }

            $seconds = max(0, time() - strtotime($heartbeat . ' UTC'));
            $detail = "last heartbeat {$seconds}s ago (result={$result}" . ($errorCode !== '' ? ", error={$errorCode}" : '') . ')';
            $stale = $seconds > self::SCHEDULER_STALE_THRESHOLD_SECONDS;

            return [
                'status' => $stale ? 'warn' : 'ok',
                'name' => 'Scheduler heartbeat',
                'message' => $detail,
                'label' => $label,
                'description' => $stale ? 'Scheduler heartbeat is outdated' : 'Scheduler is running on schedule',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'fail',
                'name' => 'Scheduler heartbeat',
                'message' => 'check failed',
                'label' => $label,
                'description' => 'Scheduler check could not be executed',
            ];
        }
    }
}
