<?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\Enums\DataImportRowStatus;
use App\Business\DataImport\Enums\DataImportType;
use App\Business\DataImport\Support\DataImportValueParser;
use App\Models\DberpCustomer;
use App\Models\DberpCustomerCategory;
use App\Models\DberpDataImportBatch;
use App\Models\DberpDataImportBatchRow;
use App\Models\DberpRegion;
use App\Support\DberpPriceLevelDictionary;
use Illuminate\Support\Facades\DB;

final class CustomerImportHandler extends AbstractPartnerMasterImportHandler implements DataImportHandler
{
    public function importType(): DataImportType
    {
        return DataImportType::Customer;
    }

    protected function partnerModelClass(): string
    {
        return DberpCustomer::class;
    }

    protected function categoryCodeField(): string
    {
        return 'customer_category_code';
    }

    protected function categoryModelClass(): string
    {
        return DberpCustomerCategory::class;
    }

    protected function categoryForeignKey(): string
    {
        return 'customer_category_id';
    }

    /**
     * @param  list<DataImportRow>  $rows
     */
    public function precheckRows(array $rows): DataImportPrecheckResult
    {
        $common = $this->precheckCommonPartnerRows($rows);
        $outcomes = $common['outcomes'];
        $salespersonCodesInFile = [];

        foreach ($rows as $row) {
            $salespersonCode = DataImportValueParser::trimmed($row->value('salesperson_employee_code'));

            if ($salespersonCode !== null) {
                $salespersonCodesInFile[] = $salespersonCode;
            }
        }

        $categoryIdsByCode = $this->referenceResolver->customerCategoryIdsByCodes(
            array_values(array_unique($common['categoryCodesInFile'])),
        );
        $regionsByCode = $this->referenceResolver->regionsByCodes(
            array_values(array_unique($common['regionCodesInFile'])),
        );
        $employeeIdsByCode = $this->referenceResolver->employeeIdsByCodes(
            array_values(array_unique($salespersonCodesInFile)),
        );

        $outcomes = $this->applyCommonPartnerReferenceChecks(
            rows: $rows,
            outcomes: $outcomes,
            codesInFile: $common['codesInFile'],
            regionCodesInFile: $common['regionCodesInFile'],
            categoryCodesInFile: $common['categoryCodesInFile'],
            categoryIdsByCode: $categoryIdsByCode,
            regionsByCode: $regionsByCode,
        );

        foreach ($rows as $row) {
            $salespersonCode = DataImportValueParser::trimmed($row->value('salesperson_employee_code'));
            $errors = [];

            if ($salespersonCode !== null && ! isset($employeeIdsByCode[$salespersonCode])) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.invalid_salesperson_employee_code', [
                    'value' => $salespersonCode,
                ]));
            }

            $priceLevelCode = DataImportValueParser::trimmed($row->value('price_level_code'));

            if ($priceLevelCode !== null && ! DberpPriceLevelDictionary::isEnabledCode($priceLevelCode)) {
                $errors[] = DataImportValueParser::rowError($row->rowNumber, (string) __('dberp.data_import.validation.invalid_price_level_code', [
                    'value' => $priceLevelCode,
                ]));
            }

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

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

    /**
     * @return list<string>
     */
    protected function validateAdditionalRowFields(DataImportRow $row): array
    {
        $errors = [];

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

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

        return $errors;
    }

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

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

        $categoryCodes = [];
        $regionCodes = [];
        $employeeCodes = [];

        foreach ($validatedRows as $batchRow) {
            $snapshot = is_array($batchRow->raw_snapshot) ? $batchRow->raw_snapshot : [];
            $categoryCode = DataImportValueParser::trimmed($snapshot['customer_category_code'] ?? null);
            $regionCode = DataImportValueParser::trimmed($snapshot['region_code'] ?? null);
            $salespersonCode = DataImportValueParser::trimmed($snapshot['salesperson_employee_code'] ?? null);

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

            if ($regionCode !== null) {
                $regionCodes[] = $regionCode;
            }

            if ($salespersonCode !== null) {
                $employeeCodes[] = $salespersonCode;
            }
        }

        $categoryIdsByCode = $this->referenceResolver->customerCategoryIdsByCodes(array_values(array_unique($categoryCodes)));
        $regionsByCode = $this->referenceResolver->regionsByCodes(array_values(array_unique($regionCodes)));
        $employeeIdsByCode = $this->referenceResolver->employeeIdsByCodes(array_values(array_unique($employeeCodes)));

        DB::transaction(function () use ($validatedRows, $categoryIdsByCode, $regionsByCode, $employeeIdsByCode, &$outcomes, &$importedCount, &$failedCount): void {
            foreach ($validatedRows as $batchRow) {
                $outcome = $this->importRow($batchRow, $categoryIdsByCode, $regionsByCode, $employeeIdsByCode);
                $outcomes[] = $outcome;

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

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

    /**
     * @param  array<string, int>  $categoryIdsByCode
     * @param  array<string, DberpRegion>  $regionsByCode
     * @param  array<string, int>  $employeeIdsByCode
     */
    private function importRow(
        DberpDataImportBatchRow $batchRow,
        array $categoryIdsByCode,
        array $regionsByCode,
        array $employeeIdsByCode,
    ): DataImportRowExecutionOutcome {
        $snapshot = is_array($batchRow->raw_snapshot) ? $batchRow->raw_snapshot : [];

        try {
            $payload = $this->buildCommonPartnerPayload($snapshot, $categoryIdsByCode, $regionsByCode);
            $salespersonCode = DataImportValueParser::trimmed($snapshot['salesperson_employee_code'] ?? null);
            $priceLevelCode = DataImportValueParser::trimmed($snapshot['price_level_code'] ?? null);

            $payload['price_level_code'] = $priceLevelCode;
            $payload['default_payment_term_days'] = DataImportValueParser::parseNonNegativeInteger($snapshot['default_payment_term_days'] ?? null);
            $payload['credit_limit_amount'] = DataImportValueParser::parseNonNegativeDecimal($snapshot['credit_limit_amount'] ?? null);
            $payload['salesperson_employee_id'] = $salespersonCode !== null
                ? ($employeeIdsByCode[$salespersonCode] ?? null)
                : null;

            $customer = DberpCustomer::query()->create($payload);

            $batchRow->update([
                'status' => DataImportRowStatus::Imported,
                'created_record_type' => DberpCustomer::class,
                'created_record_id' => (int) $customer->getKey(),
                'error_messages' => null,
            ]);

            return new DataImportRowExecutionOutcome(
                rowNumber: (int) $batchRow->row_number,
                status: DataImportRowStatus::Imported,
                createdRecordType: DberpCustomer::class,
                createdRecordId: (int) $customer->getKey(),
            );
        } 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],
            );
        }
    }
}
