<?php

namespace App\Services;

use App\Exceptions\StockException;
use App\Models\GoodsReceipt;
use App\Models\PurchaseOrder;
use App\Models\StockLedger;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class PurchaseOrderService
{
    public function __construct(protected LocationInventoryService $inventory)
    {
    }

    public function create(array $data, array $items, ?int $createdBy = null): PurchaseOrder
    {
        $createdBy = $createdBy ?? auth()->id();
        if (!$createdBy) {
            throw new StockException('Authenticated user is required to create a purchase order.');
        }

        return DB::transaction(function () use ($data, $items, $createdBy) {
            $total = collect($items)->sum(fn ($i) => ((float) $i['unit_cost']) * ((int) $i['quantity']));

            $po = PurchaseOrder::create([
                'po_number' => $data['po_number'] ?? ('PO-' . strtoupper(Str::random(8))),
                'company_id' => $data['company_id'],
                'supplier_id' => $data['supplier_id'],
                'location_id' => $data['location_id'],
                'status' => PurchaseOrder::STATUS_DRAFT,
                'expected_date' => $data['expected_date'] ?? null,
                'total_amount' => $total,
                'notes' => $data['notes'] ?? null,
                'created_by' => $createdBy,
            ]);

            foreach ($items as $item) {
                $po->items()->create([
                    'product_id' => $item['product_id'],
                    'quantity' => $item['quantity'],
                    'unit_cost' => $item['unit_cost'] ?? 0,
                    'received_quantity' => 0,
                ]);
            }

            return $po->load('items');
        });
    }

    public function markOrdered(PurchaseOrder $po): PurchaseOrder
    {
        if ($po->status !== PurchaseOrder::STATUS_DRAFT) {
            throw new StockException('Only draft POs can be marked ordered.');
        }
        $po->update(['status' => PurchaseOrder::STATUS_ORDERED]);

        return $po->fresh();
    }

    public function receive(PurchaseOrder $po, array $receiveItems, ?string $notes = null, ?int $receivedBy = null): GoodsReceipt
    {
        if (!in_array($po->status, [PurchaseOrder::STATUS_ORDERED, PurchaseOrder::STATUS_PARTIAL], true)) {
            throw new StockException('PO must be ordered before receiving.');
        }

        return DB::transaction(function () use ($po, $receiveItems, $notes, $receivedBy) {
            $grn = GoodsReceipt::create([
                'grn_number' => 'GRN-' . strtoupper(Str::random(8)),
                'location_id' => $po->location_id,
                'purchase_order_id' => $po->id,
                'status' => GoodsReceipt::STATUS_POSTED,
                'notes' => $notes,
                'received_by' => $receivedBy ?? auth()->id(),
                'received_at' => now(),
            ]);

            $po->load('items');
            foreach ($receiveItems as $row) {
                $poItem = $po->items->firstWhere('product_id', $row['product_id']);
                if (!$poItem) {
                    continue;
                }
                $qty = (int) $row['quantity'];
                if ($qty <= 0) {
                    continue;
                }

                $already = (int) $poItem->received_quantity;
                $ordered = (int) $poItem->quantity;
                $remaining = max(0, $ordered - $already);
                if ($qty > $remaining) {
                    throw new StockException(
                        "Cannot over-receive product #{$row['product_id']}: qty {$qty} exceeds remaining {$remaining} (ordered {$ordered}, already received {$already}). Over-receive is not allowed."
                    );
                }

                $grn->items()->create([
                    'product_id' => $row['product_id'],
                    'quantity' => $qty,
                    'unit_cost' => $row['unit_cost'] ?? $poItem->unit_cost,
                ]);

                $this->inventory->applyMovement(
                    locationId: $po->location_id,
                    productId: (int) $row['product_id'],
                    type: StockLedger::TYPE_PURCHASE_RECEIPT,
                    quantity: $qty,
                    notes: "GRN {$grn->grn_number} / PO {$po->po_number}",
                    referenceType: 'goods_receipt',
                    referenceId: $grn->id,
                    createdBy: $receivedBy ?? auth()->id(),
                );

                $poItem->received_quantity = $already + $qty;
                $poItem->save();
            }

            $po->load('items');
            $allReceived = $po->items->every(fn ($i) => $i->received_quantity >= $i->quantity);
            $anyReceived = $po->items->sum('received_quantity') > 0;
            $po->update([
                'status' => $allReceived
                    ? PurchaseOrder::STATUS_RECEIVED
                    : ($anyReceived ? PurchaseOrder::STATUS_PARTIAL : $po->status),
            ]);

            return $grn->load('items');
        });
    }
}
