<?php

namespace Tests\Unit\Notification;

use App\Domains\Integration\Services\MailService;
use App\Domains\Notification\Services\NotificationMailService;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Ticket\Services\TicketPdfService;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Mockery;
use ReflectionMethod;
use RuntimeException;
use Tests\TestCase;

class NotificationMailServiceLoggingTest extends TestCase
{
    private NotificationMailService $service;

    private MailService $mailService;

    protected function setUp(): void
    {
        parent::setUp();

        $this->mailService = Mockery::mock(MailService::class);
        $this->service = new NotificationMailService(
            $this->mailService,
            Mockery::mock(TicketPdfService::class),
        );
    }

    public function test_it_logs_and_swallows_a_missing_purchase(): void
    {
        $this->createEmptyPurchasesTable();

        Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
        Log::shouldReceive('warning')->once()->with(
            'Notification email skipped.',
            [
                'purchase_id' => 123,
                'reason' => 'purchase_not_found',
                'missing_model' => Purchase::class,
                'email_type' => 'purchase_confirmed',
            ],
        );

        $this->service->sendPurchaseConfirmed(123);
    }

    public function test_it_logs_successful_delivery_with_the_mailer(): void
    {
        $this->mailService->shouldReceive('mailerName')->once()->andReturn('smtp');
        Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
        Log::shouldReceive('info')->once()->with(
            'Notification email sent.',
            [
                'user_id' => 10,
                'tenant_code' => 'tenant-test',
                'email_type' => 'welcome',
                'mailer' => 'smtp',
            ],
        );

        $this->sendLogged(
            'welcome',
            ['user_id' => 10],
            fn (): array => ['tenant_code' => 'tenant-test'],
        );
    }

    public function test_it_logs_and_rethrows_delivery_failures(): void
    {
        $exception = new RuntimeException('SMTP unavailable.');
        Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
        Log::shouldReceive('error')->once()->with(
            'Notification email delivery failed.',
            Mockery::on(fn (array $context): bool => $context['purchase_id'] === 123
                && $context['email_type'] === 'purchase_confirmed'
                && $context['exception'] === $exception),
        );

        $this->expectExceptionObject($exception);

        $this->sendLogged('purchase_confirmed', ['purchase_id' => 123], function () use ($exception): array {
            throw $exception;
        });
    }

    /** @param array<string, mixed> $context */
    private function sendLogged(string $emailType, array $context, callable $send): void
    {
        (new ReflectionMethod($this->service, 'sendLogged'))->invoke(
            $this->service,
            $emailType,
            $context,
            $send,
        );
    }

    private function createEmptyPurchasesTable(): void
    {
        config([
            'database.default' => 'sqlite',
            'database.connections.sqlite.database' => ':memory:',
        ]);
        Schema::connection('sqlite')->create('compras', function (Blueprint $table): void {
            $table->id();
        });
    }
}
