<?php

// SPDX-FileCopyrightText: 2024 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Tests\Icinga\Module\Icingadb\Command\Transport;

use Icinga\Module\Icingadb\Command\Object\AddCommentCommand;
use Icinga\Module\Icingadb\Command\Transport\CommandTransportException;
use Icinga\Module\Icingadb\Model\Host;
use PHPUnit\Framework\TestCase;
use Tests\Icinga\Module\Icingadb\Lib\FailoverCommandTransport;
use Tests\Icinga\Module\Icingadb\Lib\IntermittentlyFailingCommandTransport;
use Tests\Icinga\Module\Icingadb\Lib\StrikingCommandTransport;

class CommandTransportTest extends TestCase
{
    public function testFatalErrorHandling(): void
    {
        $this->expectException(CommandTransportException::class);
        $this->expectExceptionMessage('endpointA strikes!');

        (new StrikingCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setObjects(new \CallbackFilterIterator(new \ArrayIterator([
                    (new Host())->setProperties(['name' => 'host1']),
                    (new Host())->setProperties(['name' => 'host2']),
                ]), function ($host) {
                    return $host->name === 'host2';
                }))
        );
    }

    public function testFallbackHandling(): void
    {
        $result = (new FailoverCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setObjects(new \ArrayIterator([
                    (new Host())->setProperties(['name' => 'host1']),
                    (new Host())->setProperties(['name' => 'host2']),
                ]))
        );

        $this->assertSame(
            [
                'author' => 'GLaDOS',
                'comment' => 'The cake is a lie',
                'expiry' => 42,
                'hosts' => ['host1', 'host2']
            ],
            $result
        );
    }

    public function testGeneratorsAreNotSupported(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Generators are not supported');

        (new StrikingCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setObjects((function () {
                    yield (new Host())->setProperties(['name' => 'host1']);
                    yield (new Host())->setProperties(['name' => 'host2']);
                })())
        );
    }

    public function testChunkedObjectsWithFallbackHandling(): void
    {
        // Multiple chunks

        $result = (new FailoverCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setChunkSize(1)
                ->setObjects(new \ArrayIterator([
                    (new Host())->setProperties(['name' => 'host1']),
                    (new Host())->setProperties(['name' => 'host2']),
                ]))
        );

        $this->assertSame(
            [
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host1']
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host2']
                ]
            ],
            $result
        );

        // A single chunk

        $result = (new FailoverCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setChunkSize(4)
                ->setObjects(new \ArrayIterator([
                    (new Host())->setProperties(['name' => 'host1']),
                    (new Host())->setProperties(['name' => 'host2']),
                ]))
        );

        $this->assertSame(
            [
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host1', 'host2']
                ]
            ],
            $result
        );
    }

    public function testIntermittentFailureHandlingDuringChunkedTransmission(): void
    {
        // Fails after the 2nd chunk

        $result = (new IntermittentlyFailingCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setChunkSize(1)
                ->setObjects(new \CallbackFilterIterator(new \ArrayIterator([
                    (new Host())->setProperties(['name' => 'host1']),
                    (new Host())->setProperties(['name' => 'host2']),
                    (new Host())->setProperties(['name' => 'host3']),
                    (new Host())->setProperties(['name' => 'host4']),
                    (new Host())->setProperties(['name' => 'host5']),
                    (new Host())->setProperties(['name' => 'host6']),
                ]), fn() => true))
        );

        $this->assertSame(
            [
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host1'],
                    'endpoint' => 'endpointA'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host2'],
                    'endpoint' => 'endpointB'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host3'],
                    'endpoint' => 'endpointB'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host4'],
                    'endpoint' => 'endpointB'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host5'],
                    'endpoint' => 'endpointB'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host6'],
                    'endpoint' => 'endpointB'
                ]
            ],
            $result
        );

        // Fails after the next-to-last chunk

        IntermittentlyFailingCommandTransport::$failAtAttemptNo = 3;
        IntermittentlyFailingCommandTransport::$attemptNo = 0;

        $result = (new IntermittentlyFailingCommandTransport())->send(
            (new AddCommentCommand())
                ->setExpireTime(42)
                ->setAuthor('GLaDOS')
                ->setComment('The cake is a lie')
                ->setChunkSize(2)
                ->setObjects(new \CallbackFilterIterator(new \ArrayIterator([
                    (new Host())->setProperties(['name' => 'host1']),
                    (new Host())->setProperties(['name' => 'host2']),
                    (new Host())->setProperties(['name' => 'host3']),
                    (new Host())->setProperties(['name' => 'host4']),
                    (new Host())->setProperties(['name' => 'host5']),
                    (new Host())->setProperties(['name' => 'host6']),
                ]), fn() => true))
        );

        $this->assertSame(
            [
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host1', 'host2'],
                    'endpoint' => 'endpointA'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host3', 'host4'],
                    'endpoint' => 'endpointA'
                ],
                [
                    'author' => 'GLaDOS',
                    'comment' => 'The cake is a lie',
                    'expiry' => 42,
                    'hosts' => ['host5', 'host6'],
                    'endpoint' => 'endpointB'
                ]
            ],
            $result
        );
    }

    public function testFatalErrorHandlingDuringChunkedTransmission(): void
    {
        $this->expectException(CommandTransportException::class);
        $this->expectExceptionMessage('endpointA strikes!');

        (new StrikingCommandTransport())->send(
            (new AddCommentCommand())
            ->setExpireTime(42)
            ->setAuthor('GLaDOS')
            ->setComment('The cake is a lie')
            ->setChunkSize(1)
            ->setObjects(new \ArrayIterator([
                (new Host())->setProperties(['name' => 'host1']),
                (new Host())->setProperties(['name' => 'host2']),
            ]))
        );
    }
}
