<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\CollectorCreditOrder;
use App\Models\Company;
use App\Models\Credit;
use App\Models\User;
use App\Services\CollectorSyncService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Regresión #129 (Ronda 3, item 2): CollectorSyncService (usado por los cambios
 * de estado AUTOMÁTICOS — reconciliación nocturna, SyncCreditStatusJob) hacía un
 * delete()/appendCredit() crudos, a diferencia de CreditObserver (cambios
 * síncronos), que guarda la posición histórica del cliente y reindexa
 * sort_order. Eso dejaba huecos en sort_order y perdía el "volver al mismo slot
 * de ruta" para créditos cerrados/reactivados automáticamente.
 *
 * Ahora ambos delegan en CollectorCreditOrderService::removeAndReindex() /
 * insertRespectingHistory(), así que CollectorSyncService tiene la misma
 * paridad.
 */
class CollectorSyncServiceRouteHistoryTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $collector;

    protected function setUp(): void
    {
        parent::setUp();
        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);
        $this->company = Company::factory()->create();
        $this->collector = User::factory()->create(['company_id' => $this->company->id]);
        $this->collector->assignRole('collector');
    }

    #[Test]
    public function auto_closing_a_credit_saves_history_and_reindexes_without_gaps(): void
    {
        // Tres créditos activos del mismo collector, distintos clientes → 3 filas 1,2,3.
        $credits = collect(range(1, 3))->map(fn () => Credit::factory()->create([
            'company_id' => $this->company->id,
            'collector_user_id' => $this->collector->id,
            'status' => Credit::STATUS_ACTIVE,
        ]));

        $middle = $credits[1];
        $middleClientId = $middle->client_id;

        $this->assertSame(3, CollectorCreditOrder::where('user_id', $this->collector->id)->count());

        app(CollectorSyncService::class)->handleStatusChange($middle, Credit::STATUS_ACTIVE, Credit::STATUS_PAID);

        // La fila del crédito cerrado se eliminó y el resto quedó reindexado 1..2 (sin huecos).
        $this->assertFalse(CollectorCreditOrder::where('credit_id', $middle->id)->exists());
        $orders = CollectorCreditOrder::where('user_id', $this->collector->id)
            ->orderBy('sort_order')->pluck('sort_order')->all();
        $this->assertSame([1, 2], $orders, 'sort_order debe quedar secuencial 1..N, sin huecos.');

        // Se guardó la posición histórica del cliente para una futura reactivación.
        $this->assertDatabaseHas('collector_client_route_history', [
            'company_id' => $this->company->id,
            'user_id' => $this->collector->id,
            'client_id' => $middleClientId,
        ]);
    }

    #[Test]
    public function auto_reactivating_a_credit_restores_its_historical_slot(): void
    {
        $credits = collect(range(1, 3))->map(fn () => Credit::factory()->create([
            'company_id' => $this->company->id,
            'collector_user_id' => $this->collector->id,
            'status' => Credit::STATUS_ACTIVE,
        ]));

        $target = $credits[1];

        // Cerrar (guarda historial en sort_order=2) y luego reactivar.
        app(CollectorSyncService::class)->handleStatusChange($target, Credit::STATUS_ACTIVE, Credit::STATUS_PAID);
        app(CollectorSyncService::class)->handleStatusChange($target, Credit::STATUS_PAID, Credit::STATUS_ACTIVE);

        $this->assertDatabaseHas('collector_credit_order', [
            'company_id' => $this->company->id,
            'user_id' => $this->collector->id,
            'credit_id' => $target->id,
            'sort_order' => 2,
        ]);
    }

    #[Test]
    public function auto_reactivation_works_even_when_collector_has_no_existing_route_rows(): void
    {
        // Antes: appendCredit() hacía early-return si el collector no tenía NINGUNA
        // fila de orden aún, así que el crédito ni siquiera entraba a la ruta.
        $credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'collector_user_id' => $this->collector->id,
            'status' => Credit::STATUS_ACTIVE,
        ]);
        // Elimina la fila que CreditObserver::created() insertó, para simular
        // "collector sin ninguna fila de orden" antes de la reactivación automática.
        DB::table('collector_credit_order')->where('credit_id', $credit->id)->delete();

        app(CollectorSyncService::class)->handleStatusChange($credit, Credit::STATUS_ACTIVE, Credit::STATUS_PAID);
        app(CollectorSyncService::class)->handleStatusChange($credit, Credit::STATUS_PAID, Credit::STATUS_ACTIVE);

        $this->assertDatabaseHas('collector_credit_order', [
            'company_id' => $this->company->id,
            'user_id' => $this->collector->id,
            'credit_id' => $credit->id,
        ]);
    }
}
