<?php

namespace Tests\Feature;

use App\Livewire\Settings\Email;
use App\Models\AuditEvent;
use App\Models\Setting;
use App\Models\User;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Mail;
use Livewire\Livewire;
use Tests\TestCase;

class SmtpConfigTest extends TestCase
{
    use RefreshDatabase;

    private function actingUser(): User
    {
        $user = User::factory()->create(['email' => 'admin@clusev.local']);
        $this->actingAs($user);

        return $user;
    }

    public function test_saving_persists_settings_and_stores_password_encrypted(): void
    {
        $this->actingUser();

        Livewire::test(Email::class)
            ->set('mail_host', 'smtp.example.com')
            ->set('mail_port', 2525)
            ->set('mail_username', 'panel')
            ->set('mail_password', 's3cr3t-pw')
            ->set('mail_encryption', 'tls')
            ->set('mail_from_address', 'panel@example.com')
            ->set('mail_from_name', 'Clusev')
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame('smtp.example.com', Setting::get('mail_host'));
        $this->assertSame('2525', Setting::get('mail_port'));
        $this->assertSame('panel', Setting::get('mail_username'));
        $this->assertSame('tls', Setting::get('mail_encryption'));
        $this->assertSame('panel@example.com', Setting::get('mail_from_address'));
        $this->assertSame('Clusev', Setting::get('mail_from_name'));

        // Stored value must NOT be the plaintext, but must decrypt back to it.
        $stored = Setting::get('mail_password');
        $this->assertNotNull($stored);
        $this->assertNotSame('s3cr3t-pw', $stored, 'password must be encrypted at rest');
        $this->assertSame('s3cr3t-pw', Crypt::decryptString($stored));

        $this->assertDatabaseHas('audit_events', ['action' => 'mail.configure', 'target' => 'smtp.example.com']);
    }

    public function test_password_is_not_overwritten_when_left_blank(): void
    {
        $this->actingUser();
        Setting::put('mail_host', 'smtp.example.com');
        Setting::put('mail_from_address', 'panel@example.com');
        Setting::put('mail_password', Crypt::encryptString('original-pw'));

        Livewire::test(Email::class)
            ->set('mail_from_name', 'Clusev')
            ->set('mail_password', '') // left blank -> keep the stored one
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame('original-pw', Crypt::decryptString(Setting::get('mail_password')));
    }

    public function test_configured_reflects_host_and_from_presence(): void
    {
        $this->actingUser();

        $component = Livewire::test(Email::class);
        $this->assertFalse($component->instance()->configured());

        $component->set('mail_host', 'smtp.example.com');
        $this->assertFalse($component->instance()->configured(), 'host alone is not enough');

        $component->set('mail_from_address', 'panel@example.com');
        $this->assertTrue($component->instance()->configured());
    }

    public function test_runtime_override_switches_to_smtp_when_configured(): void
    {
        config(['mail.default' => 'log']);

        Setting::put('mail_host', 'smtp.example.com');
        Setting::put('mail_port', '2525');
        Setting::put('mail_username', 'panel');
        Setting::put('mail_password', Crypt::encryptString('s3cr3t-pw'));
        Setting::put('mail_encryption', 'ssl');
        Setting::put('mail_from_address', 'panel@example.com');
        Setting::put('mail_from_name', 'Clusev');

        // Re-run the provider's boot, which applies the mail settings.
        $this->app->register(AppServiceProvider::class, true);

        $this->assertSame('smtp', config('mail.default'));
        $this->assertSame('smtp.example.com', config('mail.mailers.smtp.host'));
        $this->assertSame(2525, config('mail.mailers.smtp.port'));
        $this->assertSame('panel', config('mail.mailers.smtp.username'));
        $this->assertSame('s3cr3t-pw', config('mail.mailers.smtp.password'));
        $this->assertSame('ssl', config('mail.mailers.smtp.encryption'));
        $this->assertSame('panel@example.com', config('mail.from.address'));
    }

    public function test_runtime_override_leaves_default_mailer_when_unconfigured(): void
    {
        config(['mail.default' => 'log']);

        // No settings -> default mailer must stay untouched.
        $this->app->register(AppServiceProvider::class, true);

        $this->assertSame('log', config('mail.default'));
    }

    public function test_encryption_none_maps_to_null(): void
    {
        config(['mail.default' => 'log']);
        Setting::put('mail_host', 'smtp.example.com');
        Setting::put('mail_encryption', 'none');
        Setting::put('mail_from_address', 'panel@example.com');

        $this->app->register(AppServiceProvider::class, true);

        $this->assertSame('smtp', config('mail.default'));
        $this->assertNull(config('mail.mailers.smtp.encryption'));
    }

    public function test_test_send_dispatches_a_mail_to_the_current_user(): void
    {
        // Force the smtp mailer onto the in-memory `array` transport so the test-send
        // captures the message WITHOUT opening a real SMTP connection. The component's
        // runtime config() only sets host/port/etc., never `transport`, so this sticks
        // across its Mail::purge('smtp') rebuild — fully deterministic, no network.
        config(['mail.mailers.smtp.transport' => 'array']);

        $user = $this->actingUser();
        Setting::put('mail_host', 'smtp.example.com');
        Setting::put('mail_from_address', 'panel@example.com');

        Livewire::test(Email::class)
            ->set('mail_host', 'smtp.example.com')
            ->set('mail_from_address', 'panel@example.com')
            ->call('sendTest')
            ->assertHasNoErrors();

        $messages = app('mailer')->getSymfonyTransport()->messages();
        $this->assertCount(1, $messages, 'exactly one test mail must be sent');

        $sent = $messages[0]->getOriginalMessage();
        $recipients = array_map(fn ($a) => $a->getAddress(), $sent->getTo());
        $this->assertSame([$user->email], $recipients, 'test mail must go only to the current user');
    }

    public function test_test_send_does_nothing_when_unconfigured(): void
    {
        Mail::fake();
        $this->actingUser();

        Livewire::test(Email::class)
            ->call('sendTest')
            ->assertHasNoErrors();

        Mail::assertNothingSent();
    }

    public function test_audit_event_is_recorded_on_save(): void
    {
        $this->actingUser();

        Livewire::test(Email::class)
            ->set('mail_host', 'smtp.example.com')
            ->set('mail_from_address', 'panel@example.com')
            ->set('mail_from_name', 'Clusev')
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame(1, AuditEvent::where('action', 'mail.configure')->count());
    }
}
