<?php

declare(strict_types=1);

namespace App\Business\DataImport;

use App\Business\DataImport\Enums\DataImportRowStatus;
use App\Business\DataImport\Support\DataImportReferenceResolver;
use App\Business\DataImport\Support\DataImportValueParser;
use App\Business\Inventory\Enums\InventoryDocumentStatus;
use App\Business\Inventory\InventorySerialNumberService;
use App\Business\Inventory\OtherStockInService;
use App\Business\Product\InventoryPostingProductSkuResolver;
use App\Models\DberpDataImportBatch;
use App\Models\DberpDataImportBatchRow;
use App\Models\DberpInventoryBalance;
use App\Models\DberpOtherStockInOrder;
use App\Models\DberpOtherStockInOrderItem;
use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use App\Models\DberpWarehouse;
use App\Support\DberpLineProductSkuFormFields;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use RuntimeException;

final class OpeningStockImportService
{
    public function __construct(
        private readonly OtherStockInService $otherStockInService,
        private readonly InventorySerialNumberService $serialNumberService,
        private readonly DataImportReferenceResolver $referenceResolver,
    ) {}

    public function rowIdempotencyKey(DberpDataImportBatch $batch, DberpDataImportBatchRow $batchRow): string
    {
        return sprintf('data_init_stock:%s:%d', $batch->batch_no, (int) $batchRow->row_number);
    }

    public function importRow(DberpDataImportBatch $batch, DberpDataImportBatchRow $batchRow): DataImportRowExecutionOutcome
    {
        if ($batchRow->status === DataImportRowStatus::Imported && $batchRow->created_record_id !== null) {
            $existingOrder = DberpOtherStockInOrder::query()->find($batchRow->created_record_id);

            if ($existingOrder !== null && $existingOrder->status === InventoryDocumentStatus::Effective->value) {
                return new DataImportRowExecutionOutcome(
                    rowNumber: (int) $batchRow->row_number,
                    status: DataImportRowStatus::Imported,
                    createdRecordType: DberpOtherStockInOrder::class,
                    createdRecordId: (int) $existingOrder->getKey(),
                );
            }
        }

        $snapshot = is_array($batchRow->raw_snapshot) ? $batchRow->raw_snapshot : [];

        try {
            $orderId = DB::transaction(function () use ($batch, $batchRow, $snapshot): int {
                $productCode = DataImportValueParser::trimmed($snapshot['product_code'] ?? null);
                $warehouseCode = DataImportValueParser::trimmed($snapshot['warehouse_code'] ?? null);

                if ($productCode === null || $warehouseCode === null) {
                    throw new RuntimeException((string) __('dberp.data_import.validation.opening_stock_reference_missing'));
                }

                $productsByCode = $this->referenceResolver->enabledInventoryProductsByCodes([$productCode]);
                $warehousesByCode = $this->referenceResolver->enabledWarehousesByCodes([$warehouseCode]);
                $product = $productsByCode[$productCode] ?? null;
                $warehouse = $warehousesByCode[$warehouseCode] ?? null;

                if (! $product instanceof DberpProduct || ! $warehouse instanceof DberpWarehouse) {
                    throw new RuntimeException((string) __('dberp.data_import.validation.opening_stock_reference_missing'));
                }

                $sku = $this->resolveSku($product, DataImportValueParser::trimmed($snapshot['sku_code'] ?? null));

                if (! $sku instanceof DberpProductSku) {
                    throw new RuntimeException((string) __('dberp.data_import.validation.product_sku_code_required'));
                }

                $quantity = DataImportValueParser::parsePositiveDecimal($snapshot['quantity'] ?? null);
                $businessTime = DataImportValueParser::parseOptionalDateTime($snapshot['business_time'] ?? null) ?? now();
                $serialNumbers = $this->serialNumberService->normalizeSerialNumbers($snapshot['serial_no'] ?? null);
                $batchNo = $this->normalizeBatchNo(DataImportValueParser::trimmed($snapshot['batch_no'] ?? null));
                $productionDate = DataImportValueParser::parseOptionalDate($snapshot['production_date'] ?? null);
                $expiryDate = DataImportValueParser::parseOptionalDate($snapshot['expiry_date'] ?? null);
                $safetyStock = DataImportValueParser::parseNonNegativeDecimal($snapshot['safety_stock'] ?? null, null);
                $rowRemark = DataImportValueParser::trimmed($snapshot['remark'] ?? null);

                $order = DberpOtherStockInOrder::query()->create([
                    'warehouse_id' => (int) $warehouse->getKey(),
                    'reason' => (string) __('dberp.data_import.opening_stock.order_reason'),
                    'status' => InventoryDocumentStatus::Draft->value,
                    'business_time' => $businessTime,
                    'remark' => sprintf(
                        '%s | %s',
                        (string) __('dberp.data_import.opening_stock.order_remark_prefix'),
                        $this->rowIdempotencyKey($batch, $batchRow),
                    ),
                ]);

                DberpOtherStockInOrderItem::query()->create([
                    'order_id' => (int) $order->getKey(),
                    'product_id' => (int) $product->getKey(),
                    'product_sku_id' => (int) $sku->getKey(),
                    'quantity' => $quantity,
                    'batch_no' => $batchNo,
                    'production_date' => $productionDate,
                    'expiry_date' => $expiryDate,
                    'serial_numbers' => $serialNumbers !== [] ? $serialNumbers : null,
                    'sort_order' => (int) $batchRow->row_number,
                    'remark' => $rowRemark,
                ]);

                $this->otherStockInService->apply($order->fresh(['items.product', 'warehouse']));

                if ($safetyStock !== null) {
                    DberpInventoryBalance::query()
                        ->where('product_sku_id', $sku->getKey())
                        ->where('warehouse_id', $warehouse->getKey())
                        ->update(['safety_stock' => $safetyStock]);
                }

                return (int) $order->getKey();
            });

            $batchRow->update([
                'status' => DataImportRowStatus::Imported,
                'created_record_type' => DberpOtherStockInOrder::class,
                'created_record_id' => $orderId,
                'error_messages' => null,
            ]);

            return new DataImportRowExecutionOutcome(
                rowNumber: (int) $batchRow->row_number,
                status: DataImportRowStatus::Imported,
                createdRecordType: DberpOtherStockInOrder::class,
                createdRecordId: $orderId,
            );
        } catch (\Throwable $exception) {
            $reason = $exception instanceof ValidationException
                ? implode(' ', array_map(
                    static fn (array $messages): string => implode(' ', $messages),
                    $exception->errors(),
                ))
                : $exception->getMessage();

            $message = DataImportValueParser::rowError((int) $batchRow->row_number, (string) __('dberp.data_import.validation.import_failed', [
                'reason' => $reason,
            ]));

            $batchRow->update([
                'status' => DataImportRowStatus::Failed,
                'error_messages' => [$message],
            ]);

            return new DataImportRowExecutionOutcome(
                rowNumber: (int) $batchRow->row_number,
                status: DataImportRowStatus::Failed,
                errorMessages: [$message],
            );
        }
    }

    public function resolveSku(DberpProduct $product, ?string $skuCode): ?DberpProductSku
    {
        if ($skuCode !== null) {
            return $this->referenceResolver->enabledSkuForProductAndCode($product, $skuCode);
        }

        if (InventoryPostingProductSkuResolver::productRequiresExplicitSkuBeforeDefault($product)) {
            return null;
        }

        if (DberpLineProductSkuFormFields::enabledSkuCountForProduct((int) $product->getKey()) !== 1) {
            return null;
        }

        $sku = DberpLineProductSkuFormFields::enabledSkusForProduct((int) $product->getKey())->first();

        return $sku instanceof DberpProductSku ? $sku : null;
    }

    private function normalizeBatchNo(?string $batchNo): ?string
    {
        if ($batchNo === null) {
            return null;
        }

        $normalized = strtoupper(trim($batchNo));

        return $normalized === '' ? null : $normalized;
    }
}
