<?php
namespace PortalPeople\DisabledPostcodes\Model\Import;

use Magento\Framework\Exception\LocalizedException;
use PortalPeople\DisabledPostcodes\Model\DisabledPostcodeFactory;
use PortalPeople\DisabledPostcodes\Model\PostcodeMatcher;
use PortalPeople\DisabledPostcodes\Model\ResourceModel\DisabledPostcode as DisabledPostcodeResource;
use PortalPeople\DisabledPostcodes\Model\ResourceModel\DisabledPostcode\CollectionFactory;

class PostcodeImporter
{
    private DisabledPostcodeFactory $postcodeFactory;
    private DisabledPostcodeResource $postcodeResource;
    private PostcodeMatcher $matcher;
    private CollectionFactory $collectionFactory;

    public function __construct(
        DisabledPostcodeFactory $postcodeFactory,
        DisabledPostcodeResource $postcodeResource,
        PostcodeMatcher $matcher,
        CollectionFactory $collectionFactory
    ) {
        $this->postcodeFactory = $postcodeFactory;
        $this->postcodeResource = $postcodeResource;
        $this->matcher = $matcher;
        $this->collectionFactory = $collectionFactory;
    }

    public function import(string $filePath, string $originalName, int $defaultStoreId = 0): array
    {
        $rows = $this->readRows($filePath, $originalName);
        $created = 0;
        $updated = 0;
        $skipped = 0;

        foreach ($rows as $line => $row) {
            $postcode = (string)($row['postcode'] ?? '');
            if (trim($postcode) === '') {
                $skipped++;
                continue;
            }

            try {
                $normalized = $this->matcher->normalizePostcode($postcode);
                $this->matcher->validatePostcode($normalized);
                $matchType = $this->matcher->validateMatchType((string)($row['match_type'] ?? 'area'));
                $storeId = isset($row['store_id']) && $row['store_id'] !== '' ? (int)$row['store_id'] : $defaultStoreId;
                $description = isset($row['description']) ? (string)$row['description'] : null;
                $isActive = !isset($row['is_active']) || (int)$row['is_active'] === 1;

                $existing = $this->collectionFactory->create()
                    ->addFieldToFilter('normalized_postcode', $normalized)
                    ->addFieldToFilter('match_type', $matchType)
                    ->addFieldToFilter('store_id', $storeId)
                    ->setPageSize(1)
                    ->getFirstItem();

                $record = $existing->getId() ? $existing : $this->postcodeFactory->create();

                $record->setData('postcode', strtoupper(trim($postcode)));
                $record->setData('normalized_postcode', $normalized);
                $record->setData('match_type', $matchType);
                $record->setData('store_id', $storeId);
                $record->setData('description', $description);
                $record->setData('is_active', $isActive ? 1 : 0);

                $isNew = !$record->getId();
                $this->postcodeResource->save($record);
                if ($isNew) {
                    $created++;
                } else {
                    $updated++;
                }
            } catch (\Throwable $e) {
                $skipped++;
            }
        }

        return [
            'created' => $created,
            'updated' => $updated,
            'skipped' => $skipped,
            'total' => count($rows),
        ];
    }

    private function readRows(string $filePath, string $originalName): array
    {
        $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
        if ($extension === 'csv') {
            return $this->readCsv($filePath);
        }
        if ($extension === 'xlsx') {
            return $this->readXlsx($filePath);
        }

        throw new LocalizedException(__('Unsupported file format. Please upload CSV or XLSX.'));
    }

    private function readCsv(string $filePath): array
    {
        $rows = [];
        $file = new \SplFileObject($filePath);
        $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);

        $headers = null;
        foreach ($file as $row) {
            if (!is_array($row) || count($row) === 0) {
                continue;
            }
            if ($headers === null) {
                $headers = array_map(static function ($value) {
                    return strtolower(trim((string)$value));
                }, $row);
                continue;
            }
            $rows[] = $this->mapRow($headers, $row);
        }

        return $rows;
    }

    private function readXlsx(string $filePath): array
    {
        $zip = new \ZipArchive();
        if ($zip->open($filePath) !== true) {
            throw new LocalizedException(__('Unable to open XLSX file.'));
        }

        $sharedStrings = [];
        $sharedXml = $zip->getFromName('xl/sharedStrings.xml');
        if ($sharedXml) {
            $sx = @simplexml_load_string($sharedXml);
            if ($sx !== false) {
                foreach ($sx->si as $item) {
                    $sharedStrings[] = (string)$item->t;
                }
            }
        }

        $sheetXml = $zip->getFromName('xl/worksheets/sheet1.xml');
        $zip->close();
        if (!$sheetXml) {
            throw new LocalizedException(__('XLSX file does not contain sheet1.'));
        }

        $sx = @simplexml_load_string($sheetXml);
        if ($sx === false) {
            throw new LocalizedException(__('Unable to read XLSX worksheet.'));
        }

        $rows = [];
        $headers = null;

        foreach ($sx->sheetData->row as $row) {
            $values = [];
            foreach ($row->c as $cell) {
                $type = (string)$cell['t'];
                $raw = (string)$cell->v;
                if ($type === 's') {
                    $idx = (int)$raw;
                    $values[] = $sharedStrings[$idx] ?? '';
                } else {
                    $values[] = $raw;
                }
            }

            if ($headers === null) {
                $headers = array_map(static function ($value) {
                    return strtolower(trim((string)$value));
                }, $values);
                continue;
            }

            $rows[] = $this->mapRow($headers, $values);
        }

        return $rows;
    }

    private function mapRow(array $headers, array $values): array
    {
        $mapped = [];
        foreach ($headers as $index => $header) {
            if ($header === '') {
                continue;
            }
            $mapped[$header] = isset($values[$index]) ? trim((string)$values[$index]) : '';
        }

        return $mapped;
    }
}
