<?php

namespace MintyPHP\Tests\Service\Settings;

use MintyPHP\Repository\Settings\SettingRepositoryInterface;
use MintyPHP\Service\Settings\SettingsLoginPersistenceGateway;
use MintyPHP\Service\Settings\SettingsMetadataGateway;
use MintyPHP\Service\Settings\SettingsSessionGateway;
use PHPUnit\Framework\TestCase;

final class SettingsSessionPolicyGatewayTest extends TestCase
{
    public function testSessionTimeoutsUseFallbacksWhenMissing(): void
    {
        $gateway = $this->newSessionGateway(null, null);

        self::assertSame(30, $gateway->getSessionIdleTimeoutMinutes());
        self::assertSame(8, $gateway->getSessionAbsoluteTimeoutHours());
    }

    public function testSessionTimeoutsUseStoredValuesWhenValid(): void
    {
        $gateway = $this->newSessionGateway('15', '12');

        self::assertSame(15, $gateway->getSessionIdleTimeoutMinutes());
        self::assertSame(12, $gateway->getSessionAbsoluteTimeoutHours());
        self::assertSame(900, $gateway->getSessionIdleTimeoutSeconds());
        self::assertSame(43200, $gateway->getSessionAbsoluteTimeoutSeconds());
    }

    public function testSessionTimeoutsFallbackWhenOutOfBounds(): void
    {
        $gateway = $this->newSessionGateway('2', '100');

        self::assertSame(30, $gateway->getSessionIdleTimeoutMinutes());
        self::assertSame(8, $gateway->getSessionAbsoluteTimeoutHours());
    }

    public function testSessionSettersRejectInvalidValues(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->expects($this->never())->method('set');

        $gateway = new SettingsSessionGateway(new SettingsMetadataGateway($settings));
        self::assertFalse($gateway->setSessionIdleTimeoutMinutes(2));
        self::assertFalse($gateway->setSessionIdleTimeoutMinutes(2000));
        self::assertFalse($gateway->setSessionAbsoluteTimeoutHours(0));
    }

    public function testAutoRememberDefaultUsesExpectedBooleanNormalization(): void
    {
        $metadata = $this->createMock(SettingsMetadataGateway::class);
        $metadata->method('getValue')->willReturnMap([
            ['microsoft_auto_remember_default', null],
            ['microsoft_auto_remember_default', '1'],
        ]);

        $gateway = new SettingsLoginPersistenceGateway($metadata);
        self::assertFalse($gateway->isMicrosoftAutoRememberDefault());

        $metadataTrue = $this->createMock(SettingsMetadataGateway::class);
        $metadataTrue->expects($this->any())->method('getValue')->with('microsoft_auto_remember_default')->willReturn('1');
        $gatewayTrue = new SettingsLoginPersistenceGateway($metadataTrue);
        self::assertTrue($gatewayTrue->isMicrosoftAutoRememberDefault());
    }

    public function testAutoRememberSetterPersistsBooleanFlag(): void
    {
        $metadata = $this->createMock(SettingsMetadataGateway::class);
        $metadata->expects($this->exactly(2))
            ->method('set')
            ->willReturnCallback(static function (string $key, ?string $value): bool {
                return $key === 'microsoft_auto_remember_default'
                    && in_array($value, ['0', '1'], true);
            });

        $gateway = new SettingsLoginPersistenceGateway($metadata);
        self::assertTrue($gateway->setMicrosoftAutoRememberDefault(true));
        self::assertTrue($gateway->setMicrosoftAutoRememberDefault(false));
    }

    public function testRememberTokenLifetimeUsesFallbackWhenMissingOrInvalid(): void
    {
        $metadataMissing = $this->createMock(SettingsMetadataGateway::class);
        $metadataMissing->expects($this->any())->method('getInt')->with('remember_token_lifetime_days')->willReturn(null);
        $gatewayMissing = new SettingsLoginPersistenceGateway($metadataMissing);
        self::assertSame(30, $gatewayMissing->getRememberTokenLifetimeDays());
        self::assertSame(30 * 86400, $gatewayMissing->getRememberTokenLifetimeSeconds());

        $metadataInvalid = $this->createMock(SettingsMetadataGateway::class);
        $metadataInvalid->expects($this->any())->method('getInt')->with('remember_token_lifetime_days')->willReturn(500);
        $gatewayInvalid = new SettingsLoginPersistenceGateway($metadataInvalid);
        self::assertSame(30, $gatewayInvalid->getRememberTokenLifetimeDays());
    }

    public function testRememberTokenLifetimeAcceptsValidRangeAndComputesSeconds(): void
    {
        $metadata = $this->createMock(SettingsMetadataGateway::class);
        $metadata->expects($this->any())->method('getInt')->with('remember_token_lifetime_days')->willReturn(7);
        $metadata->expects($this->once())
            ->method('set')
            ->with('remember_token_lifetime_days', '14', $this->anything())
            ->willReturn(true);

        $gateway = new SettingsLoginPersistenceGateway($metadata);
        self::assertSame(7, $gateway->getRememberTokenLifetimeDays());
        self::assertSame(7 * 86400, $gateway->getRememberTokenLifetimeSeconds());
        self::assertTrue($gateway->setRememberTokenLifetimeDays(14));
    }

    public function testRememberTokenLifetimeRejectsInvalidRangeAndNullUsesDefault(): void
    {
        $rejectingMetadata = $this->createMock(SettingsMetadataGateway::class);
        $rejectingMetadata->expects($this->never())->method('set');
        $rejectingGateway = new SettingsLoginPersistenceGateway($rejectingMetadata);
        self::assertFalse($rejectingGateway->setRememberTokenLifetimeDays(0));
        self::assertFalse($rejectingGateway->setRememberTokenLifetimeDays(400));

        $defaultingMetadata = $this->createMock(SettingsMetadataGateway::class);
        $defaultingMetadata->expects($this->once())
            ->method('set')
            ->with('remember_token_lifetime_days', '30', $this->anything())
            ->willReturn(true);
        $defaultingGateway = new SettingsLoginPersistenceGateway($defaultingMetadata);
        self::assertTrue($defaultingGateway->setRememberTokenLifetimeDays(null));
    }

    private function newSessionGateway(?string $idle, ?string $absolute): SettingsSessionGateway
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnCallback(
            static function (string $key) use ($idle, $absolute): ?string {
                return match ($key) {
                    'session_idle_timeout_minutes' => $idle,
                    'session_absolute_timeout_hours' => $absolute,
                    default => null,
                };
            }
        );

        return new SettingsSessionGateway(new SettingsMetadataGateway($settings));
    }
}
