<?php
/**
 * Caterpoint Order Import Service
 *
 * @category    PortalPeople
 * @package     Caterpoint
 * @author      Portal People Development Team
 * @copyright   Copyright (c) 2026 Portal People (https://portalpeople.co.uk)
 */

namespace PortalPeople\caterpoint\services;

use Yii;
use yii\base\BaseObject;
use yii\helpers\Json;
use PortalPeople\caterpoint\Module;
use PortalPeople\caterpoint\components\CaterpointClient;

/**
 * Order Import Service
 * 
 * Fetches orders from Caterpoint API and imports them into the local system
 */
class OrderImportService extends BaseObject
{
    /**
     * @var CaterpointClient API client for Caterpoint
     */
    public CaterpointClient $client;

    /**
     * @var CaterpointCursorStore Cursor storage for tracking import progress
     */
    public CaterpointCursorStore $cursorStore;

    /**
     * @var Module Module reference for configuration and helpers
     */
    public Module $module;

    /**
     * @var int Default number of orders to fetch per request
     */
    public int $defaultLimit = 100;

    /**
     * @var bool Mark orders as exported in Caterpoint after successful import
     */
    public bool $markExportedAfterImport = false;

    /**
     * @var string Model class for orders (configure based on your system)
     */
    public string $orderModelClass = '';

    /**
     * @var string Model class for order lines (configure based on your system)
     */
    public string $orderLineModelClass = '';

    /**
     * Run the order import process
     * 
     * @param string|null $cursor Optional cursor to start from (overrides stored cursor)
     * @param int $limit Optional limit (overrides default)
     * @param bool $dryRun If true, preview changes without persisting them
     * @return array Summary of import results
     */
    public function run(?string $cursor = null, int $limit = 0, bool $dryRun = false): array
    {
        $effectiveLimit = $limit > 0 ? $limit : $this->defaultLimit;
        $effectiveCursor = $cursor !== null && $cursor !== '' ? $cursor : $this->cursorStore->get();

        try {
            $fetched = $this->client->getOrdersUpdatedSince($effectiveCursor, $effectiveLimit);
            $orders = $this->normalizeOrders($fetched['orders'] ?? []);
        } catch (\Throwable $throwable) {
            return [
                'cursor' => $effectiveCursor,
                'nextCursor' => null,
                'fetched' => 0,
                'processed' => 0,
                'created' => 0,
                'updated' => 0,
                'failed' => 1,
                'dryRun' => $dryRun,
                'errors' => [
                    [
                        'message' => 'Failed to fetch orders: ' . $throwable->getMessage(),
                        'code' => $throwable->getCode(),
                    ]
                ],
            ];
        }

        $summary = [
            'cursor' => $effectiveCursor,
            'nextCursor' => $fetched['nextCursor'] ?? null,
            'fetched' => count($orders),
            'processed' => 0,
            'created' => 0,
            'updated' => 0,
            'markedExported' => 0,
            'exportFailed' => 0,
            'failed' => 0,
            'dryRun' => $dryRun,
            'errors' => [],
        ];

        foreach ($orders as $order) {
            try {
                if ($dryRun) {
                    $preview = $this->previewOrder($order);
                    if ($preview['created'] === true) {
                        $summary['created']++;
                    } else {
                        $summary['updated']++;
                    }
                    $summary['processed']++;
                    continue;
                }

                $result = $this->importOrder($order);

                if ($result['created'] === true) {
                    $summary['created']++;
                } else {
                    $summary['updated']++;
                }

                $summary['processed']++;

                if ($this->markExportedAfterImport && !$dryRun) {
                    $orderId = $this->resolveOrderId($order);
                    if ($orderId !== '') {
                        try {
                            $this->client->markOrderExported($orderId);
                            $summary['markedExported']++;
                        } catch (\Throwable $throwable) {
                            $summary['exportFailed']++;
                            $summary['errors'][] = [
                                'message' => 'Failed to mark order exported: ' . $throwable->getMessage(),
                                'code' => $throwable->getCode(),
                                'order' => $order,
                            ];
                        }
                    }
                }
            } catch (\Throwable $throwable) {
                $summary['failed']++;
                $summary['errors'][] = [
                    'message' => $throwable->getMessage(),
                    'code' => $throwable->getCode(),
                    'order' => $order,
                ];

                Yii::error(
                    'Caterpoint order import failed: ' . $throwable->getMessage() . ' | Order: ' . Json::encode($order),
                    'caterpoint.order-import'
                );
            }
        }

        // Update cursor if not in dry run and we have a next cursor
        if (!$dryRun && is_string($summary['nextCursor']) && $summary['nextCursor'] !== '') {
            $this->cursorStore->set($summary['nextCursor']);
        }

        return $summary;
    }

    /**
     * Preview how an order would be imported without persisting changes
     * 
     * @param array $order Order data from Caterpoint
     * @return array Preview result with 'created' boolean flag
     */
    public function previewOrder(array $order): array
    {
        $orderId = $this->resolveOrderId($order);
        
        // Check if order already exists in local system
        // This is a placeholder - implement based on your system
        $existing = $this->findOrderByExternalId($orderId);

        return [
            'created' => $existing === null,
            'orderId' => $orderId,
        ];
    }

    /**
     * Import a single order into the local system
     * 
     * @param array $order Order data from Caterpoint
     * @return array Import result with 'created' flag
     * @throws \RuntimeException on import failure
     */
    public function importOrder(array $order): array
    {
        $this->assertRuntimeClasses();
        $orderId = $this->resolveOrderId($order);

        // Use transaction for atomic import
        return Yii::$app->db->transaction(function () use ($order, $orderId) {
            [$localOrder, $created] = $this->findOrCreateOrder($orderId, $order);
            
            if ($localOrder === null) {
                throw new \RuntimeException("Failed to create or find order for: {$orderId}");
            }

            $this->mapOrderData($localOrder, $order, $orderId);

            if (!$localOrder->save()) {
                throw new \RuntimeException(
                    'Failed to save order: ' . Json::encode($localOrder->errors)
                );
            }

            // TODO: Import order lines if applicable
            // $this->importOrderLines($localOrder, $order);

            return [
                'created' => $created,
                'orderId' => $orderId,
                'localOrderId' => $localOrder->id ?? null,
            ];
        });
    }

    /**
     * Normalize and validate order data from Caterpoint
     * 
     * @param array $orders Raw orders from API
     * @return array Normalized orders
     */
    private function normalizeOrders(array $orders): array
    {
        return array_map(function ($order) {
            return $this->normalizeOrder($order);
        }, $orders);
    }

    /**
     * Normalize a single order record
     * 
     * @param array $order Raw order from API
     * @return array Normalized order
     */
    private function normalizeOrder(array $order): array
    {
        return [
            'id' => $order['id'] ?? $order['order_id'] ?? null,
            'reference' => $order['reference'] ?? $order['orderRef'] ?? $order['order_reference'] ?? null,
            'customer_reference' => $order['customerReference'] ?? $order['customer_reference'] ?? null,
            'status' => $order['status'] ?? null,
            'date' => $order['date'] ?? $order['orderDate'] ?? $order['created_at'] ?? null,
            'updated_at' => $order['updated_at'] ?? $order['updatedAt'] ?? null,
            'items' => $order['items'] ?? $order['lines'] ?? [],
            'customer' => $order['customer'] ?? [],
            'delivery' => $order['delivery'] ?? [],
            'totals' => $order['totals'] ?? [],
            'metadata' => $order,
        ];
    }

    /**
     * Resolve the primary order identifier from Caterpoint order data
     * 
     * @param array $order Order data
     * @return string Order identifier
     */
    private function resolveOrderId(array $order): string
    {
        return (string) ($order['id'] ?? $order['order_id'] ?? $order['reference'] ?? '');
    }

    /**
     * Find an existing order by external Caterpoint ID
     * 
     * @param string $externalOrderId Caterpoint order ID
     * @return mixed Order model or null if not found
     */
    private function findOrderByExternalId(string $externalOrderId)
    {
        // TODO: Implement based on your order model
        // This is a placeholder
        return null;
    }

    /**
     * Find or create a local order from Caterpoint data
     * 
     * @param string $externalOrderId Caterpoint order ID
     * @param array $order Full order data
     * @return array [$order, $created] tuple with order model and created flag
     */
    private function findOrCreateOrder(string $externalOrderId, array $order): array
    {
        // TODO: Implement based on your order model
        // This is a placeholder that returns null as the implementation depends on your system
        // Example pattern:
        /*
        $localOrder = YourOrderModel::findOne(['external_order_id' => $externalOrderId]);
        $created = false;
        
        if ($localOrder === null) {
            $localOrder = new YourOrderModel();
            $localOrder->external_order_id = $externalOrderId;
            $created = true;
        }
        
        return [$localOrder, $created];
        */
        return [null, false];
    }

    /**
     * Map Caterpoint order data to local order model
     * 
     * @param mixed $localOrder Local order model instance
     * @param array $order Normalized order data from Caterpoint
     * @param string $externalOrderId Caterpoint order ID
     */
    private function mapOrderData($localOrder, array $order, string $externalOrderId): void
    {
        // TODO: Implement mapping based on your order model
        // Example pattern:
        /*
        $localOrder->external_order_id = $externalOrderId;
        $localOrder->reference = $order['reference'] ?? '';
        $localOrder->customer_reference = $order['customer_reference'] ?? '';
        $localOrder->status = $this->module->mapStatus($order['status'] ?? '');
        $localOrder->external_status = $order['status'] ?? '';
        $localOrder->order_date = $this->toDateTime($order['date']);
        $localOrder->updated_at = $this->toDateTime($order['updated_at']);
        $localOrder->customer_data = Json::encode($order['customer'] ?? []);
        $localOrder->delivery_data = Json::encode($order['delivery'] ?? []);
        $localOrder->external_data = Json::encode($order['metadata'] ?? []);
        */
    }

    /**
     * Convert various date formats to DateTime
     * 
     * @param mixed $dateValue Date value in various formats
     * @return int|null Unix timestamp or null
     */
    private function toDateTime($dateValue): ?int
    {
        if ($dateValue === null || $dateValue === '') {
            return null;
        }

        if (is_numeric($dateValue)) {
            return (int) $dateValue;
        }

        if (is_string($dateValue)) {
            $timestamp = strtotime($dateValue);
            return $timestamp !== false ? $timestamp : null;
        }

        return null;
    }

    /**
     * Assert that required model classes are configured
     * 
     * @throws \RuntimeException if classes are not configured
     */
    private function assertRuntimeClasses(): void
    {
        if (empty($this->orderModelClass)) {
            throw new \RuntimeException(
                'OrderImportService: orderModelClass must be configured'
            );
        }
    }
}
