<?php

namespace MintyPHP\Tests\Module\Helpdesk\Service;

use MintyPHP\Module\Helpdesk\Service\HelpdeskSettingsGateway;
use MintyPHP\Repository\Settings\SettingRepositoryInterface;
use MintyPHP\Service\Settings\SettingsCryptoGatewayInterface;
use MintyPHP\Service\Settings\SettingsMetadataGateway;
use PHPUnit\Framework\TestCase;

class HelpdeskSettingsGatewayTest extends TestCase
{
    private function createGateway(?SettingRepositoryInterface $settings = null, ?SettingsCryptoGatewayInterface $crypto = null): HelpdeskSettingsGateway
    {
        $settings = $settings ?? $this->createMock(SettingRepositoryInterface::class);
        $crypto = $crypto ?? $this->createMock(SettingsCryptoGatewayInterface::class);

        return new HelpdeskSettingsGateway(new SettingsMetadataGateway($settings), $crypto);
    }

    public function testGetAuthModeDefaultsToBasic(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturn(null);

        $gateway = $this->createGateway($settings);
        $this->assertSame('basic', $gateway->getAuthMode());
    }

    public function testGetAuthModeReturnsOAuth2WhenSet(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturn('oauth2');

        $gateway = $this->createGateway($settings);
        $this->assertSame('oauth2', $gateway->getAuthMode());
    }

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

        $gateway = $this->createGateway($settings);
        $this->assertFalse($gateway->setAuthMode('invalid'));
    }

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

        $gateway = $this->createGateway($settings);
        $this->assertFalse($gateway->setODataBaseUrl('http://insecure.example.com'));
    }

    public function testSetODataBaseUrlAcceptsHttps(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->expects($this->once())
            ->method('set')
            ->with(HelpdeskSettingsGateway::KEY_ODATA_BASE_URL, 'https://bc.example.com/OData', 'helpdesk.bc_odata_base_url')
            ->willReturn(true);

        $gateway = $this->createGateway($settings);
        $this->assertTrue($gateway->setODataBaseUrl('https://bc.example.com/OData'));
    }

    public function testGetBasicPasswordReturnsNullWhenDecryptFails(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturn('encrypted-value');

        $crypto = $this->createMock(SettingsCryptoGatewayInterface::class);
        $crypto->method('decryptString')->willThrowException(new \RuntimeException('bad key'));

        $gateway = $this->createGateway($settings, $crypto);
        $this->assertNull($gateway->getBasicPassword());
    }

    public function testSetBasicPasswordEncryptsAndPersists(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->expects($this->once())
            ->method('set')
            ->with(HelpdeskSettingsGateway::KEY_BASIC_PASSWORD_ENC, 'enc-pw', 'helpdesk.bc_basic_password_enc')
            ->willReturn(true);

        $crypto = $this->createMock(SettingsCryptoGatewayInterface::class);
        $crypto->expects($this->once())
            ->method('encryptString')
            ->with('my-password')
            ->willReturn('enc-pw');

        $gateway = $this->createGateway($settings, $crypto);
        $this->assertTrue($gateway->setBasicPassword('my-password'));
    }

    public function testSetBasicPasswordClearsWhenEmpty(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->expects($this->once())
            ->method('set')
            ->with(HelpdeskSettingsGateway::KEY_BASIC_PASSWORD_ENC, null, 'helpdesk.bc_basic_password_enc')
            ->willReturn(true);

        $crypto = $this->createMock(SettingsCryptoGatewayInterface::class);
        $crypto->expects($this->never())->method('encryptString');

        $gateway = $this->createGateway($settings, $crypto);
        $this->assertTrue($gateway->setBasicPassword(''));
    }

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

        $crypto = $this->createMock(SettingsCryptoGatewayInterface::class);
        $crypto->method('encryptString')->willThrowException(new \RuntimeException('encryption failed'));

        $gateway = $this->createGateway($settings, $crypto);
        $this->assertFalse($gateway->setBasicPassword('my-password'));
    }

    public function testBuildEntityUrlConstructsCorrectUrl(): void
    {
        $callMap = [
            [HelpdeskSettingsGateway::KEY_ODATA_BASE_URL, 'https://bc.example.com/ODataV4'],
            [HelpdeskSettingsGateway::KEY_COMPANY_NAME, 'Test Company'],
        ];

        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnCallback(static function (string $key) use ($callMap): ?string {
            foreach ($callMap as [$k, $v]) {
                if ($k === $key) {
                    return $v;
                }
            }

            return null;
        });

        $gateway = $this->createGateway($settings);
        $url = $gateway->buildEntityUrl('Integration_Customer_Card');
        $this->assertSame("https://bc.example.com/ODataV4/Company('Test%20Company')/Integration_Customer_Card", $url);
    }

    public function testValidateConfigurationReportsMissingBasicCredentials(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturn(null);

        $gateway = $this->createGateway($settings);
        $missing = $gateway->validateConfiguration();
        $this->assertNotEmpty($missing);
        $this->assertContains('BC Basic Auth User', $missing);
        $this->assertContains('BC Basic Auth Password', $missing);
    }

    public function testIsConfiguredReturnsTrueWhenBasicCredentialsPresent(): void
    {
        $callMap = [
            [HelpdeskSettingsGateway::KEY_AUTH_MODE, 'basic'],
            [HelpdeskSettingsGateway::KEY_BASIC_USER, 'testuser'],
            [HelpdeskSettingsGateway::KEY_BASIC_PASSWORD_ENC, 'encrypted'],
        ];

        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnCallback(static function (string $key) use ($callMap): ?string {
            foreach ($callMap as [$k, $v]) {
                if ($k === $key) {
                    return $v;
                }
            }

            return null;
        });

        $crypto = $this->createMock(SettingsCryptoGatewayInterface::class);
        $crypto->method('decryptString')->willReturn('decrypted-password');

        $gateway = $this->createGateway($settings, $crypto);
        $this->assertTrue($gateway->isConfigured());
    }

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

        $gateway = $this->createGateway($settings);
        $this->assertFalse($gateway->setOAuthTokenEndpoint('http://login.microsoftonline.com/token'));
    }

    public function testGetSystemRecommendationsConfigEnvelopeReturnsDefaultsWhenMissing(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnMap([
            [HelpdeskSettingsGateway::KEY_SYSTEM_RECOMMENDATIONS_CONFIG, null],
        ]);

        $gateway = $this->createGateway($settings);
        $envelope = $gateway->getSystemRecommendationsConfigEnvelope();

        $this->assertSame('default', $envelope['source']);
        $this->assertSame(5, $envelope['config']['max_items']);
        $this->assertTrue($envelope['config']['rules']['stale_open']['enabled']);
    }

    public function testGetSystemRecommendationsConfigEnvelopeFallsBackOnInvalidJson(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnMap([
            [HelpdeskSettingsGateway::KEY_SYSTEM_RECOMMENDATIONS_CONFIG, '{invalid'],
        ]);

        $gateway = $this->createGateway($settings);
        $envelope = $gateway->getSystemRecommendationsConfigEnvelope();

        $this->assertSame('fallback_invalid_json', $envelope['source']);
        $this->assertSame(5, $envelope['config']['max_items']);
    }

    public function testGetSystemRecommendationsConfigNormalizesValues(): void
    {
        $rawConfig = json_encode([
            'version' => 99,
            'max_items' => 99,
            'rules' => [
                'high_risk_aging' => [
                    'enabled' => true,
                    'priority' => '120',
                    'codes' => 'MAX, HOCH,',
                    'min_age_hours' => '12',
                ],
                'customer_backlog' => [
                    'enabled' => '0',
                    'priority' => 0,
                    'min_open_tickets' => -1,
                ],
            ],
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnMap([
            [HelpdeskSettingsGateway::KEY_SYSTEM_RECOMMENDATIONS_CONFIG, $rawConfig],
        ]);

        $gateway = $this->createGateway($settings);
        $config = $gateway->getSystemRecommendationsConfig();

        $this->assertSame(20, $config['max_items']);
        $this->assertSame(['MAX', 'HOCH'], $config['rules']['high_risk_aging']['codes']);
        $this->assertSame(12, $config['rules']['high_risk_aging']['min_age_hours']);
        $this->assertFalse($config['rules']['customer_backlog']['enabled']);
        $this->assertSame(1, $config['rules']['customer_backlog']['priority']);
        $this->assertSame(1, $config['rules']['customer_backlog']['min_open_tickets']);
    }

    public function testSetSystemRecommendationsConfigPersistsNormalizedJson(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->expects($this->once())
            ->method('set')
            ->with(
                HelpdeskSettingsGateway::KEY_SYSTEM_RECOMMENDATIONS_CONFIG,
                $this->callback(static function (mixed $value): bool {
                    if (!is_string($value) || $value === '') {
                        return false;
                    }

                    $decoded = json_decode($value, true);
                    if (!is_array($decoded)) {
                        return false;
                    }

                    return ($decoded['version'] ?? null) === 1
                        && ($decoded['max_items'] ?? null) === 1
                        && (($decoded['rules']['high_risk_aging']['codes'] ?? []) === ['MAX', 'HOCH']);
                }),
                'helpdesk.system_recommendations_config'
            )
            ->willReturn(true);

        $gateway = $this->createGateway($settings);
        $result = $gateway->setSystemRecommendationsConfig([
            'max_items' => -10,
            'rules' => [
                'high_risk_aging' => [
                    'codes' => '',
                ],
            ],
        ]);

        $this->assertTrue($result);
    }

    // --- Controlling Risk Config ---

    public function testGetControllingRiskConfigEnvelopeReturnsDefaultWhenNotSet(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnMap([
            [HelpdeskSettingsGateway::KEY_CONTROLLING_RISK_CONFIG, null],
        ]);

        $gateway = $this->createGateway($settings);
        $envelope = $gateway->getControllingRiskConfigEnvelope();

        $this->assertSame('default', $envelope['source']);
        $this->assertSame(1, $envelope['config']['version']);
        $this->assertTrue($envelope['config']['rules']['ticket_volume_increase']['enabled']);
        $this->assertSame(30, $envelope['config']['rules']['ticket_volume_increase']['threshold_percent']);
        $this->assertSame(72, $envelope['config']['rules']['avg_resolution_high']['threshold_hours']);
    }

    public function testSetControllingRiskConfigNormalizesValues(): void
    {
        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->expects($this->once())
            ->method('set')
            ->with(
                HelpdeskSettingsGateway::KEY_CONTROLLING_RISK_CONFIG,
                $this->callback(static function (mixed $value): bool {
                    if (!is_string($value) || $value === '') {
                        return false;
                    }

                    $decoded = json_decode($value, true);
                    if (!is_array($decoded)) {
                        return false;
                    }

                    return ($decoded['version'] ?? null) === 1
                        && ($decoded['rules']['ticket_volume_increase']['threshold_percent'] ?? null) === 50;
                }),
                'helpdesk.controlling_risk_config'
            )
            ->willReturn(true);

        $gateway = $this->createGateway($settings);
        $result = $gateway->setControllingRiskConfig([
            'rules' => [
                'ticket_volume_increase' => ['threshold_percent' => 50],
            ],
        ]);

        $this->assertTrue($result);
    }

    public function testNormalizeControllingRiskConfigEnforcesMinMax(): void
    {
        $rawConfig = json_encode([
            'version' => 1,
            'rules' => [
                'ticket_volume_increase' => ['enabled' => true, 'threshold_percent' => 999],
                'avg_resolution_high' => ['enabled' => true, 'threshold_hours' => 0],
                'open_aging' => ['enabled' => true, 'threshold_hours' => 5000],
                'unassigned_ratio' => ['enabled' => true, 'threshold_percent' => 101],
            ],
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $settings = $this->createMock(SettingRepositoryInterface::class);
        $settings->method('getValue')->willReturnMap([
            [HelpdeskSettingsGateway::KEY_CONTROLLING_RISK_CONFIG, $rawConfig],
        ]);

        $gateway = $this->createGateway($settings);
        $config = $gateway->getControllingRiskConfig();

        // ticket_volume_increase: 999 > 200 → clamped to max 200
        $this->assertSame(200, $config['rules']['ticket_volume_increase']['threshold_percent']);
        // avg_resolution_high: 0 < 1 → clamped to min 1
        $this->assertSame(1, $config['rules']['avg_resolution_high']['threshold_hours']);
        // open_aging: 5000 > 2160 → clamped to max 2160
        $this->assertSame(2160, $config['rules']['open_aging']['threshold_hours']);
        // unassigned_ratio: 101 > 100 → clamped to max 100
        $this->assertSame(100, $config['rules']['unassigned_ratio']['threshold_percent']);
    }
}
