<?php

/**
 * Copyright 2024-2025 Simply.IN Sp. z o.o.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the EUPL-1.2 or later.
 * You may not use this work except in compliance with the Licence.
 *
 * Copy of the Licence is available at:
 * https://joinup.ec.europa.eu/software/page/eupl
 * It is bundled with this package in the file LICENSE.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an as is basis,
 * without warranties or conditions of any kind, either express or implied.
 * Check the Licence for the specific language governing permissions
 * and limitations under the License.
 *
 * @author   Simply.IN Sp. z o.o.
 * @copyright 2024-2025 Simply.IN Sp. z o.o.
 * @license   https://joinup.ec.europa.eu/software/page/eupl
 */

declare(strict_types=1);

namespace SimplyIn\Ajax\Service;

use Country;
use Simplyin;
use SimplyIn\Api\SimplyInApiClient;
use Validate;

if (!defined('_PS_VERSION_')) {
    exit;
}

class SimplyInAddressService
{
    private const BILLING = 'billingAddresses';
    private const SHIPPING = 'shippingAddresses';
    private const PARCEL = 'parcelLockers';

    public function __construct(
        private readonly SimplyInApiClient $api,
        private readonly Simplyin $module,
        private readonly AddressService $addressService,
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function userData(?string $authToken): array
    {
        $result = $this->api->apiCall(false, 'GET', 'userDataCommerce', $authToken);

        return is_array($result) ? $result : [];
    }

    /**
     * @param array<string, mixed> $data userData payload
     *
     * @return array{address: array<string, mixed>|false, address_type: string}
     */
    public function findInUserData(array $data, string $id, string $type = ''): array
    {
        $root = $data['data'] ?? $data['userData'] ?? [];
        $collection = match ($type) {
            self::BILLING => $root[self::BILLING] ?? [],
            self::SHIPPING => $root[self::SHIPPING] ?? [],
            default => $root[self::PARCEL] ?? [],
        };
        $addressType = match ($type) {
            self::BILLING => self::BILLING,
            self::SHIPPING => self::SHIPPING,
            default => self::PARCEL,
        };

        foreach ($collection as $item) {
            if (($item['_id'] ?? null) === $id) {
                return ['address' => $item, 'address_type' => $addressType];
            }
        }

        return ['address' => false, 'address_type' => ''];
    }

    /**
     * @return array{address_type: string, address: array<string, mixed>}
     */
    public function createEmptyAddress(string $addressType): array
    {
        return [
            'address_type' => $addressType,
            'address' => [
                '_id' => '',
                'name' => '',
                'surname' => '',
                'street' => '',
                'postalCode' => '',
                'city' => '',
                'companyName' => '',
                'appartmentNumber' => '',
                'addressName' => '',
                'taxId' => '',
                'country' => $this->addressService->getDefaultCountryIso(),
            ],
        ];
    }

    /**
     * @param array<string, mixed> $address
     *
     * @return array<string, string>|true
     */
    public function validate(array $address): array|bool
    {
        if (($address['address_type'] ?? '') === self::PARCEL) {
            return true;
        }

        $errors = [];
        // Ternary (not elvis) on purpose: core Validate methods are inconsistent — isCityName()
        // returns preg_match()'s raw int 1, the rest return bool. An elvis passes that 1 through
        // and the strict `!== true` below then rejects every VALID city with the message "1".
        $rules = [
            'name' => fn ($v) => Validate::isCustomerName($v) ? true : 'Invalid name',
            'surname' => fn ($v) => Validate::isCustomerName($v) ? true : 'Invalid surname',
            'taxId' => fn ($v) => Validate::isGenericName($v) ? true : 'Invalid tax ID',
            'companyName' => fn ($v) => Validate::isGenericName($v) ? true : 'Invalid company name',
            'addressName' => fn ($v) => Validate::isGenericName($v) ? true : 'Invalid address name',
            'street' => fn ($v) => Validate::isAddress($v) ? true : 'Invalid street',
            'city' => fn ($v) => Validate::isCityName($v) ? true : 'Invalid city',
            'postalCode' => fn ($v) => Validate::isPostCode($v) ? true : 'Invalid postal code',
        ];

        foreach ($rules as $field => $check) {
            $value = (string) ($address[$field] ?? '');
            $result = $check($value);
            if ($result !== true) {

                $errors[$field] = $this->module->l($result, 'ajax');
            }
        }

        $idCountry = Country::getByIso((string) ($address['country'] ?? ''));
        if ($idCountry) {
            $country = new Country($idCountry);
            if ($country->id && !$country->checkZipCode((string) ($address['postalCode'] ?? ''))) {
                $errors['postalCode'] = sprintf(
                    $this->module->l('Invalid postal code: %s', 'ajax'),
                    (string) $country->zip_code_format,
                );
            }
        }

        return $errors === [] ? true : $errors;
    }

    /**
     * @param array<string, mixed> $address
     *
     * @return array<string, mixed>
     */
    public function saveExisting(array $address, string $id, ?string $authToken): array
    {
        $isValid = $this->validate($address);
        if ($isValid !== true) {
            return ['is_valid' => $isValid, 'success' => true];
        }

        $userData = $this->userData($authToken);
        $data = ['userData' => $userData['data'] ?? []];
        $addressType = (string) ($address['address_type'] ?? '');

        foreach ([self::BILLING, self::SHIPPING] as $bucket) {
            foreach ($data['userData'][$bucket] ?? [] as $key => $item) {
                if (($item['_id'] ?? null) !== $id) {
                    continue;
                }
                foreach ($address as $addrKey => $value) {
                    if ($addrKey === 'taxId' && $addressType === self::SHIPPING) {
                        continue;
                    }
                    if (isset($data['userData'][$bucket][$key][$addrKey])) {
                        $data['userData'][$bucket][$key][$addrKey] = $value;
                    }
                }

                break;
            }
        }

        $result = $this->api->apiCall($data, 'PATCH', 'userData', $authToken);
        $addressData = $this->findInUserData(is_array($result) ? $result : [], $id, $addressType);

        return [
            'address_type' => $addressType,
            'address' => $addressData['address'],
            'is_valid' => true,
            'success' => true,
        ];
    }

    /**
     * @param array<string, mixed> $address
     *
     * @return array<string, mixed>
     */
    public function createNew(array $address, ?string $authToken): array
    {
        $isValid = $this->validate($address);
        if ($isValid !== true) {
            return ['is_valid' => $isValid, 'success' => true];
        }

        $userData = $this->userData($authToken);
        $data = ['userData' => $userData['data'] ?? []];
        $addressType = (string) ($address['address_type'] ?? '');

        if ($addressType === self::BILLING) {
            $data['userData'][self::BILLING][] = $address;
        } elseif ($addressType === self::SHIPPING) {
            $data['userData'][self::SHIPPING][] = $address;
        } else {
            unset($address['address_type']);
            $data['userData'][self::PARCEL][] = $address;
            $addressType = self::PARCEL;
        }

        $this->api->apiCall($data, 'PATCH', 'userData', $authToken);
        $refreshed = $this->userData($authToken);
        $bucket = $refreshed['data'][$addressType] ?? [];
        $added = $bucket !== [] ? end($bucket) : null;

        return [
            'result' => $refreshed,
            'add_address' => $added,
            'address_type' => $addressType,
            'is_valid' => true,
            'success' => true,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function deleteFromUserData(string $id, string $addressType, ?string $authToken): array
    {
        $userData = $this->userData($authToken);
        $data = ['userData' => $userData['data'] ?? []];
        $bucket = match ($addressType) {
            self::BILLING => self::BILLING,
            self::SHIPPING => self::SHIPPING,
            default => self::PARCEL,
        };

        foreach ($data['userData'][$bucket] ?? [] as $key => $item) {
            if (($item['_id'] ?? null) === $id) {
                unset($data['userData'][$bucket][$key]);
                $data['userData'][$bucket] = array_values($data['userData'][$bucket]);

                break;
            }
        }

        $result = $this->api->apiCall($data, 'PATCH', 'userData', $authToken);

        return [
            $bucket => $result['data'][$bucket] ?? [],
            'success' => true,
        ];
    }

    /**
     * @param array<string, mixed> $data
     */
    public function getPhoneNumber(array $data): string
    {
        $root = $data['data'] ?? $data['userData'] ?? [];

        return (string) ($root['phoneNumber'] ?? '');
    }
}
