<?php

declare(strict_types=1);

namespace Tests\Feature\Pwa;

use App\Models\Company;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Laravel\Sanctum\Sanctum;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

class FinancialControllerTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $admin;

    private User $collector;

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

        foreach (['admin', 'supervisor', 'collector'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }

        $plan = Plan::factory()->create(['has_pwa_access' => true]);
        $this->company = Company::factory()->create();

        Subscription::factory()->active()->create([
            'company_id' => $this->company->id,
            'plan_id' => $plan->id,
            'ends_at' => now()->addYear(),
        ]);

        $this->admin = User::factory()->create(['company_id' => $this->company->id]);
        $this->admin->assignRole('admin');

        $this->collector = User::factory()->create(['company_id' => $this->company->id]);
        $this->collector->assignRole('collector');
    }

    /** Crea incomes directamente en DB para evitar hooks de validación del modelo. */
    private function createIncome(array $attrs = []): int
    {
        return DB::table('incomes')->insertGetId(array_merge([
            'company_id' => $this->company->id,
            'user_id' => $this->admin->id,
            'amount' => '100000.00',
            'category' => 'service_fee',
            'operation_date' => now()->toDateString(),
            'affects_capital' => false,
            'affects_profit' => true,
            'is_locked' => false,
            'created_at' => now(),
            'updated_at' => now(),
        ], $attrs));
    }

    /** Crea expenses directamente en DB para evitar hooks de validación del modelo. */
    private function createExpense(array $attrs = []): int
    {
        return DB::table('expenses')->insertGetId(array_merge([
            'company_id' => $this->company->id,
            'user_id' => $this->admin->id,
            'amount' => '50000.00',
            'category' => 'salary_wages',
            'expense_type' => 'operational',
            'affects_cash' => true,
            'affects_profit' => true,
            'reduces_capital' => false,
            'is_locked' => false,
            'requires_approval' => false,
            'approval_status' => 'approved',
            'operation_date' => now()->toDateString(),
            'created_at' => now(),
            'updated_at' => now(),
        ], $attrs));
    }

    public function test_returns_403_for_non_admin(): void
    {
        Sanctum::actingAs($this->collector->fresh());

        $this->getJson('/api/pwa/financials')->assertStatus(403);
    }

    public function test_returns_summary_and_movements_structure(): void
    {
        Sanctum::actingAs($this->admin->fresh());

        $this->getJson('/api/pwa/financials')
            ->assertStatus(200)
            ->assertJsonStructure([
                'summary' => ['month', 'income_total', 'expense_total', 'net_flow', 'direction'],
                'movements',
                'meta' => ['total_incomes', 'total_expenses', 'month_start', 'month_end'],
            ]);
    }

    public function test_expenses_appear_in_movements_when_incomes_have_higher_ids(): void
    {
        // Simula el escenario real: incomes tienen IDs altos (ej. 784-890)
        // y expenses IDs bajos (ej. 209-245). El bug del doble sortByDesc
        // excluía los expenses del take(100).
        Sanctum::actingAs($this->admin->fresh());

        // Creamos primero múltiples incomes para que tengan IDs más altos
        for ($i = 0; $i < 5; $i++) {
            $this->createIncome();
        }

        // Luego creamos un expense (tendrá ID más bajo)
        $this->createExpense(['amount' => '75000.00']);

        $response = $this->getJson('/api/pwa/financials')->assertStatus(200);

        $movements = $response->json('movements');
        $types = array_column($movements, 'type');

        $this->assertContains('income', $types, 'Los ingresos deben aparecer en movements');
        $this->assertContains('expense', $types, 'Los gastos deben aparecer en movements');
    }

    public function test_expense_totals_match_movements(): void
    {
        Sanctum::actingAs($this->admin->fresh());

        $this->createIncome(['amount' => '200000.00']);
        $this->createExpense(['amount' => '80000.00']);

        $response = $this->getJson('/api/pwa/financials')->assertStatus(200);

        $summary = $response->json('summary');
        $this->assertGreaterThanOrEqual(200000, $summary['income_total']);
        $this->assertGreaterThanOrEqual(80000, $summary['expense_total']);
    }

    public function test_expense_movements_include_is_capital_flag(): void
    {
        Sanctum::actingAs($this->admin->fresh());

        // Gasto operativo → is_capital debe ser false
        $this->createExpense(['category' => 'salary_wages']);

        // Retiro de capital → is_capital debe ser true
        $this->createExpense([
            'category' => 'profit_withdrawal',
            'expense_type' => 'profit_withdrawal',
            'requires_approval' => false,
            'approval_status' => 'approved',
        ]);

        $response = $this->getJson('/api/pwa/financials')->assertStatus(200);

        $movements = collect($response->json('movements'));

        $operational = $movements->firstWhere('category', 'salary_wages');
        $this->assertNotNull($operational, 'El gasto operativo debe aparecer en movements');
        $this->assertFalse($operational['is_capital'], 'salary_wages no es transacción de capital');

        $retiro = $movements->firstWhere('category', 'profit_withdrawal');
        $this->assertNotNull($retiro, 'El retiro de utilidades debe aparecer en movements');
        $this->assertTrue($retiro['is_capital'], 'profit_withdrawal es transacción de capital');
    }

    public function test_category_labels_do_not_contain_legacy_suffix(): void
    {
        Sanctum::actingAs($this->admin->fresh());

        // Categorías legacy de income que antes mostraban "(legacy)"
        $this->createIncome(['category' => 'payment']);
        $this->createExpense(['category' => 'operational', 'expense_type' => 'operational']);

        $response = $this->getJson('/api/pwa/financials')->assertStatus(200);

        $movements = $response->json('movements');

        foreach ($movements as $mov) {
            $this->assertStringNotContainsString(
                '(legacy)',
                $mov['category_label'],
                "La categoría '{$mov['category']}' no debe mostrar '(legacy)' al usuario: '{$mov['category_label']}'"
            );
        }
    }

    public function test_pending_approval_expenses_are_excluded(): void
    {
        Sanctum::actingAs($this->admin->fresh());

        $this->createExpense([
            'amount' => '999999.00',
            'requires_approval' => true,
            'approval_status' => 'pending',
        ]);

        $response = $this->getJson('/api/pwa/financials')->assertStatus(200);

        $summary = $response->json('summary');
        $this->assertLessThan(999999, $summary['expense_total'],
            'Los gastos pendientes de aprobación no deben sumarse al total');
    }

    public function test_movements_sorted_by_date_desc(): void
    {
        Sanctum::actingAs($this->admin->fresh());

        $this->createIncome(['operation_date' => now()->subDays(2)->toDateString()]);
        $this->createIncome(['operation_date' => now()->toDateString()]);
        $this->createExpense(['operation_date' => now()->subDay()->toDateString()]);

        $response = $this->getJson('/api/pwa/financials')->assertStatus(200);

        $movements = $response->json('movements');
        $this->assertNotEmpty($movements);

        // Verificar que están ordenados por fecha descendente
        $dates = array_column($movements, 'date');
        $sortedDates = $dates;
        rsort($sortedDates);

        $this->assertEquals($sortedDates, $dates, 'Los movimientos deben estar ordenados por fecha descendente');
    }
}
