<?php

namespace Tests\Feature;

use App\Enums\AccrualFrequency;
use App\Enums\ForexImportStagingStatus;
use App\Enums\InvestmentStatus;
use App\Enums\InvestmentTransactionType;
use App\Enums\UserKycStatus;
use App\Models\ForexImportStagingItem;
use App\Models\Investment;
use App\Models\InvestmentSetting;
use App\Models\InvestmentTransaction;
use App\Models\User;
use App\Services\ForexCsvImportService;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ForexCsvImportTest extends TestCase
{
    use RefreshDatabase;

    private function writeTempCsv(string $contents): string
    {
        $path = tempnam(sys_get_temp_dir(), 'forexcsv');
        $this->assertNotFalse($path);
        file_put_contents($path, $contents);

        return $path;
    }

    private function headerLine(): string
    {
        return 'nombre,apellidos,email,telefono,pais,wallet trc20 ,fecha inicio ,Duración,fecha finalización,monto plan,saldo disponible/ganancias,saldo total,saldo bloqueado,saldo pagado'."\n";
    }

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

        $this->actingAs($admin)
            ->get(route('admin.home'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page->where('adminNav.forex_import_pending', false));
    }

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

        ForexImportStagingItem::query()->create([
            'status' => ForexImportStagingStatus::Pending,
            'row_checksum' => hash('sha256', 'nav-test-row'),
            'csv_record_index' => 1,
            'email_hint' => 'pending@example.com',
            'amount' => 100,
            'duration_months' => 6,
            'monthly_return_snapshot' => 4,
        ]);

        $this->actingAs($admin)
            ->get(route('admin.home'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page->where('adminNav.forex_import_pending', true));
    }

    public function test_import_only_queues_staging_when_user_exists(): void
    {
        InvestmentSetting::current();

        User::factory()->create(['email' => 'match@example.com']);
        $csv = $this->headerLine();
        $csv .= 'Ana,Prueba,match@example.com,,MX,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        $stats = app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->assertSame(1, $stats['anchors_seen']);
        $this->assertSame(0, $stats['continuation_rows']);
        $this->assertSame(1, $stats['plans_queued_for_review']);

        $this->assertSame(0, Investment::query()->count());
        $this->assertSame(1, ForexImportStagingItem::query()->where('status', ForexImportStagingStatus::Pending)->count());
    }

    public function test_admin_approve_group_creates_active_investments_with_deposit(): void
    {
        InvestmentSetting::current();

        $user = User::factory()->create(['email' => 'approve@example.com']);
        $admin = User::factory()->admin()->create();

        $csv = $this->headerLine();
        $csv .= 'Zoe,Test,approve@example.com,,MX,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->assertSame(0, Investment::query()->count());

        $this->actingAs($admin)
            ->post(route('admin.forex-import.approve'), ['email' => 'approve@example.com'])
            ->assertRedirect(route('admin.forex-import.index'));

        $inv = Investment::query()->where('user_id', $user->id)->first();
        $this->assertNotNull($inv);
        $this->assertSame(InvestmentStatus::Active, $inv->status);
        $this->assertSame('2026-01-15', $inv->confirmed_at?->toDateString());
        $this->assertSame('2026-07-15', $inv->maturity_at?->toDateString());
        $this->assertSame($admin->id, $inv->confirmed_by);
        $this->assertStringStartsWith(ForexCsvImportService::IMPORT_TXID_PREFIX, $inv->txid);

        $this->assertDatabaseHas('investment_transactions', [
            'investment_id' => $inv->id,
            'type' => InvestmentTransactionType::Deposit->value,
            'amount' => '100.00',
        ]);

        $this->assertSame(0, ForexImportStagingItem::query()->where('status', ForexImportStagingStatus::Pending)->count());
    }

    public function test_continuation_row_queues_second_plan_for_same_email(): void
    {
        InvestmentSetting::current();

        User::factory()->create(['email' => 'multi@example.com']);
        $csv = $this->headerLine();
        $csv .= 'Luis,Uno,multi@example.com,,MX,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";
        $csv .= ',,,,,,2/1/2026,3 meses,5/1/2026,$50,,,,'."\n";

        $path = $this->writeTempCsv($csv);

        $stats = app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->assertSame(1, $stats['anchors_seen']);
        $this->assertSame(1, $stats['continuation_rows']);
        $this->assertSame(2, $stats['plans_queued_for_review']);

        $this->assertSame(2, ForexImportStagingItem::query()->where('email_hint', 'multi@example.com')->count());
        $this->assertSame(0, Investment::query()->count());
    }

    public function test_orphan_continuation_without_prior_anchor_is_skipped(): void
    {
        InvestmentSetting::current();

        User::factory()->create(['email' => 'nobody@example.com']);

        $csv = $this->headerLine();
        $csv .= ',,,,,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        $stats = app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->assertSame(0, $stats['anchors_seen']);
        $this->assertSame(1, $stats['continuation_rows']);
        $this->assertSame(1, $stats['orphan_skipped']);
        $this->assertSame(0, $stats['plans_queued_for_review']);
    }

    public function test_unknown_email_does_not_create_user_until_approve(): void
    {
        InvestmentSetting::current();

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

        $csv = $this->headerLine();
        $csv .= 'Pedro,Nuevo,brandnew@example.com,555,EC,,1/15/2026,6 meses,7/15/2026,$250,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        $stats = app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->assertSame(1, $stats['plans_queued_for_review']);
        $this->assertNull(User::query()->where('email', 'brandnew@example.com')->first());
        $this->assertSame(0, Investment::query()->count());

        $this->actingAs($admin)
            ->post(route('admin.forex-import.approve'), ['email' => 'brandnew@example.com'])
            ->assertRedirect(route('admin.forex-import.index'));

        $user = User::query()->where('email', 'brandnew@example.com')->first();
        $this->assertNotNull($user);
        $this->assertSame('Pedro', $user->first_name);
        $this->assertSame('Nuevo', $user->last_name);
        $this->assertSame('555', $user->contact_number);
        $this->assertSame('EC', $user->country);
        $this->assertNotNull($user->email_verified_at);
        $this->assertSame(UserKycStatus::Approved, $user->kyc_status);

        $this->assertDatabaseHas('investments', [
            'user_id' => $user->id,
            'amount' => '250.00',
            'status' => InvestmentStatus::Active->value,
        ]);

        $inv = Investment::query()->where('user_id', $user->id)->first();
        $this->assertNotNull($inv);
        $this->assertDatabaseHas('investment_transactions', [
            'investment_id' => $inv->id,
            'type' => InvestmentTransactionType::Deposit->value,
        ]);
    }

    public function test_admin_can_approve_manual_forex_staging_record(): void
    {
        InvestmentSetting::current();
        $settings = InvestmentSetting::current();

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

        $checksum = hash('sha256', 'manual-staging-test-'.uniqid('', true));

        $plannedStart = Carbon::parse('2026-01-10');
        $plannedMaturity = Carbon::parse('2027-01-10');

        ForexImportStagingItem::query()->create([
            'status' => ForexImportStagingStatus::Pending,
            'row_checksum' => $checksum,
            'csv_record_index' => 1,
            'email_hint' => 'legacy@example.com',
            'first_name' => 'Leg',
            'last_name' => 'Acy',
            'contact_number' => null,
            'country' => null,
            'wallet_address' => null,
            'amount' => 175.5,
            'duration_months' => 12,
            'confirmed_at' => $plannedStart,
            'maturity_at' => $plannedMaturity,
            'monthly_return_snapshot' => $settings->monthly_return,
            'payload' => null,
        ]);

        $this->assertNull(User::query()->where('email', 'legacy@example.com')->first());

        $this->actingAs($admin)
            ->post(route('admin.forex-import.approve'), ['email' => 'legacy@example.com'])
            ->assertRedirect(route('admin.forex-import.index'));

        $user = User::query()->where('email', 'legacy@example.com')->first();
        $this->assertNotNull($user);

        $this->assertDatabaseHas('investments', [
            'user_id' => $user->id,
            'amount' => '175.50',
            'duration_months' => 12,
            'status' => InvestmentStatus::Active->value,
        ]);

        $item = ForexImportStagingItem::query()->where('email_hint', 'legacy@example.com')->first();
        $this->assertNotNull($item);
        $this->assertSame(ForexImportStagingStatus::Imported, $item->status);

        $inv = Investment::query()->whereKey($item->investment_id)->first();
        $this->assertNotNull($inv);
        $this->assertSame($plannedStart->toDateString(), $inv->confirmed_at?->toDateString());
        $this->assertSame($plannedMaturity->toDateString(), $inv->maturity_at?->toDateString());
    }

    public function test_iso_8859_1_header_duracion_column_maps_correctly(): void
    {
        InvestmentSetting::current();

        $latin1Header = implode(',', [
            'nombre',
            'email',
            'fecha inicio ',
            "Duraci\xF3n",
            'fecha finalizaci\xF3n',
            'monto plan',
        ])."\n";

        $csv = $latin1Header;
        $csv .= 'Ana,ana-latin1@example.com,1/15/2026,6 meses,7/15/2026,$100'."\n";

        $path = $this->writeTempCsv($csv);
        $stats = app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->assertSame(1, $stats['plans_queued_for_review']);
        $this->assertSame(0, $stats['plans_parse_skipped']);
    }

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

        $header = 'nombre,apellidos,email,Teléfono,País,wallet trc20 ,fecha inicio ,Duración,fecha finalización,monto plan,saldo disponible/ganancias,saldo total,saldo bloqueado,saldo pagado'."\n";
        $row = 'Ana,Prueba,paccent@example.com,+593999,Ecuador,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";

        $path = $this->writeTempCsv($header.$row);
        app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $item = ForexImportStagingItem::query()->where('email_hint', 'paccent@example.com')->first();
        $this->assertNotNull($item);
        $this->assertSame('EC', $item->country);

        $this->actingAs($admin)
            ->post(route('admin.forex-import.approve'), ['email' => 'paccent@example.com'])
            ->assertRedirect(route('admin.forex-import.index'));

        $user = User::query()->where('email', 'paccent@example.com')->first();
        $this->assertNotNull($user);
        $this->assertSame('EC', $user->country);
        $this->assertSame('+593999', $user->contact_number);
    }

    public function test_forex_approve_applies_backdated_monthly_accruals(): void
    {
        $this->travelTo('2026-07-15 12:00:00');

        $settings = InvestmentSetting::current();
        $settings->update([
            'accrual_frequency' => AccrualFrequency::Monthly->value,
            'accrual_day_of_month' => 30,
            'monthly_return' => 3,
        ]);

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

        $csv = $this->headerLine();
        $csv .= 'Ana,Prueba,catchup-m@example.com,,MX,,1/15/2026,6 meses,7/15/2026,$1000,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $this->actingAs($admin)
            ->post(route('admin.forex-import.approve'), ['email' => 'catchup-m@example.com'])
            ->assertRedirect(route('admin.forex-import.index'));

        $inv = Investment::query()
            ->whereHas('user', static fn ($q) => $q->where('email', 'catchup-m@example.com'))
            ->first();

        $this->assertNotNull($inv);
        $this->assertGreaterThanOrEqual(
            1,
            InvestmentTransaction::query()
                ->where('investment_id', $inv->id)
                ->where('type', InvestmentTransactionType::PeriodAccrual)
                ->count(),
        );
        $this->assertGreaterThan(1000.01, (float) $inv->fresh()->capital_balance);

        $this->travelBack();
    }

    public function test_guest_cannot_access_forex_import_staging(): void
    {
        $this->get(route('admin.forex-import.index'))
            ->assertRedirect(route('login'));
    }

    public function test_guest_cannot_post_forex_import_approve(): void
    {
        $this->post(route('admin.forex-import.approve'), ['email' => 'x@example.com'])
            ->assertRedirect(route('login'));
    }

    public function test_admin_can_update_pending_staging_item(): void
    {
        InvestmentSetting::current();

        User::factory()->create(['email' => 'edit-staging@example.com']);
        $admin = User::factory()->admin()->create();

        $csv = $this->headerLine();
        $csv .= 'Ana,Prueba,edit-staging@example.com,,MX,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $item = ForexImportStagingItem::query()->where('email_hint', 'edit-staging@example.com')->first();
        $this->assertNotNull($item);
        $oldChecksum = $item->row_checksum;

        $this->actingAs($admin)
            ->patch(route('admin.forex-import.update', $item), [
                'email_hint' => 'edit-staging@example.com',
                'first_name' => 'Bea',
                'last_name' => 'Updated',
                'contact_number' => '+1 555',
                'country' => 'CO',
                'wallet_address' => 'TTestWallet123',
                'amount' => '250.50',
                'duration_months' => 12,
                'confirmed_at' => '2026-02-01',
                'maturity_at' => '2027-02-01',
                'monthly_return_snapshot' => '3.5',
            ])
            ->assertRedirect(route('admin.forex-import.index'));

        $item->refresh();
        $this->assertSame('250.50', (string) $item->amount);
        $this->assertSame(12, $item->duration_months);
        $this->assertSame('Bea', $item->first_name);
        $this->assertNotSame($oldChecksum, $item->row_checksum);
    }

    public function test_admin_can_destroy_pending_staging_item(): void
    {
        InvestmentSetting::current();

        User::factory()->create(['email' => 'del-staging@example.com']);
        $admin = User::factory()->admin()->create();

        $csv = $this->headerLine();
        $csv .= 'Ana,Prueba,del-staging@example.com,,MX,,1/15/2026,6 meses,7/15/2026,$100,,,,'."\n";

        $path = $this->writeTempCsv($csv);
        app(ForexCsvImportService::class)->importFromPath($path, false);
        @unlink($path);

        $item = ForexImportStagingItem::query()->where('email_hint', 'del-staging@example.com')->first();
        $this->assertNotNull($item);
        $id = $item->id;

        $this->actingAs($admin)
            ->delete(route('admin.forex-import.destroy', $item))
            ->assertRedirect(route('admin.forex-import.index'));

        $this->assertDatabaseMissing('forex_import_staging_items', ['id' => $id]);
    }
}
