<?php

declare(strict_types=1);

namespace App\Business\DataImport\Handlers;

use App\Business\DataImport\DataImportPrecheckResult;
use App\Business\DataImport\DataImportRow;
use App\Business\DataImport\DataImportRowPrecheckOutcome;
use App\Business\DataImport\Support\DataImportReferenceResolver;
use App\Business\DataImport\Support\DataImportValueParser;
use App\Models\DberpRegion;
use Illuminate\Database\Eloquent\Model;

abstract class AbstractPartnerMasterImportHandler extends AbstractMasterDataImportHandler
{
    public function __construct(
        protected readonly DataImportReferenceResolver $referenceResolver,
    ) {}

    /**
     * @return class-string<Model>
     */
    abstract protected function partnerModelClass(): string;

    abstract protected function categoryCodeField(): string;

    /**
     * @return class-string<Model>
     */
    abstract protected function categoryModelClass(): string;

    /**
     * @param  list<DataImportRow>  $rows
     * @return array{
     *     outcomes: list<DataImportRowPrecheckOutcome>,
     *     codesInFile: list<string>,
     *     regionCodesInFile: list<string>,
     *     categoryCodesInFile: list<string>,
     * }
     */
    protected function precheckCommonPartnerRows(array $rows): array
    {
        $outcomes = $this->buildInitialOutcomes($rows);
        $codesInFile = [];
        $regionCodesInFile = [];
        $categoryCodesInFile = [];

        foreach ($rows as $row) {
            $name = DataImportValueParser::trimmed($row->value('name'));
            $code = DataImportValueParser::trimmed($row->value('code'));
            $regionCode = DataImportValueParser::trimmed($row->value('region_code'));
            $categoryCode = DataImportValueParser::trimmed($row->value($this->categoryCodeField()));
            $errors = [];

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

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

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

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

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

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

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

            $errors = array_merge($errors, $this->validateAdditionalRowFields($row));

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

        return [
            'outcomes' => $outcomes,
            'codesInFile' => $codesInFile,
            'regionCodesInFile' => $regionCodesInFile,
            'categoryCodesInFile' => $categoryCodesInFile,
        ];
    }

    /**
     * @param  list<DataImportRow>  $rows
     * @param  list<DataImportRowPrecheckOutcome>  $outcomes
     * @param  list<string>  $codesInFile
     * @param  list<string>  $regionCodesInFile
     * @param  list<string>  $categoryCodesInFile
     * @param  array<string, int>  $categoryIdsByCode
     * @param  array<string, DberpRegion>  $regionsByCode
     * @return list<DataImportRowPrecheckOutcome>
     */
    protected function applyCommonPartnerReferenceChecks(
        array $rows,
        array $outcomes,
        array $codesInFile,
        array $regionCodesInFile,
        array $categoryCodesInFile,
        array $categoryIdsByCode,
        array $regionsByCode,
    ): array {
        $outcomes = $this->applyDuplicateFieldErrors(
            $rows,
            $outcomes,
            'code',
            'dberp.data_import.validation.code_duplicate_in_file',
        );

        $existingCodes = $this->loadExistingValues(
            $this->partnerModelClass(),
            'code',
            array_values(array_unique($codesInFile)),
        );

        foreach ($rows as $row) {
            $code = DataImportValueParser::trimmed($row->value('code'));
            $regionCode = DataImportValueParser::trimmed($row->value('region_code'));
            $categoryCode = DataImportValueParser::trimmed($row->value($this->categoryCodeField()));
            $errors = [];

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

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

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

            $errors = array_merge($errors, $this->validateAdditionalReferenceFields(
                $row,
                $categoryIdsByCode,
                $regionsByCode,
            ));

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

        return $outcomes;
    }

    /**
     * @return array<string, string|null>
     */
    protected function buildCommonPartnerPayload(
        array $snapshot,
        array $categoryIdsByCode,
        array $regionsByCode,
    ): array {
        $categoryCode = DataImportValueParser::trimmed($snapshot[$this->categoryCodeField()] ?? null);
        $regionCode = DataImportValueParser::trimmed($snapshot['region_code'] ?? null);
        $payload = [
            'code' => DataImportValueParser::trimmed($snapshot['code'] ?? null),
            'name' => (string) DataImportValueParser::trimmed($snapshot['name'] ?? null),
            'contact_person' => DataImportValueParser::trimmed($snapshot['contact_person'] ?? null),
            'mobile' => DataImportValueParser::trimmed($snapshot['mobile'] ?? null),
            'phone' => DataImportValueParser::trimmed($snapshot['phone'] ?? null),
            'email' => DataImportValueParser::parseOptionalEmail($snapshot['email'] ?? null),
            'address' => DataImportValueParser::trimmed($snapshot['address'] ?? null),
            'settlement_method' => DataImportValueParser::parseSettlementMethod($snapshot['settlement_method'] ?? null),
            'is_enabled' => DataImportValueParser::parseBoolean($snapshot['is_enabled'] ?? null, true),
            'sort_order' => DataImportValueParser::parseSortOrder($snapshot['sort_order'] ?? null, 0),
            'remark' => DataImportValueParser::trimmed($snapshot['remark'] ?? null),
            'country_code' => null,
            'region_id' => null,
            'region_name_snapshot' => null,
        ];

        if ($categoryCode !== null) {
            $payload[$this->categoryForeignKey()] = $categoryIdsByCode[$categoryCode] ?? null;
        } else {
            $payload[$this->categoryForeignKey()] = null;
        }

        if ($regionCode !== null) {
            $region = $regionsByCode[$regionCode] ?? null;

            if ($region === null) {
                throw new \RuntimeException((string) __('dberp.data_import.validation.invalid_region_code', [
                    'value' => $regionCode,
                ]));
            }

            $payload['country_code'] = $region->country_code;
            $payload['region_id'] = (int) $region->getKey();
            $payload['region_name_snapshot'] = $region->merger_name;
        }

        return $payload;
    }

    abstract protected function categoryForeignKey(): string;

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

    /**
     * @param  array<string, int>  $categoryIdsByCode
     * @param  array<string, DberpRegion>  $regionsByCode
     * @return list<string>
     */
    protected function validateAdditionalReferenceFields(
        DataImportRow $row,
        array $categoryIdsByCode,
        array $regionsByCode,
    ): array {
        return [];
    }

    /**
     * @param  list<DataImportRowPrecheckOutcome>  $outcomes
     */
    protected 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);
    }
}
