<?php

namespace Tests\Feature;

use App\Models\Central\Company;
use App\Models\Central\Plan;
use App\Models\Tenant\Client;
use App\Models\Tenant\Part;
use App\Models\Tenant\PartBatch;
use App\Models\Tenant\PartReservation;
use App\Models\Tenant\Vehicle;
use App\Models\Tenant\Warehouse;
use App\Models\Tenant\WarehouseEvent;
use App\Models\Tenant\WorkOrder;
use App\Models\Tenant\WorkOrderPart;
use App\Services\Warehouse\InsufficientStockException;
use App\Services\Warehouse\WarehouseService;
use App\Tenancy\TenantManager;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class WarehouseFifoTest extends TestCase
{
    use RefreshDatabase;

    private WarehouseService $svc;

    protected function setUp(): void
    {
        parent::setUp();
        $this->svc = app(WarehouseService::class);
    }

    public function test_receive_creates_batch_and_event(): void
    {
        $part = $this->makePart('alpha');

        $batch = $this->svc->receive($part, 10, 50.0);

        $this->assertEquals(10.0, (float) $batch->qty_in);
        $this->assertEquals(10.0, (float) $batch->qty_remaining);
        $this->assertEquals(50.0, (float) $batch->buy_price);

        $events = WarehouseEvent::where('part_id', $part->id)->get();
        $this->assertCount(1, $events);
        $this->assertEquals('receipt', $events[0]->type);
        $this->assertEquals(10.0, (float) $events[0]->qty_delta);

        $part->refresh();
        $this->assertEquals(10.0, (float) $part->qty);
    }

    public function test_issue_consumes_oldest_batches_first(): void
    {
        $part = $this->makePart('beta');

        // 3 batches received in chronological order with different prices.
        $b1 = $this->svc->receive($part, 5, 10.0, occurredAt: now()->subDays(3));
        $b2 = $this->svc->receive($part, 5, 20.0, occurredAt: now()->subDays(2));
        $b3 = $this->svc->receive($part, 5, 30.0, occurredAt: now()->subDays(1));

        // Issue 7 → should consume all of b1 (5) + 2 from b2.
        $events = $this->svc->issue($part, 7);

        $b1->refresh(); $b2->refresh(); $b3->refresh();
        $this->assertEquals(0.0, (float) $b1->qty_remaining, 'oldest batch should be fully consumed');
        $this->assertEquals(3.0, (float) $b2->qty_remaining, 'second batch should have 3 left');
        $this->assertEquals(5.0, (float) $b3->qty_remaining, 'newest batch untouched');

        // Two events written.
        $this->assertCount(2, $events);
    }

    public function test_issue_throws_when_insufficient_stock(): void
    {
        $part = $this->makePart('gamma');
        $this->svc->receive($part, 3, 10.0);

        $this->expectException(InsufficientStockException::class);
        $this->svc->issue($part, 5);
    }

    public function test_reservation_blocks_other_reservations_but_not_stock(): void
    {
        $part = $this->makePart('delta');
        $this->svc->receive($part, 10, 25.0);

        $wo = $this->makeWorkOrder($part);
        $wop = WorkOrderPart::create([
            'work_order_id' => $wo->id,
            'part_id' => $part->id,
            'name' => $part->name,
            'qty' => 6,
            'unit' => 'buc',
            'buy_price' => 25,
            'sell_price' => 40,
        ]);

        $part->refresh();
        $this->assertEquals(10.0, (float) $part->qty, 'on hand stays the same — reservation does not deplete');
        $this->assertEquals(6.0, (float) $part->qty_reserved, 'qty_reserved tracks reservation');

        $this->assertCount(1, PartReservation::where('work_order_part_id', $wop->id)->where('status', 'active')->get());
    }

    public function test_wo_done_consumes_reservations(): void
    {
        $part = $this->makePart('epsilon');
        $this->svc->receive($part, 10, 25.0);

        $wo = $this->makeWorkOrder($part);
        WorkOrderPart::create([
            'work_order_id' => $wo->id,
            'part_id' => $part->id,
            'name' => $part->name,
            'qty' => 4,
            'unit' => 'buc',
            'buy_price' => 25,
            'sell_price' => 40,
        ]);

        $wo->status = 'done';
        $wo->save();

        $part->refresh();
        $this->assertEquals(6.0, (float) $part->qty, 'on hand decreased by 4');
        $this->assertEquals(0.0, (float) $part->qty_reserved, 'reservation consumed');

        $r = PartReservation::where('work_order_id', $wo->id)->first();
        $this->assertEquals('consumed', $r->status);
    }

    public function test_wo_cancelled_releases_reservations(): void
    {
        $part = $this->makePart('zeta');
        $this->svc->receive($part, 10, 25.0);

        $wo = $this->makeWorkOrder($part);
        WorkOrderPart::create([
            'work_order_id' => $wo->id,
            'part_id' => $part->id,
            'name' => $part->name,
            'qty' => 4,
            'unit' => 'buc',
            'buy_price' => 25,
            'sell_price' => 40,
        ]);

        $wo->status = 'cancelled';
        $wo->save();

        $part->refresh();
        $this->assertEquals(10.0, (float) $part->qty, 'on hand untouched after cancel');
        $this->assertEquals(0.0, (float) $part->qty_reserved, 'reservation released');

        $r = PartReservation::where('work_order_id', $wo->id)->first();
        $this->assertEquals('released', $r->status);
    }

    public function test_batches_are_tenant_isolated(): void
    {
        $partA = $this->makePart('aa');
        $this->svc->receive($partA, 5, 10.0);

        $companyB = $this->makeCompany('bb');
        app(TenantManager::class)->setCurrent($companyB);

        $this->assertEquals(0, PartBatch::count(), 'tenant B sees no batches from tenant A');
    }

    public function test_transfer_moves_qty_between_warehouses(): void
    {
        $part = $this->makePart('eta');
        $main = Warehouse::where('is_default', true)->first();
        $secondary = Warehouse::create(['code' => 'BR1', 'name' => 'Sucursală', 'is_active' => true]);

        $this->svc->receive($part, 10, 15.0, $main);
        $this->svc->transfer($part, 4, $main, $secondary);

        $this->assertEquals(6.0, $this->svc->availableForIssue($part, $main));
        $this->assertEquals(4.0, $this->svc->availableForIssue($part, $secondary));

        $events = WarehouseEvent::where('part_id', $part->id)->get();
        $this->assertEqualsCanonicalizing(
            ['receipt', 'transfer_out', 'transfer_in'],
            $events->pluck('type')->sort()->values()->all() === ['receipt', 'transfer_in', 'transfer_out']
                ? ['receipt', 'transfer_in', 'transfer_out']
                : $events->pluck('type')->sort()->values()->all()
        );
    }

    private function makePart(string $companySlug): Part
    {
        $company = $this->makeCompany($companySlug);
        app(TenantManager::class)->setCurrent($company);

        // Ensure default warehouse exists (migration creates one for existing
        // companies; new test companies need it created on-the-fly).
        $wh = Warehouse::create([
            'code' => 'MAIN', 'name' => 'Depozit principal',
            'is_default' => true, 'is_active' => true,
        ]);
        $company->forceFill(['default_warehouse_id' => $wh->id])->saveQuietly();

        return Part::create([
            'name' => 'Filtru ulei',
            'unit' => 'buc',
            'qty' => 0,
            'buy_price' => 0,
            'sell_price' => 0,
            'is_active' => true,
        ]);
    }

    private function makeCompany(string $slug): Company
    {
        $plan = Plan::firstOrCreate(['slug' => 'test'], [
            'name' => 'Test', 'price' => 0, 'features' => [],
        ]);
        return Company::create([
            'plan_id' => $plan->id,
            'slug' => $slug . '-' . uniqid(),
            'name' => ucfirst($slug),
            'status' => 'active',
        ]);
    }

    private function makeWorkOrder(Part $part): WorkOrder
    {
        $client = Client::create([
            'name' => 'WOClient', 'phone' => '+37399' . random_int(100000, 999999),
            'type' => 'individual', 'status' => 'active',
        ]);
        $vehicle = Vehicle::create([
            'client_id' => $client->id,
            'make' => 'X', 'model' => 'Y',
            'plate' => 'WO' . random_int(100, 999),
        ]);
        return WorkOrder::create([
            'number' => WorkOrder::generateNumber($part->company_id),
            'client_id' => $client->id,
            'vehicle_id' => $vehicle->id,
            'opened_at' => now(),
            'status' => 'in_work',
        ]);
    }
}
