<?php

namespace Modules\Vrc\Tests\Unit;

use Illuminate\Container\Container;
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Support\Facades\Facade;
use Mockery;
use Modules\Vrc\Entities\VrcRepairOrder;
use Modules\Vrc\Entities\VrcRepairOrderService as RoServiceLine;
use Modules\Vrc\Services\Contracts\AuditServiceContract;
use Modules\Vrc\Services\Contracts\TaxServiceContract;
use Modules\Vrc\Services\Finance\TotalsBuilder;
use Modules\Vrc\Services\RepairOrder\InvalidTransitionException;
use Modules\Vrc\Services\RepairOrder\RepairOrderService;
use Modules\Vrc\Services\RepairOrder\RepairOrderStateMachine;
use Modules\Vrc\Services\RepairOrder\TransitionGuardException;
use PHPUnit\Framework\TestCase;

/**
 * Verifies the RepairOrderService transition graph + data guards (§4.3/§6.4)
 * and totals recompute, against an in-memory SQLite DB. DB::transaction is
 * available via the booted Capsule connection.
 */
class RepairOrderServiceTest extends TestCase
{
    private RepairOrderService $svc;

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

        $capsule = new Capsule;
        $capsule->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '']);
        $capsule->setAsGlobal();
        $capsule->bootEloquent();

        // Wire a minimal container so the service's DB::transaction(...),
        // now() and __() resolve without a full Laravel app.
        $container = new Container;
        $container->instance('db', $capsule->getDatabaseManager());
        $container->instance('translator', new class {
            public function get($key, array $replace = [], $locale = null) { return $key; }
            public function trans($key, array $replace = [], $locale = null) { return $key; }
        });
        Container::setInstance($container);
        Facade::clearResolvedInstances();
        Facade::setFacadeApplication($container);

        $this->schema($capsule);

        $machine = new RepairOrderStateMachine($this->transitions(), 'draft');
        $tax = new class implements TaxServiceContract {
            public function percentageFor(int $b, ?int $t): float { return $t === 1 ? 14.0 : 0.0; }
        };
        $audit = Mockery::mock(AuditServiceContract::class);
        $audit->shouldReceive('log')->andReturnNull();
        $util = Mockery::mock(\App\Utils\Util::class);

        $this->svc = new RepairOrderService($machine, new TotalsBuilder($tax), $audit, $util);
    }

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    private function transitions(): array
    {
        return [
            'draft' => ['checked_in', 'cancelled'],
            'checked_in' => ['in_progress', 'cancelled'],
            'in_progress' => ['quality_check', 'cancelled'],
            'quality_check' => ['ready_for_pickup'],
            'ready_for_pickup' => ['invoiced'],
            'invoiced' => ['completed'],
            'completed' => [], 'cancelled' => [],
        ];
    }

    private function schema(Capsule $c): void
    {
        $c->schema()->create('vrc_repair_orders', function ($t) {
            $t->increments('id'); $t->integer('business_id'); $t->integer('erp_customer_id')->nullable();
            $t->integer('vehicle_id')->nullable(); $t->string('number')->nullable(); $t->string('status')->default('draft');
            $t->dateTime('checked_in_at')->nullable(); $t->dateTime('work_started_at')->nullable(); $t->dateTime('completed_at')->nullable();
            $t->decimal('subtotal', 15, 2)->default(0); $t->decimal('discount_total', 15, 2)->default(0);
            $t->decimal('tax_total', 15, 2)->default(0); $t->decimal('grand_total', 15, 2)->default(0);
            $t->timestamps(); $t->softDeletes();
        });
        $c->schema()->create('vrc_repair_order_services', function ($t) {
            $t->increments('id'); $t->integer('business_id'); $t->integer('repair_order_id');
            $t->string('description'); $t->decimal('qty', 8, 2)->default(1); $t->decimal('unit_price', 15, 2)->default(0);
            $t->decimal('discount', 15, 2)->default(0); $t->string('discount_type')->default('fixed');
            $t->integer('tax_rate_id')->nullable(); $t->decimal('line_total', 15, 2)->default(0);
            $t->string('status')->default('pending'); $t->integer('assigned_technician_id')->nullable();
            $t->integer('sort_order')->default(0); $t->timestamps();
        });
        $c->schema()->create('vrc_repair_order_parts', function ($t) {
            $t->increments('id'); $t->integer('business_id'); $t->integer('repair_order_id');
            $t->string('description'); $t->decimal('qty', 8, 2)->default(1); $t->decimal('unit_price', 15, 2)->default(0);
            $t->decimal('discount', 15, 2)->default(0); $t->string('discount_type')->default('fixed');
            $t->integer('tax_rate_id')->nullable(); $t->decimal('line_total', 15, 2)->default(0);
            $t->integer('sort_order')->default(0); $t->timestamps();
        });
        $c->schema()->create('vrc_repair_order_status_history', function ($t) {
            $t->increments('id'); $t->integer('business_id'); $t->integer('repair_order_id');
            $t->string('from_status')->nullable(); $t->string('to_status'); $t->integer('actor_id')->nullable();
            $t->string('reason')->nullable(); $t->text('meta')->nullable(); $t->dateTime('created_at')->nullable();
        });
    }

    private function order(string $status = 'checked_in'): VrcRepairOrder
    {
        return VrcRepairOrder::create(['business_id' => 1, 'vehicle_id' => 1, 'erp_customer_id' => 1, 'status' => $status]);
    }

    public function test_illegal_transition_rejected(): void
    {
        $ro = $this->order('checked_in');
        $this->expectException(InvalidTransitionException::class);
        $this->svc->transition($ro, 'completed', 1);
    }

    public function test_in_progress_requires_technician(): void
    {
        $ro = $this->order('checked_in');
        $this->expectException(TransitionGuardException::class);
        $this->svc->transition($ro, 'in_progress', 1);
    }

    public function test_in_progress_allowed_with_technician(): void
    {
        $ro = $this->order('checked_in');
        RoServiceLine::create(['business_id' => 1, 'repair_order_id' => $ro->id, 'description' => 'X', 'assigned_technician_id' => 9]);
        $ro->load('services');
        $this->svc->transition($ro, 'in_progress', 1);
        $this->assertSame('in_progress', $ro->fresh()->status);
        $this->assertNotNull($ro->fresh()->work_started_at);
    }

    public function test_quality_check_blocked_when_services_open(): void
    {
        $ro = $this->order('in_progress');
        RoServiceLine::create(['business_id' => 1, 'repair_order_id' => $ro->id, 'description' => 'X', 'status' => 'pending', 'assigned_technician_id' => 9]);
        $this->expectException(TransitionGuardException::class);
        $this->svc->transition($ro, 'quality_check', 1);
    }

    public function test_transition_writes_history(): void
    {
        $ro = $this->order('checked_in');
        RoServiceLine::create(['business_id' => 1, 'repair_order_id' => $ro->id, 'description' => 'X', 'status' => 'done', 'assigned_technician_id' => 9]);
        $ro->load('services');
        $this->svc->transition($ro, 'in_progress', 7, 'go');
        $this->assertSame(1, $ro->statusHistory()->count());
        $this->assertSame('in_progress', $ro->statusHistory()->first()->to_status);
    }

    public function test_recompute_totals_from_lines(): void
    {
        $ro = $this->order('checked_in');
        RoServiceLine::create(['business_id' => 1, 'repair_order_id' => $ro->id, 'description' => 'Labor', 'qty' => 1, 'unit_price' => 100, 'tax_rate_id' => 1]);
        $this->svc->recomputeTotals($ro);
        $ro->refresh();
        $this->assertEquals(100, (float) $ro->subtotal);
        $this->assertEquals(14, (float) $ro->tax_total);
        $this->assertEquals(114, (float) $ro->grand_total);
    }
}
