<?php
namespace PortalPeople\DisabledPostcodes\Model;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
use Magento\Store\Model\StoreManagerInterface;
use PortalPeople\DisabledPostcodes\Api\DisabledPostcodeManagementInterface;
use PortalPeople\DisabledPostcodes\Model\ResourceModel\DisabledPostcode as DisabledPostcodeResource;
use PortalPeople\DisabledPostcodes\Model\ResourceModel\DisabledPostcode\CollectionFactory;
use Psr\Log\LoggerInterface;

class DisabledPostcodeManagement implements DisabledPostcodeManagementInterface
{
    private DisabledPostcodeFactory $postcodeFactory;
    private DisabledPostcodeResource $postcodeResource;
    private CollectionFactory $collectionFactory;
    private PostcodeMatcher $matcher;
    private Config $config;
    private StoreManagerInterface $storeManager;
    private ApiRateLimiter $rateLimiter;
    private RemoteAddress $remoteAddress;
    private LoggerInterface $logger;

    public function __construct(
        DisabledPostcodeFactory $postcodeFactory,
        DisabledPostcodeResource $postcodeResource,
        CollectionFactory $collectionFactory,
        PostcodeMatcher $matcher,
        Config $config,
        StoreManagerInterface $storeManager,
        ApiRateLimiter $rateLimiter,
        RemoteAddress $remoteAddress,
        LoggerInterface $logger
    ) {
        $this->postcodeFactory = $postcodeFactory;
        $this->postcodeResource = $postcodeResource;
        $this->collectionFactory = $collectionFactory;
        $this->matcher = $matcher;
        $this->config = $config;
        $this->storeManager = $storeManager;
        $this->rateLimiter = $rateLimiter;
        $this->remoteAddress = $remoteAddress;
        $this->logger = $logger;
    }

    public function checkPostcode(string $postcode, ?int $storeId = null): array
    {
        $ip = (string)($this->remoteAddress->getRemoteAddress() ?: 'unknown');
        $this->rateLimiter->assertAllowed('check|' . $ip, 100, 60);

        $resolvedStoreId = $this->resolveStoreId($storeId);
        return $this->matcher->checkPostcode($postcode, $resolvedStoreId);
    }

    public function getList(?int $storeId = null): array
    {
        $resolvedStoreId = $storeId === null ? null : max(0, $storeId);
        $collection = $this->collectionFactory->create();

        if ($resolvedStoreId !== null) {
            $collection->addFieldToFilter('store_id', ['in' => [0, $resolvedStoreId]]);
        }

        $collection->setOrder('store_id', 'DESC');
        $collection->setOrder('match_type', 'ASC');
        $collection->setOrder('postcode', 'ASC');

        $items = [];
        foreach ($collection as $item) {
            $items[] = $this->serializeModel($item);
        }

        return $items;
    }

    public function getById(int $id): array
    {
        $model = $this->postcodeFactory->create();
        $this->postcodeResource->load($model, $id);

        if (!$model->getId()) {
            throw new LocalizedException(__('Disabled postcode rule does not exist.'));
        }

        return $this->serializeModel($model);
    }

    public function create(
        string $postcode,
        string $matchType = 'area',
        int $storeId = 0,
        ?string $description = null,
        bool $isActive = true
    ): array {
        $model = $this->postcodeFactory->create();
        $this->persist($model, $postcode, $matchType, $storeId, $description, $isActive);

        $this->logger->info('Disabled postcode created', [
            'entity_id' => (int)$model->getId(),
            'postcode' => $model->getData('postcode'),
            'match_type' => $model->getData('match_type'),
            'store_id' => (int)$model->getData('store_id'),
        ]);

        return $this->serializeModel($model);
    }

    public function update(
        int $id,
        string $postcode,
        string $matchType = 'area',
        int $storeId = 0,
        ?string $description = null,
        bool $isActive = true
    ): array {
        $model = $this->postcodeFactory->create();
        $this->postcodeResource->load($model, $id);

        if (!$model->getId()) {
            throw new LocalizedException(__('Disabled postcode rule does not exist.'));
        }

        $this->persist($model, $postcode, $matchType, $storeId, $description, $isActive);

        $this->logger->info('Disabled postcode updated', [
            'entity_id' => (int)$model->getId(),
            'postcode' => $model->getData('postcode'),
            'match_type' => $model->getData('match_type'),
            'store_id' => (int)$model->getData('store_id'),
        ]);

        return $this->serializeModel($model);
    }

    public function delete(int $id): bool
    {
        $model = $this->postcodeFactory->create();
        $this->postcodeResource->load($model, $id);

        if (!$model->getId()) {
            throw new LocalizedException(__('Disabled postcode rule does not exist.'));
        }

        $this->postcodeResource->delete($model);
        $this->logger->info('Disabled postcode deleted', ['entity_id' => $id]);

        return true;
    }

    private function persist(
        \PortalPeople\DisabledPostcodes\Model\DisabledPostcode $model,
        string $postcode,
        string $matchType,
        int $storeId,
        ?string $description,
        bool $isActive
    ): void {
        $normalized = $this->matcher->normalizePostcode($postcode);
        $this->matcher->validatePostcode($normalized);
        $validatedType = $this->matcher->validateMatchType($matchType);

        $storeId = max(0, $storeId);

        $model->setData('postcode', strtoupper(trim($postcode)));
        $model->setData('normalized_postcode', $normalized);
        $model->setData('match_type', $validatedType);
        $model->setData('store_id', $storeId);
        $model->setData('description', $description !== null ? trim($description) : null);
        $model->setData('is_active', $isActive ? 1 : 0);

        try {
            $this->postcodeResource->save($model);
        } catch (\Throwable $e) {
            throw new LocalizedException(__('Unable to save disabled postcode rule. %1', $e->getMessage()));
        }
    }

    private function resolveStoreId(?int $storeId): int
    {
        if ($storeId !== null) {
            return max(0, $storeId);
        }

        return (int)$this->storeManager->getStore()->getId();
    }

    private function serializeModel(\PortalPeople\DisabledPostcodes\Model\DisabledPostcode $model): array
    {
        return [
            'id' => (int)$model->getId(),
            'postcode' => (string)$model->getData('postcode'),
            'normalized_postcode' => (string)$model->getData('normalized_postcode'),
            'match_type' => (string)$model->getData('match_type'),
            'store_id' => (int)$model->getData('store_id'),
            'description' => (string)$model->getData('description'),
            'is_active' => (bool)$model->getData('is_active'),
            'created_at' => (string)$model->getData('created_at'),
            'updated_at' => (string)$model->getData('updated_at'),
        ];
    }
}
