<?php

declare(strict_types=1);

namespace App\Business\MasterData;

use App\Business\Inventory\Enums\InventoryDocumentStatus;
use App\Business\Purchase\Enums\PurchaseOrderStatus;
use App\Business\Sales\Enums\SalesOrderStatus;
use App\Models\DberpCustomer;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpPurchaseReceiptOrder;
use App\Models\DberpSalesOrder;
use App\Models\DberpSalesOutboundOrder;
use App\Models\DberpSupplier;
use App\Models\DberpWarehouse;
use Illuminate\Database\Eloquent\Model;

class DberpDocumentAddressSnapshotService
{
    public function __construct(
        private readonly DberpRegionAddressService $regionAddressService,
    ) {}

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public function fillSupplierSnapshot(array $data, string $foreignKey = 'supplier_id', string $prefix = 'supplier'): array
    {
        $supplierId = filled($data[$foreignKey] ?? null) ? (int) $data[$foreignKey] : null;
        $supplier = $supplierId === null ? null : DberpSupplier::query()->find($supplierId);

        return $this->fillCounterpartySnapshot($data, $prefix, $supplier);
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public function fillCustomerSnapshot(array $data, string $foreignKey = 'customer_id', string $prefix = 'customer'): array
    {
        $customerId = filled($data[$foreignKey] ?? null) ? (int) $data[$foreignKey] : null;
        $customer = $customerId === null ? null : DberpCustomer::query()->find($customerId);

        return $this->fillCounterpartySnapshot($data, $prefix, $customer);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function fillCustomerSnapshotWhenNeeded(
        array $payload,
        ?DberpSalesOrder $record = null,
        string $foreignKey = 'customer_id',
        string $prefix = 'customer',
    ): array {
        return $this->applyCounterpartySnapshotWhenNeeded(
            $payload,
            $record,
            $foreignKey,
            $prefix,
            fn (DberpSalesOrder $order): bool => $order->status === SalesOrderStatus::Draft->value,
            fn (array $data) => $this->fillCustomerSnapshot($data, $foreignKey, $prefix),
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function fillSupplierSnapshotWhenNeeded(
        array $payload,
        ?DberpPurchaseOrder $record = null,
        string $foreignKey = 'supplier_id',
        string $prefix = 'supplier',
    ): array {
        return $this->applyCounterpartySnapshotWhenNeeded(
            $payload,
            $record,
            $foreignKey,
            $prefix,
            fn (DberpPurchaseOrder $order): bool => $order->status === PurchaseOrderStatus::Draft->value,
            fn (array $data) => $this->fillSupplierSnapshot($data, $foreignKey, $prefix),
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function fillCustomerSnapshotWhenNeededForSalesOutbound(
        array $payload,
        ?DberpSalesOutboundOrder $record = null,
        string $foreignKey = 'customer_id',
        string $prefix = 'customer',
    ): array {
        return $this->applyCounterpartySnapshotWhenNeeded(
            $payload,
            $record,
            $foreignKey,
            $prefix,
            fn (DberpSalesOutboundOrder $order): bool => $order->status === InventoryDocumentStatus::Draft->value,
            fn (array $data) => $this->fillCustomerSnapshot($data, $foreignKey, $prefix),
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function fillSupplierSnapshotWhenNeededForPurchaseReceipt(
        array $payload,
        ?DberpPurchaseReceiptOrder $record = null,
        string $foreignKey = 'supplier_id',
        string $prefix = 'supplier',
    ): array {
        return $this->applyCounterpartySnapshotWhenNeeded(
            $payload,
            $record,
            $foreignKey,
            $prefix,
            fn (DberpPurchaseReceiptOrder $order): bool => $order->status === InventoryDocumentStatus::Draft->value,
            fn (array $data) => $this->fillSupplierSnapshot($data, $foreignKey, $prefix),
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function copyCustomerSnapshotFromSalesOrder(
        array $payload,
        DberpSalesOrder $salesOrder,
        string $prefix = 'customer',
    ): array {
        return $this->copyCounterpartySnapshotFromRecord($payload, $salesOrder, $prefix);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function copySupplierSnapshotFromPurchaseOrder(
        array $payload,
        DberpPurchaseOrder $purchaseOrder,
        string $prefix = 'supplier',
    ): array {
        return $this->copyCounterpartySnapshotFromRecord($payload, $purchaseOrder, $prefix);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function applyCustomerSnapshotForSalesOutboundCreate(array $payload): array
    {
        $salesOrderId = filled($payload['sales_order_id'] ?? null) ? (int) $payload['sales_order_id'] : null;

        if ($salesOrderId === null) {
            return $this->fillCustomerSnapshot($payload);
        }

        $salesOrder = DberpSalesOrder::query()->find($salesOrderId);

        if (! $salesOrder instanceof DberpSalesOrder) {
            return $this->fillCustomerSnapshot($payload);
        }

        return $this->copyCustomerSnapshotFromSalesOrder($payload, $salesOrder);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function applySupplierSnapshotForPurchaseReceiptCreate(array $payload): array
    {
        $purchaseOrderId = filled($payload['purchase_order_id'] ?? null) ? (int) $payload['purchase_order_id'] : null;

        if ($purchaseOrderId === null) {
            return $this->fillSupplierSnapshot($payload);
        }

        $purchaseOrder = DberpPurchaseOrder::query()->find($purchaseOrderId);

        if (! $purchaseOrder instanceof DberpPurchaseOrder) {
            return $this->fillSupplierSnapshot($payload);
        }

        return $this->copySupplierSnapshotFromPurchaseOrder($payload, $purchaseOrder);
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public function fillWarehouseSnapshot(array $data, string $foreignKey = 'warehouse_id', string $prefix = 'warehouse'): array
    {
        $warehouseId = filled($data[$foreignKey] ?? null) ? (int) $data[$foreignKey] : null;
        $warehouse = $warehouseId === null ? null : DberpWarehouse::query()->find($warehouseId);

        if (! $warehouse instanceof DberpWarehouse) {
            return $this->clearWarehouseSnapshot($data, $prefix);
        }

        $data["{$prefix}_contact_phone_snapshot"] = $warehouse->contact_phone;
        $data["{$prefix}_country_code_snapshot"] = $warehouse->country_code;
        $data["{$prefix}_region_name_snapshot"] = $warehouse->region_name_snapshot;
        $data["{$prefix}_address_snapshot"] = $warehouse->address;

        return $data;
    }

    public function allowCounterpartyMasterDataFallback(?string $documentStatus): bool
    {
        if ($documentStatus === null || $documentStatus === '') {
            return true;
        }

        return $documentStatus === 'draft';
    }

    public function emptySnapshotDisplayLabel(): string
    {
        return (string) __('dberp.messages.document_snapshot_not_recorded');
    }

    public function resolveSnapshotFieldDisplay(
        ?string $snapshotValue,
        ?string $masterValue,
        bool $allowMasterFallback,
    ): string {
        if (filled($snapshotValue)) {
            return (string) $snapshotValue;
        }

        if ($allowMasterFallback && filled($masterValue)) {
            return (string) $masterValue;
        }

        return $this->emptySnapshotDisplayLabel();
    }

    public function formatAddressSummary(
        ?string $countryCode,
        ?string $regionSnapshot,
        ?string $address,
        ?string $fallbackCountryCode = null,
        ?string $fallbackRegionSnapshot = null,
        ?string $fallbackAddress = null,
        bool $allowMasterFallback = true,
    ): string {
        $result = $this->regionAddressService->formatRegionAddressSummary(
            $countryCode,
            $regionSnapshot,
            $address,
            fallbackCountryCode: $allowMasterFallback ? $fallbackCountryCode : null,
            fallbackRegionSnapshot: $allowMasterFallback ? $fallbackRegionSnapshot : null,
            fallbackAddress: $allowMasterFallback ? $fallbackAddress : null,
        );

        if ($allowMasterFallback) {
            return $result;
        }

        if ($result === (string) __('dberp.messages.address_detail_not_set')) {
            return $this->emptySnapshotDisplayLabel();
        }

        return $result;
    }

    public function formatContactSummary(
        ?string $contactPerson = null,
        ?string $mobile = null,
        ?string $phone = null,
        ?string $fallbackContactPerson = null,
        ?string $fallbackMobile = null,
        ?string $fallbackPhone = null,
        bool $allowMasterFallback = true,
    ): string {
        $resolvedPerson = filled($contactPerson)
            ? $contactPerson
            : ($allowMasterFallback ? $fallbackContactPerson : null);
        $resolvedMobile = filled($mobile)
            ? $mobile
            : ($allowMasterFallback ? $fallbackMobile : null);
        $resolvedPhone = filled($phone)
            ? $phone
            : ($allowMasterFallback ? $fallbackPhone : null);

        $parts = array_values(array_filter([
            $resolvedPerson,
            $resolvedMobile,
            $resolvedPhone,
        ], static fn (?string $value): bool => filled($value)));

        if ($parts === []) {
            return $allowMasterFallback
                ? (string) __('dberp.messages.contact_not_set')
                : $this->emptySnapshotDisplayLabel();
        }

        return implode(' / ', $parts);
    }

    /**
     * @return array{address: string, contact: string}
     */
    public function formatCounterpartySummariesForDocument(
        Model $record,
        string $prefix,
        DberpCustomer|DberpSupplier|null $party,
        bool $allowMasterFallback,
    ): array {
        return [
            'address' => $this->formatAddressSummary(
                $record->getAttribute("{$prefix}_country_code_snapshot"),
                $record->getAttribute("{$prefix}_region_name_snapshot"),
                $record->getAttribute("{$prefix}_address_snapshot"),
                $party?->country_code,
                $party?->region_name_snapshot,
                $party?->address,
                allowMasterFallback: $allowMasterFallback,
            ),
            'contact' => $this->formatContactSummary(
                $record->getAttribute("{$prefix}_contact_person_snapshot"),
                $record->getAttribute("{$prefix}_mobile_snapshot"),
                $record->getAttribute("{$prefix}_phone_snapshot"),
                $party?->contact_person,
                $party?->mobile,
                $party?->phone,
                allowMasterFallback: $allowMasterFallback,
            ),
        ];
    }

    /**
     * @template T of DberpSalesOrder|DberpPurchaseOrder
     *
     * @param  array<string, mixed>  $payload
     * @param  T|null  $record
     * @param  callable(T): bool  $isDraft
     * @param  callable(array<string, mixed>): array<string, mixed>  $fillSnapshot
     * @return array<string, mixed>
     */
    private function applyCounterpartySnapshotWhenNeeded(
        array $payload,
        ?Model $record,
        string $foreignKey,
        string $prefix,
        callable $isDraft,
        callable $fillSnapshot,
    ): array {
        if ($record !== null && ! $isDraft($record)) {
            return $this->stripCounterpartySnapshotFromPayload($payload, $prefix);
        }

        if ($record === null) {
            return $fillSnapshot($payload);
        }

        if ($this->counterpartyForeignKeyChanged($payload, $record, $foreignKey)) {
            return $fillSnapshot($payload);
        }

        if ($this->counterpartySnapshotExists($record, $prefix)) {
            return $this->stripCounterpartySnapshotFromPayload($payload, $prefix);
        }

        return $fillSnapshot($payload);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    private function stripCounterpartySnapshotFromPayload(array $payload, string $prefix): array
    {
        foreach ($this->counterpartySnapshotKeys($prefix) as $key) {
            unset($payload[$key]);
        }

        return $payload;
    }

    /**
     * @return list<string>
     */
    private function counterpartySnapshotKeys(string $prefix): array
    {
        return [
            "{$prefix}_contact_person_snapshot",
            "{$prefix}_mobile_snapshot",
            "{$prefix}_phone_snapshot",
            "{$prefix}_country_code_snapshot",
            "{$prefix}_region_name_snapshot",
            "{$prefix}_address_snapshot",
        ];
    }

    private function counterpartyForeignKeyChanged(array $payload, Model $record, string $foreignKey): bool
    {
        $newId = filled($payload[$foreignKey] ?? null) ? (int) $payload[$foreignKey] : null;
        $currentId = $record->getAttribute($foreignKey);
        $currentId = $currentId !== null ? (int) $currentId : null;

        return $newId !== $currentId;
    }

    private function counterpartySnapshotExists(Model $record, string $prefix): bool
    {
        foreach ($this->counterpartySnapshotKeys($prefix) as $key) {
            if (filled($record->getAttribute($key))) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    private function copyCounterpartySnapshotFromRecord(array $payload, Model $source, string $prefix): array
    {
        foreach ($this->counterpartySnapshotKeys($prefix) as $key) {
            $value = $source->getAttribute($key);

            if (filled($value)) {
                $payload[$key] = $value;
            }
        }

        return $payload;
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    private function fillCounterpartySnapshot(array $data, string $prefix, DberpSupplier|DberpCustomer|null $party): array
    {
        if ($party === null) {
            return $this->clearCounterpartySnapshot($data, $prefix);
        }

        $data["{$prefix}_contact_person_snapshot"] = $party->contact_person;
        $data["{$prefix}_mobile_snapshot"] = $party->mobile;
        $data["{$prefix}_phone_snapshot"] = $party->phone;
        $data["{$prefix}_country_code_snapshot"] = $party->country_code;
        $data["{$prefix}_region_name_snapshot"] = $party->region_name_snapshot;
        $data["{$prefix}_address_snapshot"] = $party->address;

        return $data;
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    private function clearCounterpartySnapshot(array $data, string $prefix): array
    {
        $data["{$prefix}_contact_person_snapshot"] = null;
        $data["{$prefix}_mobile_snapshot"] = null;
        $data["{$prefix}_phone_snapshot"] = null;
        $data["{$prefix}_country_code_snapshot"] = null;
        $data["{$prefix}_region_name_snapshot"] = null;
        $data["{$prefix}_address_snapshot"] = null;

        return $data;
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    private function clearWarehouseSnapshot(array $data, string $prefix): array
    {
        $data["{$prefix}_contact_phone_snapshot"] = null;
        $data["{$prefix}_country_code_snapshot"] = null;
        $data["{$prefix}_region_name_snapshot"] = null;
        $data["{$prefix}_address_snapshot"] = null;

        return $data;
    }
}
