<?php
/**
 * Copyright © Simplyin. All rights reserved.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Service;

use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Simplyin\Payment\Helper\ApiClient;
use Simplyin\Payment\Model\OrderSyncFactory;
use Simplyin\Payment\Model\ResourceModel\OrderSync as OrderSyncResource;
use Simplyin\Payment\Model\ResourceModel\OrderSync\CollectionFactory as OrderSyncCollectionFactory;
use Psr\Log\LoggerInterface;

/**
 * Service for syncing orders to SimplyIN
 */
class OrderSyncService
{
    private const CONFIG_PATH_TRANSACTION_HISTORY_DAYS = 'payment/simplyin_payment/transaction_history_length_days';
    private const CONFIG_PATH_FROM_DATE_ORDER_SYNC = 'payment/simplyin_payment/from_date_order_sync';
    private const CONFIG_PATH_BATCH_SIZE = 'payment/simplyin_payment/sync_batch_size';
    private const DEFAULT_BATCH_SIZE = 50;
    private const DEFAULT_HISTORY_DAYS = 180;

    private const SYNC_STATUS_PENDING = 'pending';
    private const SYNC_STATUS_SYNCED = 'synced';
    private const SYNC_STATUS_FAILED = 'failed';
    private const SYNC_STATUS_SKIPPED = 'skipped';

    /**
     * @var OrderCollectionFactory
     */
    private $orderCollectionFactory;

    /**
     * @var ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @var ApiClient
     */
    private $apiClient;

    /**
     * @var OrderSyncFactory
     */
    private $orderSyncFactory;

    /**
     * @var OrderSyncResource
     */
    private $orderSyncResource;

    /**
     * @var OrderSyncCollectionFactory
     */
    private $orderSyncCollectionFactory;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var OrderPayloadBuilder
     */
    private $orderPayloadBuilder;

    /**
     * @param OrderCollectionFactory $orderCollectionFactory
     * @param ScopeConfigInterface $scopeConfig
     * @param ApiClient $apiClient
     * @param OrderSyncFactory $orderSyncFactory
     * @param OrderSyncResource $orderSyncResource
     * @param OrderSyncCollectionFactory $orderSyncCollectionFactory
     * @param LoggerInterface $logger
     * @param OrderPayloadBuilder $orderPayloadBuilder
     */
    public function __construct(
        OrderCollectionFactory $orderCollectionFactory,
        ScopeConfigInterface $scopeConfig,
        ApiClient $apiClient,
        OrderSyncFactory $orderSyncFactory,
        OrderSyncResource $orderSyncResource,
        OrderSyncCollectionFactory $orderSyncCollectionFactory,
        LoggerInterface $logger,
        OrderPayloadBuilder $orderPayloadBuilder
    ) {
        $this->orderCollectionFactory = $orderCollectionFactory;
        $this->scopeConfig = $scopeConfig;
        $this->apiClient = $apiClient;
        $this->orderSyncFactory = $orderSyncFactory;
        $this->orderSyncResource = $orderSyncResource;
        $this->orderSyncCollectionFactory = $orderSyncCollectionFactory;
        $this->logger = $logger;
        $this->orderPayloadBuilder = $orderPayloadBuilder;
    }

    /**
     * Sync orders to SimplyIN
     *
     * @return array
     */
    public function syncOrders(): array
    {
        $result = [
            'processed' => 0,
            'synced' => 0,
            'skipped' => 0,
            'failed' => 0
        ];

        $batchSize = $this->getBatchSize();

        // Get date range - use fixed from_date_order_sync from config
        $toDate = date('Y-m-d H:i:s');
        $fromDate = $this->getFromDateOrderSync();

        $this->logger->info('SimplyIN: Syncing orders', [
            'fromDate' => $fromDate,
            'toDate' => $toDate,
            'batchSize' => $batchSize
        ]);

        // Get orders not yet synced
        $orders = $this->getOrdersToSync($fromDate, $toDate, $batchSize);

        if ($orders->getSize() == 0) {
            $this->logger->info('SimplyIN: No orders to sync');
            return $result;
        }

        // Collect unique emails and their orders
        $emailOrdersMap = [];
        foreach ($orders as $order) {
            $email = $order->getCustomerEmail();
            if (!isset($emailOrdersMap[$email])) {
                $emailOrdersMap[$email] = [];
            }
            $emailOrdersMap[$email][] = $order;
        }

        // Verify which emails exist in SimplyIN (batch check)
        $emails = array_keys($emailOrdersMap);
        $existingEmails = $this->verifyEmailsExist($emails);

        // Process orders for existing SimplyIN users
        foreach ($emailOrdersMap as $email => $customerOrders) {
            $emailHash = hash('sha256', $email);
            $emailExists = in_array($emailHash, $existingEmails);

            foreach ($customerOrders as $order) {
                $result['processed']++;
                $orderId = (int)$order->getId();

                if (!$emailExists) {
                    // Mark as skipped - user doesn't exist in SimplyIN
                    $this->markOrderSync($orderId, self::SYNC_STATUS_SKIPPED);
                    $result['skipped']++;
                    continue;
                }

                // Sync order to SimplyIN
                $syncResult = $this->syncOrderToSimplyIn($order);

                if ($syncResult['success']) {
                    $this->markOrderSync(
                        $orderId,
                        self::SYNC_STATUS_SYNCED,
                        $syncResult['simplyin_order_id'] ?? null
                    );
                    $result['synced']++;
                } else {
                    $this->markOrderSync($orderId, self::SYNC_STATUS_FAILED);
                    $result['failed']++;
                }
            }
        }

        return $result;
    }


    /**
     * Get from_date_order_sync from config
     *
     * This is the fixed date calculated at installation time (today - transactionHistoryLengthDays)
     *
     * @return string
     */
    private function getFromDateOrderSync(): string
    {
        $fromDate = $this->scopeConfig->getValue(
            self::CONFIG_PATH_FROM_DATE_ORDER_SYNC,
            ScopeInterface::SCOPE_STORE
        );

        // If from_date_order_sync is not set, fall back to calculating from transactionHistoryLengthDays
        if (empty($fromDate)) {
            $historyDays = $this->getTransactionHistoryDays();
            $fromDate = date('Y-m-d', strtotime("-{$historyDays} days"));
            $this->logger->warning('SimplyIN: from_date_order_sync not set, using calculated date', [
                'fromDate' => $fromDate,
                'historyDays' => $historyDays
            ]);
        }

        return $fromDate . ' 00:00:00';
    }

    /**
     * Get transaction history days from config
     *
     * @return int
     */
    private function getTransactionHistoryDays(): int
    {
        $days = (int) $this->scopeConfig->getValue(
            self::CONFIG_PATH_TRANSACTION_HISTORY_DAYS,
            ScopeInterface::SCOPE_STORE
        );

        return $days > 0 ? $days : self::DEFAULT_HISTORY_DAYS;
    }

    /**
     * Get batch size from config
     *
     * @return int
     */
    private function getBatchSize(): int
    {
        $batchSize = (int) $this->scopeConfig->getValue(
            self::CONFIG_PATH_BATCH_SIZE,
            ScopeInterface::SCOPE_STORE
        );

        return $batchSize > 0 ? $batchSize : self::DEFAULT_BATCH_SIZE;
    }

    /**
     * Get orders to sync — uses a LEFT JOIN to exclude already-synced orders
     *
     * Avoids loading all synced IDs into PHP memory.
     *
     * @param string $fromDate
     * @param string $toDate
     * @param int $batchSize
     * @return \Magento\Sales\Model\ResourceModel\Order\Collection
     */
    private function getOrdersToSync(string $fromDate, string $toDate, int $batchSize)
    {
        $collection = $this->orderCollectionFactory->create();

        // Use a subquery/join to exclude already-synced orders without loading all IDs
        $syncTable = $collection->getResource()->getTable('simplyin_order_sync');
        $collection->getSelect()->joinLeft(
            ['sos' => $syncTable],
            'main_table.entity_id = sos.order_id AND sos.sync_status IN ("' . self::SYNC_STATUS_SYNCED . '","' . self::SYNC_STATUS_SKIPPED . '")',
            []
        )->where('sos.order_id IS NULL');

        // Qualify created_at with main_table: the joined simplyin_order_sync (sos) table also has a
        // created_at column, so an unqualified filter/order is ambiguous (breaks the COUNT query).
        $collection->addFieldToFilter('main_table.created_at', ['gteq' => $fromDate])
            ->addFieldToFilter('main_table.created_at', ['lteq' => $toDate])
            ->addFieldToFilter('main_table.customer_email', ['notnull' => true]);

        $collection->setPageSize($batchSize)
            ->setCurPage(1)
            ->setOrder('main_table.created_at', 'ASC');

        return $collection;
    }

    /**
     * Verify which emails exist in SimplyIN
     *
     * @param array $emails
     * @return array Array of SHA-256 hashed emails that exist
     */
    private function verifyEmailsExist(array $emails): array
    {
        if (empty($emails)) {
            return [];
        }

        // Hash emails with SHA-256
        $hashedEmails = [];
        foreach ($emails as $email) {
            $hashedEmails[] = hash('sha256', $email);
        }

        $response = $this->apiClient->verifyHashedEmailsExist($hashedEmails);

        if (!$response['success'] || empty($response['data'])) {
            return [];
        }

        // Extract existing email hashes from response
        $existingHashes = [];
        if (isset($response['data']['data']) && is_array($response['data']['data'])) {
            foreach ($response['data']['data'] as $item) {
                if (isset($item['email'])) {
                    $existingHashes[] = $item['email'];
                }
            }
        }

        return $existingHashes;
    }

    /**
     * Sync single order to SimplyIN
     *
     * @param \Magento\Sales\Model\Order $order
     * @return array
     */
    private function syncOrderToSimplyIn($order): array
    {
        try {
            $orderData = $this->orderPayloadBuilder->buildOrderData($order);
            $response = $this->apiClient->createOrder($orderData, '');

            if ($response['success']) {
                $simplyinOrderId = $response['data']['createdOrder']['_id'] ?? null;
                return [
                    'success' => true,
                    'simplyin_order_id' => $simplyinOrderId
                ];
            }

            // Check if order already exists
            if (isset($response['error']) && strpos($response['error'], 'Order already exists') !== false) {
                return [
                    'success' => true,
                    'simplyin_order_id' => null
                ];
            }

            $this->logger->warning('SimplyIN: Failed to sync order', [
                'order_id' => $order->getId(),
                'error' => $response['error'] ?? 'Unknown error'
            ]);

            return ['success' => false];
        } catch (\Exception $e) {
            $this->logger->error('SimplyIN: Exception syncing order', [
                'order_id' => $order->getId(),
                'error' => $e->getMessage()
            ]);
            return ['success' => false];
        }
    }


    /**
     * Mark order sync status
     *
     * @param int $orderId
     * @param string $status
     * @param string|null $simplyinOrderId
     * @return void
     */
    private function markOrderSync(int $orderId, string $status, ?string $simplyinOrderId = null): void
    {
        try {
            $collection = $this->orderSyncCollectionFactory->create();
            $collection->addFieldToFilter('order_id', $orderId);
            $existingRecord = $collection->getFirstItem();

            if ($existingRecord->getId()) {
                $orderSync = $existingRecord;
            } else {
                $orderSync = $this->orderSyncFactory->create();
                $orderSync->setOrderId($orderId);
            }

            $orderSync->setSyncStatus($status);
            $orderSync->setLastSyncAt((new \DateTime())->format('Y-m-d H:i:s'));

            if ($simplyinOrderId) {
                $orderSync->setSimplyinOrderId($simplyinOrderId);
            }

            $this->orderSyncResource->save($orderSync);
        } catch (\Exception $e) {
            $this->logger->error('SimplyIN: Failed to mark order sync status', [
                'order_id' => $orderId,
                'error' => $e->getMessage()
            ]);
        }
    }
}
