<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tests\Tenant;

use Ferienpass\CoreBundle\Entity\Tenant;
use Ferienpass\CoreBundle\Entity\TenantDomain;
use Ferienpass\CoreBundle\Repository\TenantDomainRepository;
use Ferienpass\CoreBundle\Tenant\HostResolver;
use Ferienpass\CoreBundle\Tenant\Surface;
use PHPUnit\Framework\TestCase;

class HostResolverTest extends TestCase
{
    public function testASingleTenantBuildServesEverythingEverywhere(): void
    {
        $resolved = $this->resolver([], enabled: false)->resolve('anything.example.com');

        self::assertNotNull($resolved);
        self::assertNull($resolved->tenant);
        self::assertTrue($resolved->servesFrontend);
        self::assertTrue($resolved->servesAdmin);
        self::assertFalse($resolved->adminAtRoot);
    }

    public function testAnUnclaimedHostResolvesToNothing(): void
    {
        self::assertNull($this->resolver([])->resolve('unknown.example.com'));
    }

    public function testAPublicSiteAlsoServesTheAdminWhileThereIsNoAdminDomain(): void
    {
        $tenant = self::tenant(['ferienpass-nordhorn.de' => Surface::Frontend]);

        $resolved = $this->resolver($tenant->getDomains()->toArray())->resolve('ferienpass-nordhorn.de');

        self::assertSame($tenant, $resolved?->tenant);
        self::assertTrue($resolved->servesFrontend);
        self::assertTrue($resolved->servesAdmin);
        self::assertFalse($resolved->adminAtRoot);
    }

    public function testAPublicSiteStopsServingTheAdminOnceThereIsAnAdminDomain(): void
    {
        $tenant = self::tenant([
            'ferienpass-nordhorn.de' => Surface::Frontend,
            'nordhorn.my-ferienpass.de' => Surface::Admin,
        ]);

        $resolved = $this->resolver($tenant->getDomains()->toArray())->resolve('ferienpass-nordhorn.de');

        self::assertTrue($resolved?->servesFrontend);
        self::assertFalse($resolved->servesAdmin);
    }

    public function testAnAdminDomainServesTheAdminAtItsRootAndNoPublicSite(): void
    {
        $tenant = self::tenant([
            'ferienpass-nordhorn.de' => Surface::Frontend,
            'nordhorn.my-ferienpass.de' => Surface::Admin,
        ]);

        $resolved = $this->resolver($tenant->getDomains()->toArray())->resolve('nordhorn.my-ferienpass.de');

        self::assertSame($tenant, $resolved?->tenant);
        self::assertFalse($resolved->servesFrontend);
        self::assertTrue($resolved->servesAdmin);
        self::assertTrue($resolved->adminAtRoot);
    }

    public function testThePlatformConsoleBelongsToNoTenant(): void
    {
        $resolved = $this->resolver([], platformHost: 'platform.my-ferienpass.de')->resolve('platform.my-ferienpass.de');

        self::assertNotNull($resolved);
        self::assertNull($resolved->tenant);
        self::assertTrue($resolved->isPlatform);
        self::assertTrue($resolved->adminAtRoot);
        self::assertFalse($resolved->servesFrontend);
    }

    public function testTheHostIsNormalisedBeforeItIsCompared(): void
    {
        $resolver = $this->resolver([], platformHost: 'https://Platform.My-Ferienpass.de:8443/');

        self::assertTrue($resolver->resolve('PLATFORM.my-ferienpass.de.')?->isPlatform);
    }

    /**
     * @param list<TenantDomain> $domains
     */
    private function resolver(array $domains, bool $enabled = true, ?string $platformHost = null): HostResolver
    {
        $repository = $this->createMock(TenantDomainRepository::class);
        $repository->method('findOneByHost')->willReturnCallback(static function (string $host) use ($domains): ?TenantDomain {
            foreach ($domains as $domain) {
                if ($domain->getHost() === $host) {
                    return $domain;
                }
            }

            return null;
        });

        return new HostResolver($repository, $enabled, $platformHost);
    }

    /**
     * @param array<string, Surface> $domains
     */
    private static function tenant(array $domains): Tenant
    {
        $tenant = new Tenant();

        foreach ($domains as $host => $surface) {
            $tenant->addDomain(new TenantDomain($tenant, $host, $surface));
        }

        return $tenant;
    }
}
