<?php

use App\Contracts\ShouldNotifyAdmin;
use App\Notifications\SystemErrorNotification;
use App\Services\AdminNotificationService;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Notification;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Mailer\Exception\TransportException;

beforeEach(function () {
    config([
        'libra.notifications.system_notifications_enabled' => true,
        'libra.notifications.admin_email' => 'admin@test.com',
        'libra.notifications.throttle_minutes' => 15,
    ]);
});

describe('shouldNotifyAdmin', function () {
    it('returns false for validation exceptions', function () {
        $service = new AdminNotificationService;
        $exception = ValidationException::withMessages(['field' => ['error']]);

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });

    it('returns false for authentication exceptions', function () {
        $service = new AdminNotificationService;
        $exception = new AuthenticationException;

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });

    it('returns false for authorization exceptions', function () {
        $service = new AdminNotificationService;
        $exception = new AuthorizationException;

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });

    it('returns false for model not found exceptions', function () {
        $service = new AdminNotificationService;
        $exception = new ModelNotFoundException;

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });

    it('returns false for not found http exceptions', function () {
        $service = new AdminNotificationService;
        $exception = new NotFoundHttpException;

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });

    it('returns false for token mismatch exceptions', function () {
        $service = new AdminNotificationService;
        $exception = new TokenMismatchException;

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });

    it('returns true for exceptions implementing ShouldNotifyAdmin', function () {
        $service = new AdminNotificationService;
        $exception = new class extends Exception implements ShouldNotifyAdmin {};

        expect($service->shouldNotifyAdmin($exception))->toBeTrue();
    });

    it('returns true for mail transport exceptions', function () {
        $service = new AdminNotificationService;
        $exception = new TransportException('SMTP error');

        expect($service->shouldNotifyAdmin($exception))->toBeTrue();
    });

    it('returns true for critical database connection errors', function () {
        $service = new AdminNotificationService;
        $pdo = new PDOException('Connection refused');
        $exception = new QueryException('mysql', 'SELECT 1', [], $pdo);

        // Mock the code to be a critical code
        $reflection = new ReflectionClass($exception);
        $property = $reflection->getProperty('code');
        $property->setAccessible(true);
        $property->setValue($exception, '2002');

        expect($service->shouldNotifyAdmin($exception))->toBeTrue();
    });

    it('returns false for non-critical database errors', function () {
        $service = new AdminNotificationService;
        $pdo = new PDOException('Syntax error');
        $exception = new QueryException('mysql', 'SELECT 1', [], $pdo);

        expect($service->shouldNotifyAdmin($exception))->toBeFalse();
    });
});

describe('notifyError', function () {
    it('sends notification when enabled', function () {
        Notification::fake();
        Cache::flush();

        $service = new AdminNotificationService;
        $exception = new RuntimeException('Test error');

        $service->notifyError($exception);

        Notification::assertSentOnDemand(
            SystemErrorNotification::class,
            function ($notification, $channels, $notifiable) {
                return $notifiable->routes['mail'] === 'admin@test.com';
            }
        );
    });

    it('does not send notification when disabled', function () {
        Notification::fake();
        config(['libra.notifications.system_notifications_enabled' => false]);

        $service = new AdminNotificationService;
        $exception = new RuntimeException('Test error');

        $service->notifyError($exception);

        Notification::assertNothingSent();
    });

    it('does not send notification when admin email is not configured', function () {
        Notification::fake();
        config(['libra.notifications.admin_email' => null]);

        $service = new AdminNotificationService;
        $exception = new RuntimeException('Test error');

        $service->notifyError($exception);

        Notification::assertNothingSent();
    });

    it('rate limits duplicate notifications', function () {
        Notification::fake();
        Cache::flush();

        $service = new AdminNotificationService;
        $exception = new RuntimeException('Test error');

        $service->notifyError($exception);
        $service->notifyError($exception);

        Notification::assertSentOnDemandTimes(SystemErrorNotification::class, 1);
    });

    it('allows notifications for different error types', function () {
        Notification::fake();
        Cache::flush();

        $service = new AdminNotificationService;
        $exception1 = new RuntimeException('Error 1');
        $exception2 = new InvalidArgumentException('Error 2');

        $service->notifyError($exception1);
        $service->notifyError($exception2);

        Notification::assertSentOnDemandTimes(SystemErrorNotification::class, 2);
    });

    it('allows notifications for same error type with different messages', function () {
        Notification::fake();
        Cache::flush();

        $service = new AdminNotificationService;
        $exception1 = new RuntimeException('Error message 1');
        $exception2 = new RuntimeException('Error message 2');

        $service->notifyError($exception1);
        $service->notifyError($exception2);

        Notification::assertSentOnDemandTimes(SystemErrorNotification::class, 2);
    });
});
