<?php

namespace Tests\Feature\Admin;

use App\Enums\InvestmentStatus;
use App\Enums\InvestmentTransactionType;
use App\Enums\UserKycStatus;
use App\Enums\WithdrawalRequestStatus;
use App\Mail\AdminInvestmentPendingMail;
use App\Mail\AdminKycSubmittedMail;
use App\Mail\AdminNewUserRegisteredMail;
use App\Mail\AdminWithdrawalRequestedMail;
use App\Mail\ConfirmInvestmentMail;
use App\Mail\ConfirmWithdrawalMail;
use App\Models\AdminNotificationSetting;
use App\Models\Investment;
use App\Models\InvestmentSetting;
use App\Models\InvestmentTransaction;
use App\Models\User;
use App\Models\UserWallet;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Laravel\Fortify\Features;
use Tests\TestCase;

class AdminNotificationSettingsTest extends TestCase
{
    use RefreshDatabase;

    public function test_admin_can_view_notification_settings(): void
    {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->get(route('admin.notification-settings.edit'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('admin/notification-settings')
                ->where('settings.notify_new_user_registered', true)
                ->where('settings.notify_investment_pending', true)
                ->where('settings.notify_withdrawal_requested', true)
                ->where('settings.notify_kyc_submitted', true));
    }

    public function test_guest_and_investor_cannot_access_notification_settings(): void
    {
        $this->get(route('admin.notification-settings.edit'))
            ->assertRedirect(route('login'));

        $investor = User::factory()->create();

        $this->actingAs($investor)
            ->get(route('admin.notification-settings.edit'))
            ->assertForbidden();

        $this->actingAs($investor)
            ->put(route('admin.notification-settings.update'), $this->enabledPayload())
            ->assertForbidden();
    }

    public function test_admin_can_update_notification_settings(): void
    {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->put(route('admin.notification-settings.update'), [
                'notify_new_user_registered' => false,
                'notify_investment_pending' => true,
                'notify_withdrawal_requested' => false,
                'notify_kyc_submitted' => true,
            ])
            ->assertRedirect(route('admin.notification-settings.edit'));

        $settings = AdminNotificationSetting::current()->fresh();

        $this->assertFalse($settings->notify_new_user_registered);
        $this->assertTrue($settings->notify_investment_pending);
        $this->assertFalse($settings->notify_withdrawal_requested);
        $this->assertTrue($settings->notify_kyc_submitted);
    }

    public function test_new_investor_registration_notifies_admins(): void
    {
        $this->skipUnlessFortifyHas(Features::registration());

        $admin = User::factory()->admin()->create();
        Mail::fake();

        $this->post(route('register.store'), [
            'first_name' => 'Bob',
            'last_name' => 'Builder',
            'email' => 'bob@example.com',
            'contact_number' => '+1 555 200 2000',
            'country' => 'US',
            'password' => 'password',
            'password_confirmation' => 'password',
        ])->assertRedirect(route('dashboard', absolute: false));

        Mail::assertSent(AdminNewUserRegisteredMail::class, function (AdminNewUserRegisteredMail $mail) use ($admin): bool {
            return $mail->hasTo($admin->email)
                && $mail->hasSubject('Nuevo usuario registrado')
                && $mail->user->email === 'bob@example.com';
        });
    }

    public function test_first_admin_registration_does_not_send_new_user_mail(): void
    {
        $this->skipUnlessFortifyHas(Features::registration());

        Mail::fake();

        $this->post(route('register.store'), [
            'first_name' => 'Ada',
            'last_name' => 'Lovelace',
            'email' => 'ada@example.com',
            'contact_number' => '+1 555 100 1000',
            'country' => 'US',
            'password' => 'password',
            'password_confirmation' => 'password',
        ])->assertRedirect(route('dashboard', absolute: false));

        Mail::assertNotSent(AdminNewUserRegisteredMail::class);
    }

    public function test_new_user_mail_is_skipped_when_disabled(): void
    {
        $this->skipUnlessFortifyHas(Features::registration());

        User::factory()->admin()->create();
        AdminNotificationSetting::current()->update([
            'notify_new_user_registered' => false,
        ]);
        Mail::fake();

        $this->post(route('register.store'), [
            'first_name' => 'Bob',
            'last_name' => 'Builder',
            'email' => 'bob@example.com',
            'contact_number' => '+1 555 200 2000',
            'country' => 'US',
            'password' => 'password',
            'password_confirmation' => 'password',
        ])->assertRedirect(route('dashboard', absolute: false));

        Mail::assertNotSent(AdminNewUserRegisteredMail::class);
    }

    public function test_pending_investment_notifies_admins_after_email_confirmation(): void
    {
        InvestmentSetting::current();
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();
        Mail::fake();

        $this->actingAs($investor)->post(route('investments.store'), [
            'funding_source' => 'blockchain',
            'amount' => 500,
            'duration_months' => 4,
            'txid' => 'trc20-admin-notify',
        ])->assertRedirect(route('investments.index'));

        Mail::assertNotSent(AdminInvestmentPendingMail::class);

        $confirmUrl = null;
        Mail::assertSent(ConfirmInvestmentMail::class, function (ConfirmInvestmentMail $mail) use (&$confirmUrl): bool {
            $confirmUrl = $mail->confirmUrl;

            return true;
        });

        $this->assertIsString($confirmUrl);
        $this->assertSame(1, preg_match('#/confirmaciones/correo/inversion/([a-fA-F0-9]+)#', $confirmUrl, $matches));

        $this->actingAs($investor)
            ->get(route('confirm.email.investment', ['token' => $matches[1]]))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('confirmations/investment')
                ->where('pending_approval', true));

        Mail::assertSent(AdminInvestmentPendingMail::class, function (AdminInvestmentPendingMail $mail) use ($admin, $investor): bool {
            return $mail->hasTo($admin->email)
                && $mail->hasSubject('Inversión pendiente de aprobación')
                && $mail->investment->user_id === $investor->id;
        });
    }

    public function test_pending_investment_mail_is_skipped_when_disabled(): void
    {
        InvestmentSetting::current();
        User::factory()->admin()->create();
        $investor = User::factory()->create();
        AdminNotificationSetting::current()->update([
            'notify_investment_pending' => false,
        ]);
        Mail::fake();

        $this->actingAs($investor)->post(route('investments.store'), [
            'funding_source' => 'blockchain',
            'amount' => 500,
            'duration_months' => 4,
            'txid' => 'trc20-admin-notify-off',
        ]);

        $confirmUrl = null;
        Mail::assertSent(ConfirmInvestmentMail::class, function (ConfirmInvestmentMail $mail) use (&$confirmUrl): bool {
            $confirmUrl = $mail->confirmUrl;

            return true;
        });

        $this->assertIsString($confirmUrl);
        preg_match('#/confirmaciones/correo/inversion/([a-fA-F0-9]+)#', $confirmUrl, $matches);

        $this->actingAs($investor)
            ->get(route('confirm.email.investment', ['token' => $matches[1]]))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('confirmations/investment')
                ->where('pending_approval', true));

        Mail::assertNotSent(AdminInvestmentPendingMail::class);
    }

    public function test_withdrawal_notifies_admins_only_after_investor_email_confirmation(): void
    {
        [, $investor, $admin, $wallet] = $this->seededActiveInvestmentWithDeposit();
        Mail::fake();

        $this->actingAs($investor)
            ->post(route('balances.withdrawals.store'), [
                'amount' => '100',
                'user_wallet_id' => $wallet->id,
            ])
            ->assertRedirect(route('balances.index'));

        $this->assertDatabaseHas('withdrawal_requests', [
            'user_id' => $investor->id,
            'status' => WithdrawalRequestStatus::PendingEmail->value,
        ]);

        Mail::assertNotSent(AdminWithdrawalRequestedMail::class);

        $confirmUrl = null;
        Mail::assertSent(ConfirmWithdrawalMail::class, function (ConfirmWithdrawalMail $mail) use (&$confirmUrl): bool {
            $confirmUrl = $mail->confirmUrl;

            return true;
        });

        $this->assertIsString($confirmUrl);
        $this->assertSame(1, preg_match('#/confirmaciones/correo/retiro/([a-fA-F0-9]+)#', $confirmUrl, $matches));

        $this->actingAs($investor)
            ->get(route('confirm.email.withdrawal', ['token' => $matches[1]]))
            ->assertRedirect(route('balances.index'));

        $this->assertDatabaseHas('withdrawal_requests', [
            'user_id' => $investor->id,
            'status' => WithdrawalRequestStatus::Pending->value,
        ]);

        Mail::assertSent(AdminWithdrawalRequestedMail::class, function (AdminWithdrawalRequestedMail $mail) use ($admin, $investor): bool {
            return $mail->hasTo($admin->email)
                && $mail->hasSubject('Nuevo retiro solicitado')
                && $mail->withdrawalRequest->user_id === $investor->id;
        });
    }

    public function test_withdrawal_mail_is_skipped_when_disabled(): void
    {
        [, $investor, , $wallet] = $this->seededActiveInvestmentWithDeposit();
        AdminNotificationSetting::current()->update([
            'notify_withdrawal_requested' => false,
        ]);
        Mail::fake();

        $this->actingAs($investor)
            ->post(route('balances.withdrawals.store'), [
                'amount' => '80',
                'user_wallet_id' => $wallet->id,
            ])
            ->assertRedirect(route('balances.index'));

        $confirmUrl = null;
        Mail::assertSent(ConfirmWithdrawalMail::class, function (ConfirmWithdrawalMail $mail) use (&$confirmUrl): bool {
            $confirmUrl = $mail->confirmUrl;

            return true;
        });

        $this->assertIsString($confirmUrl);
        preg_match('#/confirmaciones/correo/retiro/([a-fA-F0-9]+)#', $confirmUrl, $matches);

        $this->actingAs($investor)
            ->get(route('confirm.email.withdrawal', ['token' => $matches[1]]))
            ->assertRedirect(route('balances.index'));

        Mail::assertNotSent(AdminWithdrawalRequestedMail::class);
    }

    public function test_kyc_submission_notifies_admins(): void
    {
        Storage::fake('kyc');
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->kycAwaitingSubmission()->create();
        Mail::fake();

        $this->actingAs($investor)
            ->post(route('kyc.store'), $this->kycPayload())
            ->assertRedirect(route('kyc.show'));

        $this->assertSame(UserKycStatus::PendingReview, $investor->fresh()->kyc_status);

        Mail::assertSent(AdminKycSubmittedMail::class, function (AdminKycSubmittedMail $mail) use ($admin, $investor): bool {
            return $mail->hasTo($admin->email)
                && $mail->hasSubject('Nueva solicitud de verificación KYC')
                && $mail->user->is($investor);
        });
    }

    public function test_kyc_mail_is_skipped_when_disabled(): void
    {
        Storage::fake('kyc');
        User::factory()->admin()->create();
        $investor = User::factory()->kycAwaitingSubmission()->create();
        AdminNotificationSetting::current()->update([
            'notify_kyc_submitted' => false,
        ]);
        Mail::fake();

        $this->actingAs($investor)
            ->post(route('kyc.store'), $this->kycPayload())
            ->assertRedirect(route('kyc.show'));

        Mail::assertNotSent(AdminKycSubmittedMail::class);
    }

    /**
     * @return array<string, mixed>
     */
    private function enabledPayload(): array
    {
        return [
            'notify_new_user_registered' => true,
            'notify_investment_pending' => true,
            'notify_withdrawal_requested' => true,
            'notify_kyc_submitted' => true,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function kycPayload(): array
    {
        return [
            'legal_first_name' => 'Ada',
            'legal_last_name' => 'Investor',
            'date_of_birth' => '1991-06-01',
            'document_type' => 'national_id',
            'document_number' => 'ID-991',
            'document_issuing_country' => 'EC',
            'country_of_residence' => 'EC',
            'address_line1' => 'Av. Ejemplo 1',
            'city' => 'Quito',
            'region_state' => 'Pichincha',
            'postal_code' => '170101',
            'document_front' => UploadedFile::fake()->image('frente.jpg'),
        ];
    }

    /**
     * @return array{Investment, User, User, UserWallet}
     */
    private function seededActiveInvestmentWithDeposit(): array
    {
        InvestmentSetting::current();
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 300,
            'duration_months' => 6,
            'monthly_return_snapshot' => 5,
            'txid' => 'tx-notify-wr',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => now(),
            'confirmed_by' => $admin->id,
            'capital_balance' => 300,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 300,
            'balance_after' => 300,
            'platform_month_accrual_id' => null,
            'investment_month_index' => null,
            'applied_percent' => null,
            'withdrawal_request_id' => null,
        ]);

        $wallet = UserWallet::query()->create([
            'user_id' => $investor->id,
            'currency_network' => 'TRC20 USDT',
            'address' => 'TTestWalletNotify123',
            'label' => 'Main',
            'is_default' => true,
        ]);

        return [$investment, $investor, $admin, $wallet];
    }
}
