<?php

declare(strict_types=1);

namespace Apexl\Io\Tests\UnitTests;

use Apexl\Io\Logger\Dtos\HandlerConfigParams;
use Apexl\Io\Logger\Dtos\HandlerConfigParams\GoogleChatHandlerConfigParams;
use Apexl\Io\Logger\Dtos\HandlerConfigParams\MailHandlerConfigParams;
use Apexl\Io\Logger\Enums\Handler;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use stdClass;

/**
 * A destination whose params class cannot be resolved falls back to stdClass, and every
 * param configured for it is then silently dropped. Handlers that need one — a mail `to`,
 * a webhook URL — degrade to a NullHandler, so nothing throws and nothing arrives: the
 * logging config looks correct and the logs simply stop.
 *
 * That is exactly what a lower-camel candidate class name did. PHP matches class names
 * case-insensitively but PSR-4 resolves them to a file of the same case, so
 * `mailHandlerConfigParams` was found on a case-insensitive filesystem and nowhere else.
 */
final class HandlerConfigParamsTest extends TestCase
{
    /**
     * Destinations with a param that has no default. Only listed so this test can build
     * one — a real config missing it is a config error, and Spatie says so.
     */
    private const array MINIMUM_PARAMS = [
        'database' => ['table' => 'logs'],
    ];

    /** @return array<string, array{string, class-string}> */
    public static function destinations(): array
    {
        return [
            'single word' => ['mail', MailHandlerConfigParams::class],
            'snake_case' => ['google_chat', GoogleChatHandlerConfigParams::class],
        ];
    }

    #[Test]
    #[DataProvider('destinations')]
    public function aDestinationResolvesToItsTypedParams(string $destination, string $expected): void
    {
        self::assertInstanceOf($expected, HandlerConfigParams::factory($destination, []));
    }

    /**
     * The guard that matters: wherever a params class has been written, the destination
     * has to reach it. `error_log` takes no params and has none, which is not a fault —
     * a destination silently missing the one it does have is.
     */
    #[Test]
    public function everyDestinationWithAParamsClassReachesIt(): void
    {
        foreach (Handler::cases() as $handler) {
            $expected = sprintf(
                '%s\\%sHandlerConfigParams',
                HandlerConfigParams::class,
                str_replace(' ', '', ucwords(str_replace('_', ' ', $handler->value))),
            );

            if (!class_exists($expected)) {
                continue;
            }

            self::assertInstanceOf(
                $expected,
                HandlerConfigParams::factory($handler->value, self::MINIMUM_PARAMS[$handler->value] ?? []),
                sprintf('Destination "%s" fell back to stdClass; its params would be dropped.', $handler->value),
            );
        }
    }

    #[Test]
    public function paramsSurviveOntoTheTypedObject(): void
    {
        $params = HandlerConfigParams::factory('mail', ['to' => 'someone@example.com']);

        self::assertSame('someone@example.com', $params->to);
    }

    /** An unknown destination still has to degrade rather than fatal. */
    #[Test]
    public function anUnknownDestinationFallsBackToStdClass(): void
    {
        self::assertInstanceOf(stdClass::class, HandlerConfigParams::factory('carrier_pigeon', []));
    }
}
