<?php

namespace MintyPHP\Tests\Service\System;

use MintyPHP\Repository\System\SystemHealthRepositoryInterface;
use MintyPHP\Service\System\SystemHealthService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class SystemHealthServiceTest extends TestCase
{
    private SystemHealthRepositoryInterface&MockObject $repository;
    private SystemHealthService $service;

    protected function setUp(): void
    {
        $this->repository = $this->createMock(SystemHealthRepositoryInterface::class);
        $this->service = new SystemHealthService($this->repository);
    }

    public function testCheckDatabaseReturnsOkWhenConnected(): void
    {
        $this->repository->expects($this->once())
            ->method('checkDatabaseConnectivity')
            ->willReturn(true);

        $result = $this->service->checkDatabase();

        self::assertSame('ok', $result['status']);
        self::assertSame('Database connectivity', $result['name']);
        self::assertSame('Database', $result['label']);
        self::assertSame('Connection established', $result['description']);
    }

    public function testCheckDatabaseReturnsFailWhenDisconnected(): void
    {
        $this->repository->expects($this->once())
            ->method('checkDatabaseConnectivity')
            ->willReturn(false);

        $result = $this->service->checkDatabase();

        self::assertSame('fail', $result['status']);
        self::assertSame('Database', $result['label']);
        self::assertSame('Database did not return the expected response', $result['description']);
    }

    public function testCheckDatabaseReturnsFailOnException(): void
    {
        $this->repository->expects($this->once())
            ->method('checkDatabaseConnectivity')
            ->willThrowException(new \RuntimeException('Connection refused'));

        $result = $this->service->checkDatabase();

        self::assertSame('fail', $result['status']);
        self::assertSame('check failed', $result['message']);
    }

    public function testCheckDatabaseSchemaReturnsOkWhenAllTablesPresent(): void
    {
        $this->repository->expects($this->once())
            ->method('listPresentTables')
            ->willReturn([
                'users', 'roles', 'permissions', 'user_roles', 'role_permissions',
                'tenants', 'departments', 'settings', 'scheduler_runtime_status',
            ]);

        $result = $this->service->checkDatabaseSchema();

        self::assertSame('ok', $result['status']);
    }

    public function testCheckDatabaseSchemaReturnsFailWhenTablesMissing(): void
    {
        $this->repository->expects($this->once())
            ->method('listPresentTables')
            ->willReturn(['users', 'roles']);

        $result = $this->service->checkDatabaseSchema();

        self::assertSame('fail', $result['status']);
        self::assertStringContainsString('missing tables', $result['message']);
    }

    public function testCheckRbacBaselineReturnsOkWhenAllPermissionsActive(): void
    {
        $this->repository->expects($this->once())
            ->method('listActivePermissionKeys')
            ->willReturn([
                'users.view', 'tenants.view', 'departments.view',
                'roles.view', 'permissions.view', 'settings.view',
            ]);

        $result = $this->service->checkRbacBaseline();

        self::assertSame('ok', $result['status']);
    }

    public function testCheckRbacBaselineReturnsFailWhenPermissionsMissing(): void
    {
        $this->repository->expects($this->once())
            ->method('listActivePermissionKeys')
            ->willReturn(['users.view']);

        $result = $this->service->checkRbacBaseline();

        self::assertSame('fail', $result['status']);
        self::assertStringContainsString('missing active permissions', $result['message']);
    }

    public function testCheckAdminRoleAssignmentReturnsOkWhenAdminsExist(): void
    {
        $this->repository->expects($this->once())
            ->method('countAdminUsers')
            ->willReturn(2);

        $result = $this->service->checkAdminRoleAssignment();

        self::assertSame('ok', $result['status']);
        self::assertStringContainsString('2 admin user(s)', $result['message']);
    }

    public function testCheckAdminRoleAssignmentReturnsFailWhenNoAdmins(): void
    {
        $this->repository->expects($this->once())
            ->method('countAdminUsers')
            ->willReturn(0);

        $result = $this->service->checkAdminRoleAssignment();

        self::assertSame('fail', $result['status']);
    }

    public function testCheckSchedulerHeartbeatReturnsWarnWhenNoStatusRow(): void
    {
        $this->repository->expects($this->once())
            ->method('getSchedulerStatus')
            ->willReturn(null);

        $result = $this->service->checkSchedulerHeartbeat();

        self::assertSame('warn', $result['status']);
    }

    public function testCheckSchedulerHeartbeatReturnsOkWhenRecent(): void
    {
        $this->repository->expects($this->once())
            ->method('getSchedulerStatus')
            ->willReturn([
                'last_heartbeat_at' => gmdate('Y-m-d H:i:s', time() - 10),
                'last_result' => 'ok',
                'last_error_code' => '',
            ]);

        $result = $this->service->checkSchedulerHeartbeat();

        self::assertSame('ok', $result['status']);
    }

    public function testCheckSchedulerHeartbeatReturnsWarnWhenStale(): void
    {
        $this->repository->expects($this->once())
            ->method('getSchedulerStatus')
            ->willReturn([
                'last_heartbeat_at' => gmdate('Y-m-d H:i:s', time() - 600),
                'last_result' => 'ok',
                'last_error_code' => '',
            ]);

        $result = $this->service->checkSchedulerHeartbeat();

        self::assertSame('warn', $result['status']);
    }

    public function testRunAllReturnsAllSixChecks(): void
    {
        $this->repository->method('checkDatabaseConnectivity')->willReturn(true);
        $this->repository->method('listPresentTables')->willReturn([
            'users', 'roles', 'permissions', 'user_roles', 'role_permissions',
            'tenants', 'departments', 'settings', 'scheduler_runtime_status',
        ]);
        $this->repository->method('listActivePermissionKeys')->willReturn([
            'users.view', 'tenants.view', 'departments.view',
            'roles.view', 'permissions.view', 'settings.view',
        ]);
        $this->repository->method('countAdminUsers')->willReturn(1);
        $this->repository->method('getSchedulerStatus')->willReturn(null);

        $results = $this->service->runAll();

        self::assertCount(6, $results);
        foreach ($results as $check) {
            self::assertArrayHasKey('status', $check);
            self::assertArrayHasKey('name', $check);
            self::assertArrayHasKey('message', $check);
            self::assertArrayHasKey('label', $check);
            self::assertArrayHasKey('description', $check);
            self::assertNotSame('', $check['label']);
            self::assertNotSame('', $check['description']);
        }
    }
}
