<?php

declare(strict_types=1);

namespace App\Business\DataImport\Handlers;

use App\Business\DataImport\Contracts\DataImportHandler;
use App\Business\DataImport\DataImportExecutionResult;
use App\Business\DataImport\DataImportPrecheckResult;
use App\Business\DataImport\DataImportRow;
use App\Business\DataImport\DataImportRowExecutionOutcome;
use App\Business\DataImport\DataImportRowPrecheckOutcome;
use App\Business\DataImport\Enums\DataImportRowStatus;
use App\Business\DataImport\Enums\DataImportType;
use App\Business\DataImport\Support\DataImportReferenceResolver;
use App\Business\DataImport\Support\DataImportValueParser;
use App\Models\DberpDataImportBatch;
use App\Models\DberpDataImportBatchRow;
use App\Models\DberpProduct;
use App\Models\DberpProductSku;
use Illuminate\Support\Facades\DB;
use RuntimeException;

final class ProductImportHandler extends AbstractMasterDataImportHandler implements DataImportHandler
{
    public function __construct(
        private readonly DataImportReferenceResolver $referenceResolver,
    ) {}

    public function importType(): DataImportType
    {
        return DataImportType::Product;
    }

    /**
     * @param  list<DataImportRow>  $rows
     */
    public function precheckRows(array $rows): DataImportPrecheckResult
    {
        $outcomes = $this->buildInitialOutcomes($rows);
        $codesInFile = [];
        $unitCodesInFile = [];
        $categoryCodesInFile = [];
        $brandCodesInFile = [];
        $barcodesInFile = [];

        foreach ($rows as $row) {
            $name = DataImportValueParser::trimmed($row->value('name'));
            $code = DataImportValueParser::trimmed($row->value('code'));
            $unitCode = DataImportValueParser::trimmed($row->value('unit_code'));
            $categoryCode = DataImportValueParser::trimmed($row->value('category_code'));
            $brandCode = DataImportValueParser::trimmed($row->value('brand_code'));
            $barcode = DataImportValueParser::trimmed($row->value('barcode'));
            $errors = [];

            if ($name === null) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.name_required'));
            }

            if ($unitCode === null) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.unit_code_required'));
            } else {
                $unitCodesInFile[] = $unitCode;
            }

            if ($code !== null) {
                $codesInFile[] = $code;
            }

            if ($categoryCode !== null) {
                $categoryCodesInFile[] = $categoryCode;
            }

            if ($brandCode !== null) {
                $brandCodesInFile[] = $brandCode;
            }

            if ($barcode !== null) {
                $barcodesInFile[] = $barcode;
            }

            try {
                DataImportValueParser::parseNonNegativeDecimal($row->value('cost_price'), null);
            } catch (\InvalidArgumentException $exception) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, $exception->getMessage());
            }

            try {
                DataImportValueParser::parseNonNegativeDecimal($row->value('sales_price'), null);
            } catch (\InvalidArgumentException $exception) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, $exception->getMessage());
            }

            try {
                DataImportValueParser::parseBoolean($row->value('manages_inventory'), true);
            } catch (\InvalidArgumentException $exception) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, $exception->getMessage());
            }

            try {
                DataImportValueParser::parseBoolean($row->value('is_enabled'), true);
            } catch (\InvalidArgumentException $exception) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, $exception->getMessage());
            }

            if ($errors !== []) {
                $outcomes = $this->markRowFailed($outcomes, $row->rowNumber, $errors);
            }
        }

        $outcomes = $this->applyDuplicateFieldErrors(
            $rows,
            $outcomes,
            'code',
            'dberp.data_import.validation.code_duplicate_in_file',
        );

        $outcomes = $this->applyDuplicateFieldErrors(
            $rows,
            $outcomes,
            'barcode',
            'dberp.data_import.validation.barcode_duplicate_in_file',
        );

        $uniqueUnitCodes = array_values(array_unique($unitCodesInFile));
        $uniqueCategoryCodes = array_values(array_unique($categoryCodesInFile));
        $uniqueBrandCodes = array_values(array_unique($brandCodesInFile));
        $uniqueBarcodes = array_values(array_unique($barcodesInFile));
        $uniqueCodes = array_values(array_unique($codesInFile));

        $enabledUnitIdsByCode = $this->referenceResolver->enabledUnitIdsByCodes($uniqueUnitCodes);
        $enabledCategoryIdsByCode = $this->referenceResolver->enabledProductCategoryIdsByCodes($uniqueCategoryCodes);
        $enabledBrandIdsByCode = $this->referenceResolver->enabledProductBrandIdsByCodes($uniqueBrandCodes);
        $existingUnitCodes = $this->referenceResolver->existingUnitCodes($uniqueUnitCodes);
        $existingCategoryCodes = $this->referenceResolver->existingProductCategoryCodes($uniqueCategoryCodes);
        $existingBrandCodes = $this->referenceResolver->existingProductBrandCodes($uniqueBrandCodes);
        $existingProductCodes = $this->loadExistingValues(DberpProduct::class, 'code', $uniqueCodes);
        $existingBarcodes = $this->referenceResolver->existingProductBarcodes($uniqueBarcodes);

        foreach ($rows as $row) {
            $code = DataImportValueParser::trimmed($row->value('code'));
            $unitCode = DataImportValueParser::trimmed($row->value('unit_code'));
            $categoryCode = DataImportValueParser::trimmed($row->value('category_code'));
            $brandCode = DataImportValueParser::trimmed($row->value('brand_code'));
            $barcode = DataImportValueParser::trimmed($row->value('barcode'));
            $errors = [];

            if ($unitCode !== null) {
                if (! in_array($unitCode, $existingUnitCodes, true)) {
                    $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.invalid_unit_code', [
                        'value' => $unitCode,
                    ]));
                } elseif (! isset($enabledUnitIdsByCode[$unitCode])) {
                    $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.disabled_unit_code', [
                        'value' => $unitCode,
                    ]));
                }
            }

            if ($categoryCode !== null) {
                if (! in_array($categoryCode, $existingCategoryCodes, true)) {
                    $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.invalid_product_category_code', [
                        'value' => $categoryCode,
                    ]));
                } elseif (! isset($enabledCategoryIdsByCode[$categoryCode])) {
                    $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.disabled_product_category_code', [
                        'value' => $categoryCode,
                    ]));
                }
            }

            if ($brandCode !== null) {
                if (! in_array($brandCode, $existingBrandCodes, true)) {
                    $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.invalid_product_brand_code', [
                        'value' => $brandCode,
                    ]));
                } elseif (! isset($enabledBrandIdsByCode[$brandCode])) {
                    $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.disabled_product_brand_code', [
                        'value' => $brandCode,
                    ]));
                }
            }

            if ($code !== null && in_array($code, $existingProductCodes, true)) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.code_exists_in_database', [
                    'value' => $code,
                ]));
            }

            if ($barcode !== null && in_array($barcode, $existingBarcodes, true)) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.barcode_exists_in_database', [
                    'value' => $barcode,
                ]));
            }

            if ($errors !== []) {
                $outcomes = $this->markRowFailed($outcomes, $row->rowNumber, $errors);
            }
        }

        return $this->buildPrecheckResult($this->finalizeOutcomes($outcomes));
    }

    public function execute(DberpDataImportBatch $batch): DataImportExecutionResult
    {
        $outcomes = [];
        $importedCount = 0;
        $failedCount = 0;

        $validatedRows = $batch->rows()
            ->where('status', DataImportRowStatus::Validated)
            ->orderBy('row_number')
            ->get();

        foreach ($validatedRows as $batchRow) {
            $outcome = $this->importRow($batchRow);
            $outcomes[] = $outcome;

            if ($outcome->imported()) {
                $importedCount++;
            } else {
                $failedCount++;
            }
        }

        return new DataImportExecutionResult($outcomes, $importedCount, $failedCount);
    }

    private function importRow(DberpDataImportBatchRow $batchRow): DataImportRowExecutionOutcome
    {
        $snapshot = is_array($batchRow->raw_snapshot) ? $batchRow->raw_snapshot : [];

        try {
            $productId = DB::transaction(function () use ($snapshot): int {
                $unitCode = DataImportValueParser::trimmed($snapshot['unit_code'] ?? null);
                $categoryCode = DataImportValueParser::trimmed($snapshot['category_code'] ?? null);
                $brandCode = DataImportValueParser::trimmed($snapshot['brand_code'] ?? null);

                if ($unitCode === null) {
                    throw new RuntimeException((string) __('dberp.data_import.validation.unit_code_required'));
                }

                $unitIdsByCode = $this->referenceResolver->enabledUnitIdsByCodes([$unitCode]);
                $unitId = $unitIdsByCode[$unitCode] ?? null;

                if ($unitId === null) {
                    throw new RuntimeException((string) __('dberp.data_import.validation.invalid_unit_code', [
                        'value' => $unitCode,
                    ]));
                }

                $categoryId = null;

                if ($categoryCode !== null) {
                    $categoryIdsByCode = $this->referenceResolver->enabledProductCategoryIdsByCodes([$categoryCode]);
                    $categoryId = $categoryIdsByCode[$categoryCode] ?? null;

                    if ($categoryId === null) {
                        throw new RuntimeException((string) __('dberp.data_import.validation.invalid_product_category_code', [
                            'value' => $categoryCode,
                        ]));
                    }
                }

                $brandId = null;

                if ($brandCode !== null) {
                    $brandIdsByCode = $this->referenceResolver->enabledProductBrandIdsByCodes([$brandCode]);
                    $brandId = $brandIdsByCode[$brandCode] ?? null;

                    if ($brandId === null) {
                        throw new RuntimeException((string) __('dberp.data_import.validation.invalid_product_brand_code', [
                            'value' => $brandCode,
                        ]));
                    }
                }

                $costPrice = DataImportValueParser::parseNonNegativeDecimal($snapshot['cost_price'] ?? null, '0.0000');
                $salesPrice = DataImportValueParser::parseNonNegativeDecimal($snapshot['sales_price'] ?? null, '0.0000');

                $payload = [
                    'code' => DataImportValueParser::trimmed($snapshot['code'] ?? null),
                    'name' => (string) DataImportValueParser::trimmed($snapshot['name'] ?? null),
                    'category_id' => $categoryId,
                    'brand_id' => $brandId,
                    'spec_model' => DataImportValueParser::trimmed($snapshot['spec_model'] ?? null),
                    'spec_enabled' => false,
                    'unit_id' => $unitId,
                    'barcode' => DataImportValueParser::trimmed($snapshot['barcode'] ?? null),
                    'cost_price' => $costPrice ?? '0.0000',
                    'sales_price' => $salesPrice ?? '0.0000',
                    'manages_inventory' => DataImportValueParser::parseBoolean($snapshot['manages_inventory'] ?? null, true),
                    'is_enabled' => DataImportValueParser::parseBoolean($snapshot['is_enabled'] ?? null, true),
                    'can_purchase' => true,
                    'can_sale' => true,
                    'remark' => DataImportValueParser::trimmed($snapshot['remark'] ?? null),
                ];

                $product = DberpProduct::query()->create($payload);

                $skuCount = DberpProductSku::query()
                    ->where('product_id', $product->getKey())
                    ->count();

                if ($skuCount === 0) {
                    throw new RuntimeException((string) __('dberp.data_import.validation.default_sku_creation_failed'));
                }

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

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

            return new DataImportRowExecutionOutcome(
                rowNumber: (int) $batchRow->row_number,
                status: DataImportRowStatus::Imported,
                createdRecordType: DberpProduct::class,
                createdRecordId: $productId,
            );
        } catch (\Throwable $exception) {
            $message = DataImportValueParser::rowError((int) $batchRow->row_number, (string) __('dberp.data_import.validation.import_failed', [
                'reason' => $exception->getMessage(),
            ]));

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

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

    /**
     * @param  list<DataImportRowPrecheckOutcome>  $outcomes
     */
    private function buildPrecheckResult(array $outcomes): DataImportPrecheckResult
    {
        $validatedCount = 0;
        $failedCount = 0;

        foreach ($outcomes as $outcome) {
            if ($outcome->passed()) {
                $validatedCount++;
            } else {
                $failedCount++;
            }
        }

        return new DataImportPrecheckResult($outcomes, $validatedCount, $failedCount);
    }
}
