<?php

namespace Database\Seeders\Demo;

use App\Models\Branch;
use App\Models\BranchStock;
use App\Models\Catalog\Product;
use App\Models\Inventory\ProductSerial;
use App\Models\Inventory\StockAdjustment;
use App\Models\Inventory\StockBatch;
use App\Models\Inventory\StockTransfer;
use App\Models\Lookups\AdjustmentReason;
use App\Models\Lookups\MovementType;
use App\Models\Lookups\Status;
use App\Models\Lookups\StockTransferReason;
use App\Services\BatchService;
use App\Services\SerialService;
use App\Services\StockMovementService;
use Database\Seeders\Demo\Concerns\GeneratesDemoData;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Auth;

/**
 * Stock adjustments and inter-branch transfers. Neither has a dedicated
 * service class in the app — this mirrors the inline logic in
 * StockAdjustmentController::store() / StockTransferController::store().
 */
class StockOpsSeeder extends Seeder
{
    use GeneratesDemoData;

    private const ADJUSTMENT_COUNT = 30;

    private const TRANSFER_COUNT = 25;

    public function run(): void
    {
        $stock = app(StockMovementService::class);
        $batches = app(BatchService::class);
        $serials = app(SerialService::class);
        $adminId = (int) Auth::id();

        $branches = Branch::orderBy('id')->get();
        // Adjustments never touch products with serials on file (those are
        // corrected individually via the Serial Numbers page instead).
        $adjustableProducts = Product::where('is_active', true)->whereDoesntHave('serials')->get();
        $allProducts = Product::where('is_active', true)->get();

        $this->seedAdjustments($stock, $batches, $branches, $adjustableProducts, $adminId);
        $this->seedTransfers($stock, $batches, $serials, $branches, $allProducts);
    }

    private function seedAdjustments(StockMovementService $stock, BatchService $batches, Collection $branches, Collection $products, int $adminId): void
    {
        $reasonCodes = [AdjustmentReason::COUNT_ERROR, AdjustmentReason::THEFT, AdjustmentReason::FOUND, AdjustmentReason::SUPPLIER_CORRECTION, AdjustmentReason::OTHER];
        $created = 0;
        $attempts = 0;

        while ($created < self::ADJUSTMENT_COUNT && $attempts < self::ADJUSTMENT_COUNT * 4) {
            $attempts++;
            $branch = $branches->random();
            $product = $products->random();
            $isPerishable = in_array($product->id, CatalogSeeder::$perishableIds, true);

            $batch = $isPerishable
                ? StockBatch::where('branch_id', $branch->id)->where('product_id', $product->id)->active()->inRandomOrder()->first()
                : null;

            if ($isPerishable && ! $batch) {
                continue;
            }

            $currentStock = $batch
                ? (float) $batch->available_quantity
                : BranchStock::quantityFor($branch->id, $product->id);

            if ($currentStock <= 0 && random_int(0, 1) === 0) {
                continue; // prefer adjusting products that actually have stock
            }

            $diffDirection = fake()->boolean(60) ? -1 : 1;
            $diff = $diffDirection * round(max(1, $currentStock * fake()->randomFloat(2, 0.02, 0.1)), 2);
            $newStock = max(0, $currentStock + $diff);
            $diff = $newStock - $currentStock;

            if (abs($diff) < 0.01) {
                continue;
            }

            $date = $this->recentDate(5);

            $adjustment = StockAdjustment::create([
                'branch_id' => $branch->id,
                'product_id' => $product->id,
                'adjustment_reason_id' => AdjustmentReason::where('code', fake()->randomElement($reasonCodes))->value('id'),
                'stock_batch_id' => $batch?->id,
                'before_stock' => $currentStock,
                'new_stock' => $newStock,
                'diff' => $diff,
                'note' => fake()->randomElement(['Physical stock count variance', 'Cycle count correction', 'Found in overflow storage', 'Reported shrinkage']),
                'adjusted_by' => $adminId,
            ]);

            $stock->record(
                branchId: $branch->id, productId: $product->id, movementTypeCode: MovementType::ADJUSTMENT,
                quantityChange: $diff, stockBatchId: $batch?->id,
            );

            if ($batch) {
                $batches->adjust($batch, $newStock);
            }

            $this->backdate($adjustment, $date, cascadeStockMovements: true);
            $created++;
        }
    }

    private function seedTransfers(StockMovementService $stock, BatchService $batches, SerialService $serials, Collection $branches, Collection $products): void
    {
        $reasonCodes = [StockTransferReason::INTERNAL, StockTransferReason::REPLENISH, StockTransferReason::RELOCATION, StockTransferReason::OTHER];
        $availableStatusId = Status::idOf(Status::CTX_PRODUCT_SERIAL, Status::SERIAL_AVAILABLE);
        $created = 0;
        $attempts = 0;

        while ($created < self::TRANSFER_COUNT && $attempts < self::TRANSFER_COUNT * 5) {
            $attempts++;
            $fromBranch = $branches->random();
            $toBranch = $branches->where('id', '!=', $fromBranch->id)->random();
            $product = $products->random();

            $stockBatchId = null;
            $serialIds = null;
            $quantity = null;
            $batch = null;

            if (in_array($product->id, CatalogSeeder::$serializedIds, true)) {
                $picked = ProductSerial::where('product_id', $product->id)->where('branch_id', $fromBranch->id)
                    ->where('status_id', $availableStatusId)->inRandomOrder()->limit(random_int(1, 2))->get();
                if ($picked->isEmpty()) {
                    continue;
                }
                $serialIds = $picked->pluck('id')->all();
                $quantity = (float) count($serialIds);
            } elseif (in_array($product->id, CatalogSeeder::$perishableIds, true)) {
                $batch = StockBatch::where('branch_id', $fromBranch->id)->where('product_id', $product->id)->active()->inRandomOrder()->first();
                if (! $batch || (float) $batch->available_quantity < 1) {
                    continue;
                }
                $stockBatchId = $batch->id;
                $quantity = round(min((float) $batch->available_quantity, max(1, (float) $batch->available_quantity * fake()->randomFloat(2, 0.1, 0.4))), 2);
            } else {
                $available = BranchStock::quantityFor($fromBranch->id, $product->id);
                if ($available < 2) {
                    continue;
                }
                $quantity = round(min($available, max(1, $available * fake()->randomFloat(2, 0.1, 0.3))), 2);
            }

            if (! $quantity || $quantity <= 0) {
                continue;
            }

            $date = $this->recentDate(5);
            $reference = 'TRF-'.$date->year.'-'.str_pad((string) ($created + 1), 5, '0', STR_PAD_LEFT);

            $transfer = StockTransfer::create([
                'from_branch_id' => $fromBranch->id,
                'to_branch_id' => $toBranch->id,
                'product_id' => $product->id,
                'unit_id' => $product->base_unit_id,
                'stock_batch_id' => $stockBatchId,
                'serial_ids' => $serialIds,
                'reason_id' => StockTransferReason::where('code', fake()->randomElement($reasonCodes))->value('id'),
                'quantity' => $quantity,
                'note' => 'Branch replenishment transfer.',
            ]);

            $stock->record(branchId: $fromBranch->id, productId: $product->id, movementTypeCode: MovementType::TRANSFER_OUT, quantityChange: -$quantity, referenceType: $transfer, referenceNumber: $reference, stockBatchId: $stockBatchId);
            $stock->record(branchId: $toBranch->id, productId: $product->id, movementTypeCode: MovementType::TRANSFER_IN, quantityChange: $quantity, referenceType: $transfer, referenceNumber: $reference, stockBatchId: $stockBatchId);

            if ($batch) {
                $batches->transferPortion($batch, $quantity, $toBranch->id);
            }
            if ($serialIds) {
                $serials->transfer($serialIds, $toBranch->id);
            }

            $this->backdate($transfer, $date, cascadeStockMovements: true);
            $created++;
        }

        $this->command?->info("StockOpsSeeder: created {$created} of ".self::TRANSFER_COUNT.' transfers.');
    }
}
