<?php

namespace PortalPeople\indicator\services;

use Yii;
use yii\base\BaseObject;
use backend\models\Cartsbe;
use backend\models\Cartlines;
use backend\models\Shipment;
use PortalPeople\cart\models\Cart as NgaugeCart;
use PortalPeople\indicator\Module;

class ProcessFilesService extends BaseObject
{
    public string $inboxPath = '';
    public string $processedPath = '';
    public string $failedPath = '';
    public string $cartClass = NgaugeCart::class;
    public ?Module $module = null;

    /** @var resource|false|null */
    private $ftpConn = null;

    public function process(): array
    {
        $result = [
            'success' => false,
            'processed' => 0,
            'created' => 0,
            'failed' => 0,
            'errors' => [],
        ];

        if (!is_dir($this->inboxPath)) {
            return $result;
        }

        $files = glob($this->inboxPath . '/*.xml');
        if (!is_array($files)) {
            $files = [];
        }

        // Open FTP connection once for all post-process moves
        if ($this->module && $this->module->moveAfterDownload) {
            $this->openFtpConnection();
        }

        try {
            foreach ($files as $filePath) {
                $filename = basename($filePath);

                try {
                    $content = file_get_contents($filePath);
                    if ($content === false) {
                        throw new \RuntimeException("Cannot read file: {$filename}");
                    }

                    $orders = $this->parseXml($content);
                    $imported = 0;

                    foreach ($orders as $orderData) {
                        if ($this->importOrder($orderData)) {
                            $imported++;
                        }
                    }

                    $result['created'] += $imported;
                    $result['processed']++;

                    $this->moveToProcessed($filePath);
                    $this->ftpMoveToArchive($filename);
                } catch (\Throwable $e) {
                    Yii::error(
                        "Failed to process {$filename}: " . $e->getMessage(),
                        'indicator.process'
                    );
                    $result['errors'][] = [
                        'file' => $filename,
                        'error' => $e->getMessage(),
                    ];
                    $result['failed']++;

                    $this->moveToFailed($filePath);
                    $this->ftpMoveToRoot($filename);
                }
            }
        } finally {
            $this->closeFtpConnection();
        }

        $result['success'] = $result['failed'] === 0;
        $this->sendSummaryEmail($result);

        return $result;
    }

    /**
     * @throws \RuntimeException
     */
    public function parseXml(string $content): array
    {
        libxml_use_internal_errors(true);
        $dom = new \DOMDocument();
        $ok  = $dom->loadXML($content);
        $errs = libxml_get_errors();
        libxml_clear_errors();
        libxml_use_internal_errors(false);

        if (!$ok) {
            $msg = 'Invalid XML format (content_size=' . strlen($content) . ')';
            if (!empty($errs)) {
                $msg .= ': ' . implode('; ', array_map(fn($e) => "L{$e->line} " . trim($e->message), $errs));
            }
            throw new \RuntimeException($msg);
        }

        $xml = simplexml_import_dom($dom);

        $nsSh = 'http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader';
        $xml->registerXPathNamespace('sh', $nsSh);

        $xp = fn(string $expr) => $xml->xpath($expr)[0] ?? null;

        // ---- Common SBDH header fields ----
        $orderNumber  = (string) ($xp('//sh:InstanceIdentifier') ?? '');
        $createdAt    = (string) ($xp('//sh:CreationDateAndTime') ?? '');
        $contactName  = (string) ($xp('//sh:Contact') ?? '');
        $contactEmail = (string) ($xp('//sh:EmailAddress') ?? '');

        if (empty($orderNumber)) {
            throw new \RuntimeException('Could not extract order number from XML');
        }

        $dateOrdered = !empty($createdAt) ? strtotime($createdAt) : time();
        if (!$dateOrdered) {
            $dateOrdered = time();
        }

        // ---- Format detection ----
        // GS1 ECOM v3 uses urn:gs1:ecom:order:xsd:3; EAN.UCC v2 uses urn:ean.ucc:order:2
        $namespaces = $xml->getNamespaces(true);
        if (in_array('urn:gs1:ecom:order:xsd:3', $namespaces, true)) {
            return $this->parseGs1V3($xml, $xp, $orderNumber, $dateOrdered, $contactName, $contactEmail);
        }

        return $this->parseEanUcc($xml, $xp, $orderNumber, $dateOrdered, $contactName, $contactEmail);
    }

    /**
     * Parse GS1 ECOM Order v3 format (order:orderMessage root element).
     * Quantity and item identification use XML attributes; no pricing data.
     */
    private function parseGs1V3(
        \SimpleXMLElement $xml,
        callable $xp,
        string $orderNumber,
        int $dateOrdered,
        string $contactName,
        string $contactEmail
    ): array {
        // ---- Account code from shipTo seller-assigned identifier ----
        $accountCode = '';
        $shipToNodes = $xml->xpath(
            '//shipTo/additionalPartyIdentification[@additionalPartyIdentificationTypeCode="SELLER_ASSIGNED_IDENTIFIER_FOR_A_PARTY"]'
        );
        if (!empty($shipToNodes)) {
            $accountCode = (string) $shipToNodes[0];
        }
        if (empty($accountCode)) {
            $accountCode = $this->module?->defaultAccountCode ?? 'INDICATOR001';
        }

        // ---- Delivery date ----
        $deliveryDate = (string) ($xp('//requestedDeliveryDateTime/date') ?? '');

        // ---- Special delivery note ----
        $specialNote = (string) ($xp('//specialDeliveryNote/text') ?? '');

        // ---- Line items ----
        $lineItems = $xml->xpath('//orderLineItem');
        $lines     = [];
        foreach ($lineItems as $item) {
            // Quantity: text content; UOM: attribute
            $qty = (float) ((string) ($item->requestedQuantity ?? '1') ?: 1);
            $uom = strtoupper((string) ($item->requestedQuantity['measurementUnitCode'] ?? ''));

            // Supplier code: attribute-typed additionalTradeItemIdentification
            $supplierCode = '';
            foreach ($item->transactionalTradeItem->additionalTradeItemIdentification ?? [] as $tid) {
                if ((string) $tid['additionalTradeItemIdentificationTypeCode'] === 'SUPPLIER_ASSIGNED') {
                    $supplierCode = (string) $tid;
                    break;
                }
            }

            $description = (string) ($item->transactionalTradeItem->tradeItemDescription ?? '');
            if (empty($description)) {
                $description = $supplierCode ?: 'Item';
            }

            $displayDesc = $description;
            if (!empty($uom) && $uom !== 'EA' && $uom !== 'EACH') {
                $displayDesc = "{$description} ({$uom})";
            }

            $lines[] = [
                'stockCode' => $supplierCode,
                'qty'       => $qty,
                'price'     => 0,
                'product'   => $displayDesc,
                'data'      => [],
            ];
        }

        return [[
            'orderNumber'          => $orderNumber,
            'deliveryCustomerCode' => $accountCode,
            'deliveryDate'         => $deliveryDate,
            'contactName'          => $contactName,
            'contactEmail'         => $contactEmail,
            'message'              => $specialNote,
            'dateordered'          => $dateOrdered,
            'lines'                => $lines,
        ]];
    }

    /**
     * Parse EAN.UCC Order v2 format (sh:StandardBusinessDocument root element).
     * Quantity and item identification use child elements; includes net pricing.
     */
    private function parseEanUcc(
        \SimpleXMLElement $xml,
        callable $xp,
        string $orderNumber,
        int $dateOrdered,
        string $contactName,
        string $contactEmail
    ): array {
        $xml->registerXPathNamespace('eanucc', 'urn:ean.ucc:2');
        $xml->registerXPathNamespace('order',  'urn:ean.ucc:order:2');

        // ---- Buyer account code ----
        $accountCode = '';
        $identNodes = $xml->xpath(
            '//buyer/additionalPartyIdentification[additionalPartyIdentificationType="SELLER_ASSIGNED_IDENTIFIER_FOR_A_PARTY"]/additionalPartyIdentificationValue'
        );
        if (!empty($identNodes)) {
            $accountCode = (string) $identNodes[0];
        }
        if (empty($accountCode)) {
            $accountCode = $this->module?->defaultAccountCode ?? 'ZUPA001';
        }

        // ---- Delivery date ----
        $deliveryDate = (string) ($xp('//requestedDeliveryDate/date') ?? '');

        // ---- Special delivery note ----
        $specialNote = (string) ($xp('//specialDeliveryNote/text') ?? '');

        // ---- Line items ----
        $lineItems = $xml->xpath('//orderLineItem');
        $lines     = [];
        foreach ($lineItems as $item) {
            $qty   = (float) ($item->requestedQuantity->value ?? 1);
            $uom   = (string) ($item->requestedQuantity->unitOfMeasure->measurementUnitCodeValue ?? '');
            $price = (float) ($item->netPrice->amount->monetaryAmount ?? 0);

            $supplierCode = '';
            $description  = '';
            foreach ($item->tradeItemIdentification->additionalTradeItemIdentification ?? [] as $tid) {
                $type  = (string) $tid->additionalTradeItemIdentificationType;
                $value = (string) $tid->additionalTradeItemIdentificationValue;
                if ($type === 'SUPPLIER_ASSIGNED') {
                    $supplierCode = $value;
                } elseif ($type === 'BUYER_ASSIGNED') {
                    $description = $value;
                }
            }

            if (empty($description)) {
                $description = $supplierCode ?: 'Item';
            }

            $displayDesc = $description;
            if (!empty($uom) && $uom !== 'EA') {
                $displayDesc = "{$description} ({$uom})";
            }

            $lines[] = [
                'stockCode' => $supplierCode,
                'qty'       => $qty,
                'price'     => $price,
                'product'   => $displayDesc,
                'data'      => [],
            ];
        }

        return [[
            'orderNumber'          => $orderNumber,
            'deliveryCustomerCode' => $accountCode,
            'deliveryDate'         => $deliveryDate,
            'contactName'          => $contactName,
            'contactEmail'         => $contactEmail,
            'message'              => $specialNote,
            'dateordered'          => $dateOrdered,
            'lines'                => $lines,
        ]];
    }

    private function importOrder(array $orderData): bool
    {
        try {
            return (bool) Yii::$app->db->transaction(function () use ($orderData) {
                // Deduplication: delete any existing basket+lines then re-import
                $existing = Cartsbe::find()->where(['porderref' => $orderData['orderNumber']])->one();
                if ($existing !== null) {
                    Cartlines::deleteAll(['basketid' => $existing->id]);
                    $existing->delete();
                }

                $result = NgaugeCart::fromApi($orderData);

                if (isset($result['errors'])) {
                    throw new \RuntimeException(
                        'Cart::fromApi() failed: ' . json_encode($result['errors'])
                    );
                }

                $localOrder = $result['order'] ?? null;
                if ($localOrder === null) {
                    throw new \RuntimeException("Cart::fromApi() returned no order for: {$orderData['orderNumber']}");
                }

                $localOrder->type        = 'indicator';
                $localOrder->sourceurl   = 'indicator';
                $localOrder->message     = $orderData['message'] ?? '';
                $localOrder->dateordered = $orderData['dateordered'] ?? time();
                $localOrder->save(false);

                $deliveryDate = !empty($orderData['deliveryDate'])
                    ? date('Y-m-d', strtotime($orderData['deliveryDate']))
                    : date('Y-m-d');

                $shipment = new Shipment();
                $shipment->id            = \common\models\Base::TempId();
                $shipment->cart_id       = $localOrder->id;
                $shipment->shipment_type = 0;
                $shipment->language      = $localOrder->language ?? 'en';
                $shipment->delivery      = 'Delivery';
                $shipment->shipment_date = $deliveryDate;
                $shipment->created_at    = time();

                if (!$shipment->save()) {
                    Yii::warning(
                        'Indicator: failed to save shipment for basket ' . $localOrder->id . ': ' . json_encode($shipment->errors),
                        'indicator.import'
                    );
                }

                Yii::info("Indicator order imported: {$orderData['orderNumber']} → cart {$localOrder->id}", 'indicator.import');

                return true;
            });
        } catch (\Throwable $e) {
            Yii::error(
                'Indicator import exception: ' . $e->getMessage() . ' | Data: ' . json_encode($orderData),
                'indicator.import'
            );

            return false;
        }
    }

    private function moveToProcessed(string $filePath): void
    {
        $filename = basename($filePath);
        $dest = $this->processedPath . DIRECTORY_SEPARATOR . $filename;

        if (!is_dir($this->processedPath)) {
            @mkdir($this->processedPath, 0755, true);
        }

        @rename($filePath, $dest);
    }

    private function moveToFailed(string $filePath): void
    {
        $filename = basename($filePath);
        $dest = $this->failedPath . DIRECTORY_SEPARATOR . $filename;

        if (!is_dir($this->failedPath)) {
            @mkdir($this->failedPath, 0755, true);
        }

        @rename($filePath, $dest);
    }

    private function openFtpConnection(): void
    {
        $m = $this->module;
        if (!$m) {
            return;
        }

        try {
            $conn = @ftp_ssl_connect($m->host, $m->port, 15);
            if (!$conn) {
                Yii::warning("Indicator FTP: could not connect to {$m->host}:{$m->port}", 'indicator');
                $this->ftpConn = false;
                return;
            }
            if (!@ftp_login($conn, $m->username, $m->password)) {
                Yii::warning("Indicator FTP: login failed for {$m->username}@{$m->host}", 'indicator');
                ftp_close($conn);
                $this->ftpConn = false;
                return;
            }
            if ($m->passiveMode) {
                ftp_pasv($conn, true);
            }
            $this->ftpConn = $conn;
        } catch (\Throwable $e) {
            Yii::warning("Indicator FTP connect error: " . $e->getMessage(), 'indicator');
            $this->ftpConn = false;
        }
    }

    private function closeFtpConnection(): void
    {
        if ($this->ftpConn) {
            @ftp_close($this->ftpConn);
        }
        $this->ftpConn = null;
    }

    private function ftpMoveToArchive(string $filename): void
    {
        if (!$this->ftpConn || !$this->module) {
            return;
        }
        $from = rtrim($this->module->moveToPath, '/') . '/' . $filename;
        $to   = rtrim($this->module->remotePath, '/') . '/' . $filename;
        if (!@ftp_rename($this->ftpConn, $from, $to)) {
            Yii::warning("Indicator FTP: could not move {$from} → {$to}", 'indicator');
        } else {
            Yii::info("Indicator FTP: archived {$filename} to {$to}", 'indicator');
        }
    }

    private function ftpMoveToRoot(string $filename): void
    {
        if (!$this->ftpConn || !$this->module) {
            return;
        }
        $from = rtrim($this->module->moveToPath, '/') . '/' . $filename;
        $to   = '/' . $filename;
        if (!@ftp_rename($this->ftpConn, $from, $to)) {
            Yii::warning("Indicator FTP: could not move {$from} → {$to}", 'indicator');
        } else {
            Yii::info("Indicator FTP: returned failed file {$filename} to FTP root", 'indicator');
        }
    }

    private function sendSummaryEmail(array $result): void
    {
        if (!$this->module || !$this->module->summaryEmail) {
            return;
        }

        try {
            $body = "Indicator Import Run Summary\n";
            $body .= str_repeat('=', 40) . "\n\n";
            $body .= "Processed files: {$result['processed']}\n";
            $body .= "Orders created: {$result['created']}\n";
            $body .= "Failed: {$result['failed']}\n\n";

            if (!empty($result['errors'])) {
                $body .= "Errors:\n";
                foreach ($result['errors'] as $err) {
                    $body .= "  - {$err['file']}: {$err['error']}\n";
                }
            }

            $body .= "\nRun at: " . date('Y-m-d H:i:s') . "\n";

            Yii::$app->mailer
                ->compose()
                ->setTo($this->module->summaryEmail)
                ->setFrom(['noreply@example.com' => 'Indicator Import'])
                ->setSubject("[Indicator] Import Summary - " . date('Y-m-d H:i'))
                ->setTextBody($body)
                ->send();
        } catch (\Throwable $e) {
            Yii::error('Failed to send Indicator summary email: ' . $e->getMessage(), 'indicator');
        }
    }
}
