<?php

namespace App\Services;

use App\Exceptions\StockException;
use App\Models\InventoryBalance;
use App\Models\StockLedger;
use App\Models\StockTransfer;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class StockTransferService
{
    public function __construct(protected LocationInventoryService $inventory)
    {
    }

    public function create(int $fromLocationId, int $toLocationId, array $items, ?string $notes = null, ?int $createdBy = null): StockTransfer
    {
        if ($fromLocationId === $toLocationId) {
            throw new StockException('Source and destination locations must differ.');
        }

        $createdBy = $createdBy ?? auth()->id();
        if (!$createdBy) {
            throw new StockException('Authenticated user is required to create a transfer.');
        }

        return DB::transaction(function () use ($fromLocationId, $toLocationId, $items, $notes, $createdBy) {
            $transfer = StockTransfer::create([
                'transfer_number' => $this->generateTransferNumber(),
                'from_location_id' => $fromLocationId,
                'to_location_id' => $toLocationId,
                'status' => StockTransfer::STATUS_DRAFT,
                'notes' => $notes,
                'created_by' => $createdBy,
            ]);

            $totalsByProduct = [];
            foreach ($items as $item) {
                $productId = (int) $item['product_id'];
                $quantity = (int) $item['quantity'];
                $totalsByProduct[$productId] = ($totalsByProduct[$productId] ?? 0) + $quantity;
            }

            foreach ($totalsByProduct as $productId => $totalQty) {
                $available = $this->inventory->getAvailableStock($productId, $fromLocationId);
                if ($totalQty > $available) {
                    throw new StockException(
                        "Transfer qty ({$totalQty}) exceeds available stock ({$available}) at source location for product #{$productId}."
                    );
                }
            }

            foreach ($items as $item) {
                $transfer->items()->create([
                    'product_id' => (int) $item['product_id'],
                    'quantity' => (int) $item['quantity'],
                    'received_quantity' => 0,
                ]);
            }

            return $transfer->load('items');
        });
    }

    /**
     * Generate a unique transfer number with simple collision retries.
     */
    protected function generateTransferNumber(): string
    {
        for ($attempt = 0; $attempt < 8; $attempt++) {
            $number = 'TR-' . strtoupper(Str::random(8));
            if (!StockTransfer::query()->where('transfer_number', $number)->exists()) {
                return $number;
            }
        }

        return 'TR-' . strtoupper(Str::random(12)) . substr((string) microtime(true), -4);
    }

    public function approve(StockTransfer $transfer, ?int $approvedBy = null): StockTransfer
    {
        if ($transfer->status !== StockTransfer::STATUS_DRAFT) {
            throw new StockException('Only draft transfers can be approved.');
        }

        return DB::transaction(function () use ($transfer, $approvedBy) {
            $transfer->load('items');

            foreach ($transfer->items as $item) {
                $balance = $this->inventory->getOrCreateBalance(
                    (int) $transfer->from_location_id,
                    (int) $item->product_id,
                    true
                );
                $balance = InventoryBalance::query()->whereKey($balance->id)->lockForUpdate()->firstOrFail();

                if ($balance->available() < (int) $item->quantity) {
                    throw new StockException(
                        "Cannot approve transfer {$transfer->transfer_number}: available stock ({$balance->available()}) at source is less than qty ({$item->quantity}) for product #{$item->product_id}."
                    );
                }
            }

            $transfer->update([
                'status' => StockTransfer::STATUS_APPROVED,
                'approved_by' => $approvedBy ?? auth()->id(),
                'approved_at' => now(),
            ]);

            return $transfer->fresh('items');
        });
    }

    public function ship(StockTransfer $transfer): StockTransfer
    {
        if ($transfer->status !== StockTransfer::STATUS_APPROVED) {
            throw new StockException('Only approved transfers can be shipped.');
        }

        return DB::transaction(function () use ($transfer) {
            $transfer->load('items');

            foreach ($transfer->items as $item) {
                $balance = $this->inventory->getOrCreateBalance(
                    (int) $transfer->from_location_id,
                    (int) $item->product_id,
                    true
                );
                $balance = InventoryBalance::query()->whereKey($balance->id)->lockForUpdate()->firstOrFail();

                if ($balance->available() < (int) $item->quantity) {
                    throw new StockException(
                        "Cannot ship transfer {$transfer->transfer_number}: available stock ({$balance->available()}) at source is less than qty ({$item->quantity}) for product #{$item->product_id}."
                    );
                }

                // Move from on_hand to in_transit_out at source
                $this->inventory->applyMovement(
                    locationId: $transfer->from_location_id,
                    productId: $item->product_id,
                    type: StockLedger::TYPE_TRANSFER_OUT,
                    quantity: -$item->quantity,
                    notes: "Transfer {$transfer->transfer_number} shipped",
                    referenceType: 'stock_transfer',
                    referenceId: $transfer->id,
                    affectsOnHand: true,
                    affectsInTransitOut: true,
                    inTransitOutDelta: $item->quantity,
                );

                // Destination in_transit_in
                $this->inventory->applyMovement(
                    locationId: $transfer->to_location_id,
                    productId: $item->product_id,
                    type: StockLedger::TYPE_TRANSFER_IN,
                    quantity: 0,
                    notes: "Transfer {$transfer->transfer_number} in transit",
                    referenceType: 'stock_transfer',
                    referenceId: $transfer->id,
                    dualWriteLegacy: false,
                    affectsOnHand: false,
                    affectsInTransitIn: true,
                    inTransitInDelta: $item->quantity,
                );
            }

            $transfer->update([
                'status' => StockTransfer::STATUS_IN_TRANSIT,
                'shipped_at' => now(),
            ]);

            return $transfer->fresh('items');
        });
    }

    public function receive(StockTransfer $transfer, ?array $receivedQtys = null): StockTransfer
    {
        if ($transfer->status !== StockTransfer::STATUS_IN_TRANSIT) {
            throw new StockException('Only in-transit transfers can be received.');
        }

        return DB::transaction(function () use ($transfer, $receivedQtys) {
            $transfer->load('items');

            foreach ($transfer->items as $item) {
                $alreadyReceived = (int) $item->received_quantity;
                $ordered = (int) $item->quantity;
                $remaining = max(0, $ordered - $alreadyReceived);

                $qty = array_key_exists($item->id, $receivedQtys ?? [])
                    ? (int) $receivedQtys[$item->id]
                    : $remaining;

                if ($qty < 0 || $qty > $remaining) {
                    throw new StockException(
                        "Invalid receive qty ({$qty}) for transfer item #{$item->id}: must be between 0 and remaining ({$remaining})."
                    );
                }

                if ($qty === 0) {
                    continue;
                }

                // Clear in_transit_out at source by received qty only (partial-safe)
                $this->inventory->applyMovement(
                    locationId: $transfer->from_location_id,
                    productId: $item->product_id,
                    type: StockLedger::TYPE_TRANSFER_OUT,
                    quantity: 0,
                    notes: "Transfer {$transfer->transfer_number} received (clear out {$qty})",
                    referenceType: 'stock_transfer',
                    referenceId: $transfer->id,
                    dualWriteLegacy: false,
                    affectsOnHand: false,
                    affectsInTransitOut: true,
                    inTransitOutDelta: -$qty,
                );

                // Clear in_transit_in and add on_hand at destination by received qty
                $this->inventory->applyMovement(
                    locationId: $transfer->to_location_id,
                    productId: $item->product_id,
                    type: StockLedger::TYPE_TRANSFER_IN,
                    quantity: $qty,
                    notes: "Transfer {$transfer->transfer_number} received",
                    referenceType: 'stock_transfer',
                    referenceId: $transfer->id,
                    affectsOnHand: true,
                    affectsInTransitIn: true,
                    inTransitInDelta: -$qty,
                );

                $item->update(['received_quantity' => $alreadyReceived + $qty]);
            }

            $transfer->load('items');
            $allReceived = $transfer->items->every(
                fn ($i) => (int) $i->received_quantity >= (int) $i->quantity
            );

            $transfer->update([
                'status' => $allReceived ? StockTransfer::STATUS_RECEIVED : StockTransfer::STATUS_IN_TRANSIT,
                'received_at' => $allReceived ? now() : $transfer->received_at,
            ]);

            return $transfer->fresh('items');
        });
    }

    public function cancel(StockTransfer $transfer): StockTransfer
    {
        if ($transfer->status === StockTransfer::STATUS_RECEIVED) {
            throw new StockException('Cannot cancel a received transfer.');
        }

        if ($transfer->status === StockTransfer::STATUS_CANCELLED) {
            throw new StockException('Transfer is already cancelled.');
        }

        // Draft / approved: status flip only (stock not yet moved)
        if (in_array($transfer->status, [StockTransfer::STATUS_DRAFT, StockTransfer::STATUS_APPROVED], true)) {
            $transfer->update(['status' => StockTransfer::STATUS_CANCELLED]);

            return $transfer->fresh();
        }

        if ($transfer->status !== StockTransfer::STATUS_IN_TRANSIT) {
            throw new StockException('Cannot cancel transfer in status: ' . $transfer->status);
        }

        // In-transit: return remaining stock to source and clear in_transit_* safely
        return DB::transaction(function () use ($transfer) {
            $transfer->load('items');

            foreach ($transfer->items as $item) {
                $remaining = max(0, (int) $item->quantity - (int) $item->received_quantity);
                if ($remaining <= 0) {
                    continue;
                }

                // Restore on_hand at source and clear in_transit_out
                $this->inventory->applyMovement(
                    locationId: $transfer->from_location_id,
                    productId: $item->product_id,
                    type: StockLedger::TYPE_TRANSFER_OUT,
                    quantity: $remaining,
                    notes: "Transfer {$transfer->transfer_number} cancelled — returned to source",
                    referenceType: 'stock_transfer',
                    referenceId: $transfer->id,
                    dualWriteLegacy: false,
                    affectsOnHand: true,
                    affectsInTransitOut: true,
                    inTransitOutDelta: -$remaining,
                );

                // Clear in_transit_in at destination
                $this->inventory->applyMovement(
                    locationId: $transfer->to_location_id,
                    productId: $item->product_id,
                    type: StockLedger::TYPE_TRANSFER_IN,
                    quantity: 0,
                    notes: "Transfer {$transfer->transfer_number} cancelled — clear in transit in",
                    referenceType: 'stock_transfer',
                    referenceId: $transfer->id,
                    dualWriteLegacy: false,
                    affectsOnHand: false,
                    affectsInTransitIn: true,
                    inTransitInDelta: -$remaining,
                );
            }

            $transfer->update(['status' => StockTransfer::STATUS_CANCELLED]);

            return $transfer->fresh('items');
        });
    }
}
