<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Tests\Commerce\Catalog\Configuration;

use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogHubConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogConfigurationProvider;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogConfigurationSnapshot;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogIntegrationConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Configuration\CatalogWebhookEndpointResolver;
use PHPUnit\Framework\TestCase;

final class CatalogHubConfigurationTest extends TestCase
{
    private const string INSTANCE_ID = '7ad9e145-910f-4de4-9cec-5f832494adef';
    private const string WEBHOOK_SECRET = '0123456789abcdef0123456789abcdef';

    public function testEnrollmentRequiresAnAllowedHubUrlAndAToken(): void
    {
        $configuration = $this->hubConfiguration('https://hub.example/base/', 'long-enrollment-token');

        self::assertTrue($configuration->isEnrollmentReady());
        self::assertFalse($configuration->isJwtVerificationReady());
        self::assertSame('https://hub.example/base', $configuration->hubUrl());
        self::assertSame('long-enrollment-token', $configuration->enrollmentToken());
        self::assertFalse($this->hubConfiguration('https://hub.example', '')->isEnrollmentReady());
        self::assertFalse($this->hubConfiguration('https://user@hub.example', 'token')->isEnrollmentReady());
    }

    public function testPlainHttpIsAllowedOnlyWhenDebugTransportIsEnabled(): void
    {
        self::assertFalse($this->hubConfiguration('http://127.0.0.1:8085', 'token')->isEnrollmentReady());
        self::assertTrue($this->hubConfiguration('http://127.0.0.1:8085', 'token', true)->isEnrollmentReady());
    }

    public function testJwtVerificationRequiresAReadablePublicKeyAndAllExpectedValues(): void
    {
        $path = tempnam(sys_get_temp_dir(), 'viticore-hub-public-key-');
        self::assertNotFalse($path);
        self::assertNotFalse(file_put_contents($path, 'public-key-pem'));

        try {
            $configuration = new CatalogHubConfiguration(
                'https://hub.example',
                'enrollment-token',
                $path,
                'serenity-commerce-hub',
                'serenity:catalog',
                'hub-main',
                false,
            );

            self::assertTrue($configuration->isJwtVerificationReady());
            self::assertSame($path, $configuration->publicKeyPath());
            self::assertFalse(new CatalogHubConfiguration(
                'https://hub.example',
                'enrollment-token',
                $path . '-missing',
                'serenity-commerce-hub',
                'serenity:catalog',
                'hub-main',
                false,
            )->isJwtVerificationReady());
        } finally {
            unlink($path);
        }
    }

    public function testWebhookUsesExplicitUrlBeforeTheHubDerivedUrl(): void
    {
        $resolver = new CatalogWebhookEndpointResolver(
            $this->catalogConfiguration(),
            $this->hubConfiguration('https://hub.example/base', ''),
            'https://events.example/serenity',
            self::WEBHOOK_SECRET,
        );

        self::assertTrue($resolver->isReady());
        self::assertSame('https://events.example/serenity', $resolver->url());
        self::assertSame(self::WEBHOOK_SECRET, $resolver->secret());
    }

    public function testWebhookIsDerivedWithoutDependingOnTheEnrollmentToken(): void
    {
        $resolver = new CatalogWebhookEndpointResolver(
            $this->catalogConfiguration(),
            $this->hubConfiguration('http://127.0.0.1:8085/', '', true),
            '',
            self::WEBHOOK_SECRET,
        );

        self::assertTrue($resolver->isReady());
        self::assertSame(
            'http://127.0.0.1:8085/webhooks/serenity/by-instance/' . self::INSTANCE_ID,
            $resolver->url(),
        );
    }

    public function testWebhookFailsClosedForAnInvalidExplicitUrlOrSecret(): void
    {
        $hubConfiguration = $this->hubConfiguration('https://hub.example', 'token');

        self::assertFalse(new CatalogWebhookEndpointResolver(
            $this->catalogConfiguration(),
            $hubConfiguration,
            'http://events.example/serenity',
            self::WEBHOOK_SECRET,
        )->isReady());
        self::assertFalse(new CatalogWebhookEndpointResolver(
            $this->catalogConfiguration(),
            $hubConfiguration,
            '',
            'too-short',
        )->isReady());
        self::assertFalse(new CatalogWebhookEndpointResolver(
            $this->catalogConfiguration(false),
            $hubConfiguration,
            '',
            self::WEBHOOK_SECRET,
        )->isReady());
    }

    public function testRuntimeAdaptersPreferThePersistentSnapshotOverLegacyEnvironmentValues(): void
    {
        $provider = $this->createStub(CatalogConfigurationProvider::class);
        $provider->method('current')->willReturn(new CatalogConfigurationSnapshot(
            enabled: true,
            hubUrl: 'https://persistent-hub.example',
            instanceId: self::INSTANCE_ID,
            currency: 'EUR',
            authAudience: 'serenity:catalog',
            enrollmentToken: 'persistent-enrollment-token',
            webhookSecret: self::WEBHOOK_SECRET,
            webhookUrl: null,
            jwtIssuer: 'serenity-commerce-hub',
            jwtKeyId: 'hub-main',
            jwtPublicKeyPem: 'public-key-pem',
        ));
        $catalog = new CatalogIntegrationConfiguration(false, '', '', 'USD', $provider);
        $hub = new CatalogHubConfiguration('', '', '', '', '', '', false, $provider);
        $webhook = new CatalogWebhookEndpointResolver($catalog, $hub, '', '', $provider);

        self::assertTrue($catalog->isEnabled());
        self::assertSame(self::INSTANCE_ID, $catalog->instanceId());
        self::assertSame('EUR', $catalog->currency());
        self::assertSame('https://persistent-hub.example', $hub->hubUrl());
        self::assertSame('persistent-enrollment-token', $hub->enrollmentToken());
        self::assertTrue($hub->isJwtVerificationReady());
        self::assertTrue($webhook->isReady());
        self::assertSame(
            'https://persistent-hub.example/webhooks/serenity/by-instance/' . self::INSTANCE_ID,
            $webhook->url(),
        );
    }

    public function testPersistentEnrollmentIsReadyBeforeTheHubVerificationKeyIsStored(): void
    {
        $provider = $this->createStub(CatalogConfigurationProvider::class);
        $provider->method('current')->willReturn(new CatalogConfigurationSnapshot(
            enabled: true,
            hubUrl: 'https://hub.example',
            instanceId: self::INSTANCE_ID,
            currency: 'EUR',
            authAudience: 'serenity:catalog',
            enrollmentToken: 'long-lived-enrollment-token',
            webhookSecret: self::WEBHOOK_SECRET,
            webhookUrl: null,
            jwtIssuer: null,
            jwtKeyId: null,
            jwtPublicKeyPem: null,
        ));
        $catalog = new CatalogIntegrationConfiguration(false, '', '', 'EUR', $provider);
        $hub = new CatalogHubConfiguration('', '', '', '', '', '', false, $provider);
        $webhook = new CatalogWebhookEndpointResolver($catalog, $hub, '', '', $provider);

        self::assertTrue($hub->isEnrollmentReady());
        self::assertTrue($webhook->isReady());
        self::assertFalse($hub->isJwtVerificationReady());
    }

    private function hubConfiguration(
        string $url,
        string $enrollmentToken,
        bool $allowInsecure = false,
    ): CatalogHubConfiguration {
        return new CatalogHubConfiguration(
            $url,
            $enrollmentToken,
            '',
            'serenity-commerce-hub',
            'serenity:catalog',
            'hub-main',
            $allowInsecure,
        );
    }

    private function catalogConfiguration(bool $enabled = true): CatalogIntegrationConfiguration
    {
        return new CatalogIntegrationConfiguration(
            $enabled,
            self::INSTANCE_ID,
            '',
            'EUR',
        );
    }
}
