<?php

declare(strict_types=1);

namespace Ramsey\Uuid\Test\Builder;

use DateTimeInterface;
use Mockery;
use Ramsey\Uuid\Builder\FallbackBuilder;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Codec\StringCodec;
use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
use Ramsey\Uuid\Converter\Time\PhpTimeConverter;
use Ramsey\Uuid\Exception\BuilderNotFoundException;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder;
use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder;
use Ramsey\Uuid\Rfc4122\UuidV1;
use Ramsey\Uuid\Rfc4122\UuidV2;
use Ramsey\Uuid\Rfc4122\UuidV6;
use Ramsey\Uuid\Test\TestCase;

class FallbackBuilderTest extends TestCase
{
    public function testBuildThrowsExceptionAfterAllConfiguredBuildersHaveErrored(): void
    {
        $codec = Mockery::mock(CodecInterface::class);
        $bytes = 'foobar';

        $builder1 = Mockery::mock(UuidBuilderInterface::class);
        $builder1
            ->shouldReceive('build')
            ->once()
            ->with($codec, $bytes)
            ->andThrow(UnableToBuildUuidException::class);

        $builder2 = Mockery::mock(UuidBuilderInterface::class);
        $builder2
            ->shouldReceive('build')
            ->once()
            ->with($codec, $bytes)
            ->andThrow(UnableToBuildUuidException::class);

        $builder3 = Mockery::mock(UuidBuilderInterface::class);
        $builder3
            ->shouldReceive('build')
            ->once()
            ->with($codec, $bytes)
            ->andThrow(UnableToBuildUuidException::class);

        $fallbackBuilder = new FallbackBuilder([$builder1, $builder2, $builder3]);

        $this->expectException(BuilderNotFoundException::class);
        $this->expectExceptionMessage(
            'Could not find a suitable builder for the provided codec and fields'
        );

        $fallbackBuilder->build($codec, $bytes);
    }

    /**
     * @dataProvider provideBytes
     */
    public function testSerializationOfBuilderCollection(string $bytes): void
    {
        $calculator = new BrickMathCalculator();
        $genericNumberConverter = new GenericNumberConverter($calculator);
        $genericTimeConverter = new GenericTimeConverter($calculator);
        $phpTimeConverter = new PhpTimeConverter($calculator, $genericTimeConverter);

        // Use the GenericTimeConverter.
        $guidBuilder = new GuidBuilder($genericNumberConverter, $genericTimeConverter);
        $rfc4122Builder = new Rfc4122UuidBuilder($genericNumberConverter, $genericTimeConverter);
        $nonstandardBuilder = new NonstandardUuidBuilder($genericNumberConverter, $genericTimeConverter);

        // Use the PhpTimeConverter.
        $guidBuilder2 = new GuidBuilder($genericNumberConverter, $phpTimeConverter);
        $rfc4122Builder2 = new Rfc4122UuidBuilder($genericNumberConverter, $phpTimeConverter);
        $nonstandardBuilder2 = new NonstandardUuidBuilder($genericNumberConverter, $phpTimeConverter);

        /** @var list<UuidBuilderInterface> $unserializedBuilderCollection */
        $unserializedBuilderCollection = unserialize(serialize([
            $guidBuilder,
            $guidBuilder2,
            $rfc4122Builder,
            $rfc4122Builder2,
            $nonstandardBuilder,
            $nonstandardBuilder2,
        ]));

        foreach ($unserializedBuilderCollection as $builder) {
            $codec = new StringCodec($builder);

            $this->assertInstanceOf(UuidBuilderInterface::class, $builder);

            try {
                $uuid = $builder->build($codec, $bytes);

                if (($uuid instanceof UuidV1) || ($uuid instanceof UuidV2) || ($uuid instanceof UuidV6)) {
                    $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime());
                }
            } catch (UnableToBuildUuidException $exception) {
                switch ($exception->getMessage()) {
                    case 'The byte string received does not contain a valid version':
                    case 'The byte string received does not conform to the RFC 4122 variant':
                    case 'The byte string received does not conform to the RFC 4122 or Microsoft Corporation variants':
                        // This is expected; ignoring.
                        break;
                    default:
                        throw $exception;
                }
            }
        }
    }

    /**
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification
     */
    public function provideBytes(): array
    {
        return [
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1110b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1111b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1112b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1113b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1114b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1115b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1116b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e1117b210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e111eb210800200c9a66'),
            ],
            [
                // GUID bytes
                'bytes' => hex2bin('b08c6fff7dc5e111fb210800200c9a66'),
            ],
            [
                // Version 1 bytes
                'bytes' => hex2bin('ff6f8cb0c57d11e19b210800200c9a66'),
            ],
            [
                // Version 2 bytes
                'bytes' => hex2bin('000001f55cde21ea84000242ac130003'),
            ],
            [
                // Version 3 bytes
                'bytes' => hex2bin('ff6f8cb0c57d31e1bb210800200c9a66'),
            ],
            [
                // Version 4 bytes
                'bytes' => hex2bin('ff6f8cb0c57d41e1ab210800200c9a66'),
            ],
            [
                // Version 5 bytes
                'bytes' => hex2bin('ff6f8cb0c57d51e18b210800200c9a66'),
            ],
            [
                // Version 6 bytes
                'bytes' => hex2bin('ff6f8cb0c57d61e18b210800200c9a66'),
            ],
            [
                // NIL bytes
                'bytes' => hex2bin('00000000000000000000000000000000'),
            ],
        ];
    }
}
