<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\Company;
use App\Models\Expense;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Str;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Tests para POST /api/pwa/sync/expenses
 *
 * Cubre:
 *   - Batch válido → processed: N, failed: 0
 *   - Duplicate idempotency_key → status: "ok" sin duplicar fila
 *   - Batch mixto (1 inválido) → el inválido falla solo, los válidos se procesan
 *   - Batch vacío → processed: 0, failed: 0 sin error
 */
class SyncExpensesTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    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();
        $this->company = Company::factory()->create();
        Subscription::factory()->create([
            'company_id' => $this->company->id,
            'plan_id' => $plan->id,
            'status' => Subscription::STATUS_ACTIVE,
            'is_active' => true,
            'starts_at' => now()->subMonth(),
            'ends_at' => now()->addYear(),
        ]);

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

    // ------------------------------------------------------------------
    // Helpers
    // ------------------------------------------------------------------

    private function makeExpensePayload(array $overrides = []): array
    {
        return array_merge([
            'idempotency_key' => (string) Str::uuid(),
            'amount' => 150.00,
            'category' => 'transportation',
            'notes' => 'Combustible visita',
            'date' => now()->toDateString(),
        ], $overrides);
    }

    // ------------------------------------------------------------------
    // Tests
    // ------------------------------------------------------------------

    #[Test]
    public function batch_of_three_valid_expenses_returns_processed_3_failed_0(): void
    {
        $expenses = [
            $this->makeExpensePayload(['amount' => 100.00, 'category' => 'transportation']),
            $this->makeExpensePayload(['amount' => 200.00, 'category' => 'commission']),
            $this->makeExpensePayload(['amount' => 50.00,  'category' => 'office_supplies']),
        ];

        $response = $this->actingAs($this->collector, 'sanctum')
            ->postJson('/api/pwa/sync/expenses', ['expenses' => $expenses]);

        $response->assertOk()
            ->assertJsonPath('processed', 3)
            ->assertJsonPath('failed', 0);

        $results = $response->json('results');
        $this->assertCount(3, $results);
        foreach ($results as $result) {
            $this->assertSame('ok', $result['status']);
            $this->assertArrayHasKey('expense_id', $result);
        }

        // Confirmar que los 3 registros existen en DB
        foreach ($expenses as $expense) {
            $this->assertDatabaseHas('expenses', [
                'company_id' => $this->company->id,
                'idempotency_key' => $expense['idempotency_key'],
                'category' => $expense['category'],
            ]);
        }
    }

    #[Test]
    public function duplicate_idempotency_key_returns_ok_without_creating_duplicate_row(): void
    {
        $expense = $this->makeExpensePayload(['amount' => 75.00, 'category' => 'rent']);

        // Primera petición: crea el gasto
        $this->actingAs($this->collector, 'sanctum')
            ->postJson('/api/pwa/sync/expenses', ['expenses' => [$expense]])
            ->assertOk()
            ->assertJsonPath('processed', 1);

        $countAfterFirst = Expense::withoutGlobalScopes()
            ->where('company_id', $this->company->id)
            ->where('idempotency_key', $expense['idempotency_key'])
            ->count();

        // Segunda petición con la misma key: debe devolver ok (idempotente)
        $response = $this->actingAs($this->collector, 'sanctum')
            ->postJson('/api/pwa/sync/expenses', ['expenses' => [$expense]]);

        $response->assertOk()
            ->assertJsonPath('processed', 1)
            ->assertJsonPath('failed', 0)
            ->assertJsonPath('results.0.status', 'ok');

        // La fila no se duplicó
        $countAfterSecond = Expense::withoutGlobalScopes()
            ->where('company_id', $this->company->id)
            ->where('idempotency_key', $expense['idempotency_key'])
            ->count();

        $this->assertSame($countAfterFirst, $countAfterSecond);
        $this->assertSame(1, $countAfterSecond);
    }

    #[Test]
    public function batch_with_one_invalid_expense_fails_that_one_but_processes_the_rest(): void
    {
        $validA = $this->makeExpensePayload(['amount' => 120.00, 'category' => 'utilities']);
        $invalid = $this->makeExpensePayload(['category' => 'disbursement']); // categoría no permitida
        $validB = $this->makeExpensePayload(['amount' => 60.00, 'category' => 'bank_fees']);

        // La validación es por ítem: el inválido falla solo, los otros dos se guardan.
        $response = $this->actingAs($this->collector, 'sanctum')
            ->postJson('/api/pwa/sync/expenses', ['expenses' => [$validA, $invalid, $validB]]);

        $response->assertOk()
            ->assertJsonPath('processed', 2)
            ->assertJsonPath('failed', 1)
            ->assertJsonPath('results.1.index', 1)
            ->assertJsonPath('results.1.status', 'error');

        $this->assertDatabaseHas('expenses', ['idempotency_key' => $validA['idempotency_key']]);
        $this->assertDatabaseHas('expenses', ['idempotency_key' => $validB['idempotency_key']]);
        $this->assertDatabaseMissing('expenses', ['idempotency_key' => $invalid['idempotency_key']]);
    }

    #[Test]
    public function collector_synced_expense_is_pending_approval(): void
    {
        $expense = $this->makeExpensePayload(['amount' => 100.00, 'category' => 'transportation']);

        $this->actingAs($this->collector, 'sanctum')
            ->postJson('/api/pwa/sync/expenses', ['expenses' => [$expense]])
            ->assertOk()
            ->assertJsonPath('processed', 1);

        // El gasto de un cobrador queda pendiente: no descuenta caja hasta aprobarse.
        $this->assertDatabaseHas('expenses', [
            'idempotency_key' => $expense['idempotency_key'],
            'requires_approval' => 1,
            'approval_status' => 'pending',
            'approved_by_user_id' => null,
        ]);
    }

    #[Test]
    public function empty_batch_returns_processed_0_failed_0_without_error(): void
    {
        $response = $this->actingAs($this->collector, 'sanctum')
            ->postJson('/api/pwa/sync/expenses', ['expenses' => []]);

        $response->assertOk()
            ->assertJsonPath('processed', 0)
            ->assertJsonPath('failed', 0)
            ->assertJsonStructure(['processed', 'failed', 'results', 'synced_at']);

        $this->assertSame([], $response->json('results'));
    }
}
