<?php

namespace PortalPeople\magento\models;

use Yii;
use backend\models\{ActionLog, Cartsbe};
use PortalPeople\magento\components\FieldMapper;
use PortalPeople\magento\Module;
use PortalPeople\magento\services\MagentoClient;
use common\components\cart\CartLineRecalculationTrait;
use common\components\cart\DeliveryLineSyncService;
use yii\base\Model;
use yii\helpers\Json;

class Cart extends Cartsbe
{
    use CartLineRecalculationTrait;
    private const DELIVERY_PRODUCT_ID = '331D4D9B-7F11-9473-1279-CAFDA629360A';
    public $csvFile;
    public function rules() : array
    {
        $rules = parent::rules();
        $rules[] = [['account_code'], 'default', 'value' => 'GAR001'];
        return $rules;
    }

    public static function newMagentoOrder(array $rawOrder): array
    {
        $module = Yii::$app->getModule('magento');
        if (!$module instanceof Module || !$module->isConfigured()) {
            throw new \RuntimeException('Magento module is not configured.');
        }

        $normalized = null;

        try {
            Yii::warning($rawOrder, 'magento');
            $normalized = FieldMapper::normalizeOrder($rawOrder);
            return self::upsertFromMagento($normalized, $module);
        } catch (\Throwable $e) {
            self::logImportFailure('newMagentoOrder', $normalized ?? $rawOrder, null, $e);
            throw $e;
        }
    }

    public static function upsertFromMagento(array $order, Module $module): array
    {
        $orderNo = (string) ($order['orderNo'] ?? '');
        if ($orderNo === '') {
            throw new \InvalidArgumentException('Magento order payload is missing an order number.');
        }

        $cart = null;

        try {
            return Yii::$app->db->transaction(function () use ($order, $orderNo, $module, &$cart) {
                $cart = self::findExistingCart($orderNo);
                if ($cart === null) {
                    $cart = new self();
                    $cart->id = $cart::tempId();
                    $cart->status = 'Paid';
                }

                self::applyMagentoOrderData($cart, $order, $orderNo);

                if (!$cart->save()) {
                    self::logImportFailure('cart-save', $order, $cart, null);
                    throw new \RuntimeException('Failed to save Magento cart: ' . json_encode($cart->errors));
                }
                $mage = new \PortalPeople\magento\models\carts\Magento();
                
                $mage->orderId = $order['orderId'] ?? null;
                $mage->orderNo = $orderNo;
                $mage->status = $order['status'] ?? null;
                $mage->paymentMethod = $cart->payment?->paymentmethod ?? null;
                $mage->raw = $order;
                $cart->data->magento = $mage;

                self::upsertMagentoLines($cart, $order['orderLines'] ?? []);

                if (!$cart->save()) {
                    throw new \RuntimeException('Failed to refresh Magento cart after line import.');
                }
                $pdata = $order['_raw']['extension_attributes']['payment_additional_info'] ?? [];
                $paymentData =[];
                foreach ($pdata as $info) {
                    $paymentData[$info['key']] = $info['value'];
                }
                Yii::warning($paymentData, 'magento payment');
                Yii::warning($order['_raw']['payment']['method'], 'magento payment');
                $method = '';
                $paymentRef = '';
                $paymentStatus = '';
                if (array_key_exists('method', $order['_raw']['payment'])) {
                    $method = $order['_raw']['payment']['method'];
                }
                $pay = new \backend\models\Paymentresponsesbe();
                $pay->basketid = $cart->id;
                $pay->source = 'www.gardoo.co.uk';
                $pay->amount = $order['_raw']['grand_total'];
                if (in_array($method, ['paypal_express', 'hosted_pro'])) {
                    if (array_key_exists('paypal_pending_reason', $paymentData)) {
                        $paymentStatus = $paymentData['paypal_pending_reason'];
                    }
                    if (array_key_exists('paypal_correlation_id', $paymentData)) {
                        $paymentRef = $paymentData['paypal_correlation_id'];
                    }
                    $pay->paymentmethod = 'PayPal';
                    $cart->paymentmethod = 'PayPal';
                    $paymentStatus = $paymentStatus == 'authorization' ? 'Paid' : 'Pending';
                } elseif (in_array($method, ['stripe_payments', 'stripe_payments_express'])) {
                    if (array_key_exists('is_transaction_pending', $paymentData)) {
                        $paymentStatus = $paymentData['is_transaction_pending'] == false ? 'Paid' : 'Pending';
                        if (array_key_exists('server_side_transaction_id', $paymentData)) {
                            $paymentRef = $paymentData['server_side_transaction_id'];
                        }
                    }
                    if (array_key_exists('payment_intent_id', $paymentData)) {
                        $paymentRef = $paymentData['payment_intent_id'];
                    }
                    $pay->paymentmethod = 'Stripe';
                    $cart->paymentmethod = 'Stripe';
                } 
                $pay->status = $paymentStatus;
                $pay->paymentref = $paymentRef;
                $pay->save();
                if (isset($pay) && $pay->status == 'Paid') {
                    $cart->billingstatus = 'Paid';
                }
                $cart->save();

                self::pushNgaugeOrderFieldsToMagento($module, $cart);
                return [
                    'created' => $cart->getIsNewRecord(),
                    'orderNo' => $orderNo,
                    'cartId' => $cart->id,
                ];
            });
        } catch (\Throwable $e) {
            self::logImportFailure('upsertFromMagento', $order, $cart, $e);
            throw $e;
        }
    }

    public static function updateMagentoPayment(array $payment): array
    {
        $orderNo = (string) ($payment['orderNo'] ?? '');
        if ($orderNo === '') {
            throw new \InvalidArgumentException('Magento payment snapshot is missing an order number.');
        }
        return Yii::$app->db->transaction(function () use ($payment, $orderNo) {
            $cart = self::findExistingCart($orderNo);
            if ($cart === null) {
                return [
                    'updated' => false,
                    'reason' => 'cart-not-found',
                    'orderNo' => $orderNo,
                ];
            }

            $data = is_array($cart->data) ? $cart->data : [];
            $magento = $data['magento'] ?? [];
            $magento['order_no'] = $magento['order_no'] ?? $orderNo;
            $magento['payment_method'] = (string) ($payment['method'] ?? ($magento['payment_method'] ?? ''));
            $magento['payment'] = [
                'order_id' => $payment['orderId'] ?? null,
                'order_no' => $orderNo,
                'status' => $payment['status'] ?? null,
                'created_at' => $payment['createdAt'] ?? null,
                'method' => $payment['method'] ?? null,
                'amount' => $payment['amount'] ?? null,
                'currency' => $payment['currency'] ?? null,
                'customer_email' => $payment['customerEmail'] ?? null,
                'raw' => $payment['raw'] ?? null,
            ];
            $data['magento'] = $magento;
            $cart->data = Json::encode($data, JSON_UNESCAPED_SLASHES);

            if (!$cart->save()) {
                throw new \RuntimeException('Failed to save Magento payment data: ' . json_encode($cart->errors));
            }

            return [
                'updated' => true,
                'orderNo' => $orderNo,
                'cartId' => $cart->id,
            ];
        });
    }

    public static function pushNgaugeOrderFieldsToMagento(Module $module, $cart): void
    {
        try {
            $magentoData = null;
            if (is_object($cart->data) && isset($cart->data->magento)) {
                $magentoData = $cart->data->magento;
            } elseif (is_array($cart->data) && isset($cart->data['magento'])) {
                $magentoData = $cart->data['magento'];
            } elseif (is_string($cart->data) && $cart->data !== '') {
                $decodedData = Json::decode($cart->data, true);
                if (is_array($decodedData) && isset($decodedData['magento'])) {
                    $magentoData = $decodedData['magento'];
                }
            }

            $magentoOrderId = (int) (
                is_object($magentoData)
                    ? ($magentoData->orderId ?? $magentoData->orderid ?? 0)
                    : (is_array($magentoData) ? ($magentoData['orderId'] ?? $magentoData['orderid'] ?? 0) : 0)
            );
            $ngaugeOrderNo = (string) $cart->orderref;

            if ($magentoOrderId <= 0 || $ngaugeOrderNo === '') {
                return;
            }

            $client = new MagentoClient((string) $module->apiUrl, (string) $module->accessToken);
            $link = Yii::$app->params['urls']['ngauge']['full'] . '/magento/orders?id=' . $cart->id;
            $client->updateOrderNgaugeFields($magentoOrderId, (string) $cart->id, $ngaugeOrderNo, $link);
            $client->addOrderComment($magentoOrderId, 'NGauge order number: ' . $ngaugeOrderNo . ' (Cart ID: ' . $cart->id . ')', 'processing');

        } catch (\Throwable $e) {
            Yii::warning('Failed to push NGauge order fields back to Magento for cart ' . $cart->id . ': ' . $e->getMessage(), 'magento');
        }
    }

    public static function findExistingCart(string $orderNo)
    {
        $json = new \yii\db\Expression("json_value(`data`, '$.magento.order_no')");
        return self::find()->where(['=', $json, $orderNo])->one();
    }

    public static function findImportedMagentoOrderNumbers(string $accountCode = 'GAR001'): array
    {
        $query = self::find()
            ->select([
                'magento_order_no' => new \yii\db\Expression("json_value(`data`, '$.magento.order_no')"),
                'cart_order_no' => 'orderref',
                'cart_id' => 'id',
            ])
            ->where(['account_code' => $accountCode])
            ->asArray();

        return array_values(array_filter($query->all(), static function ($row): bool {
            return (string) ($row['magento_order_no'] ?? '') !== '';
        }));
    }

    private static function applyMagentoOrderData($cart, array $order, string $orderNo): void
    {
        
        $cart->ordernumber = $cart->ordernumber ?: $orderNo;
        $cart->porderref = $cart->porderref ?: $orderNo;
        $rawOrder = is_array($order['_raw'] ?? null) ? $order['_raw'] : [];

        if (($order['customerEmail'] ?? '') !== '') {
            $cart->email = $cart->email ?: (string) $order['customerEmail'];
        }

        if (($order['customerName'] ?? '') !== '') {
            $cart->contact = $cart->contact ?: (string) $order['customerName'];
        }

        if (($order['telephone'] ?? '') !== '') {
            $cart->telephone = $cart->telephone ?: (string) $order['telephone'];
        }

        $cart->sourceurl = $cart->sourceurl ?: 'magento';

        if (empty($cart->dateordered)) {
            $cart->dateordered = strtotime((string) ($order['orderDate'] ?? 'now'));
        }

        //$cart->paymentmethod = $cart->paymentmethod ?: (string) ($order['paymentMethod'] ?? ($rawOrder['payment']['method'] ?? ''));
        $cart->shippingmethod = $cart->shippingmethod ?: (string) ($rawOrder['shipping_description'] ?? ($rawOrder['shipping_method'] ?? ''));
        $cart->billingstatus = $cart->billingstatus ?: (((string) ($order['status'] ?? '')) === 'complete' ? 'Paid' : 'Pending');
        $cart->shippingstatus = $cart->shippingstatus ?: (string) ($order['status'] ?? '');
        $cart->delivery_message = $cart->delivery_message ?: (string) ($rawOrder['extension_attributes']['shipping_message'] ?? '');
        $cart->delivery_date = (string) ($rawOrder['extension_attributes']['shipping_date'] ?? '');

        $fulladdresses = $cart->fulladdresses ?? [];
        //$model->telephone = $data['customer']['telephone'];
        $billingAddress = New \backend\models\Address();
        $billingAddress->address1 = $order['billingAddress']['address1'] ?: $order['billing_address']['street'] ?? '';
        $billingAddress->town = $order['billingAddress']['city'] ?: $order['billing_address']['city'] ?? '';
        $billingAddress->postcode = $order['billingAddress']['postcode'] ?: $order['billing_address']['postcode'] ?? '';
        $billingAddress->telephone = $order['billingAddress']['telephone'] ?: $order['billing_address']['telephone'] ?? '';
        
        $shippingAddress = New \backend\models\Address();
        $shippingAddress->address1 = $order['deliveryAddress']['address1'] ?: $order['shipping_address']['street'] ?? '';
        $shippingAddress->town = $order['deliveryAddress']['city'] ?: $order['shipping_address']['city'] ?? '';
        $shippingAddress->postcode = $order['deliveryAddress']['postcode'] ?: $order['shipping_address']['postcode'] ?? '';
        $shippingAddress->telephone = $order['deliveryAddress']['telephone'] ?: $order['shipping_address']['telephone'] ?? '';
        $fulladdresses['billing'] = $billingAddress;
        $fulladdresses['shipping'] = $shippingAddress;
        $cart->fulladdresses = $fulladdresses;

        if (isset($order)) {
            $cart->imported_data = $order;
        }
    }

    private static function mergeMagentoAddresses($cart, array $order): array
    {
        return [
            'billing' => $order['billingAddress'] ?? [],
            'shipping' => $order['deliveryAddress'] ?? [],
        ];
    }

    private static function setMagentoMetadata($cart, array $metadata): void
    {
        $existing = self::decodeExistingData($cart);
        $magento = $existing['magento'] ?? [];

        if (!is_array($magento)) {
            $magento = [];
        }

        $existing['magento'] = array_merge($magento, $metadata);
        $cart->data = Json::encode($existing, JSON_UNESCAPED_SLASHES);
    }

    private static function decodeExistingData($cart): array
    {
        $rawData = $cart->isNewRecord ? null : $cart->getOldAttribute('data');

        if (!is_string($rawData) || $rawData === '') {
            if (is_string($cart->data)) {
                $rawData = $cart->data;
            }
        }

        if (!is_string($rawData) || $rawData === '') {
            return [];
        }

        $decoded = Json::decode($rawData, true);

        return is_array($decoded) ? $decoded : [];
    }

    private static function upsertMagentoLines($cart, array $lines): void
    {
        $lineClass = '\backend\models\Cartlines';
        $seen = [];

        foreach ($lines as $index => $line) {
            if (!is_array($line)) {
                continue;
            }

            $sequence = (int) ($line['lineNo'] ?? $index + 1);
            $seen[] = $sequence;
            $model = $lineClass::find()->where(['basketid' => $cart->id, 'sequence' => $sequence])->one();

            if ($model === null) {
                $model = new $lineClass(['basketid' => $cart->id, 'sequence' => $sequence]);
            }

            $model->qty = (int) ($line['quantity'] ?? $line['qty'] ?? 0);
            $model->price = (float) ($line['unitPrice'] ?? $line['price'] ?? 0);
            $model->stock_code = (string) ($line['sku'] ?? '');
            $model->product_name = (string) ($line['productName'] ?? $line['name'] ?? $line['productDescription'] ?? '');
            $model->product_description = (string) ($line['description'] ?? $line['productDescription'] ?? $line['name'] ?? '');
            $model->data = Json::encode($line['data'] ?? $line, JSON_UNESCAPED_SLASHES);
            $model->status = 'Active';
            

            if (!$model->save()) {
                self::logImportFailure('cart-line-save', [
                    'cartId' => $cart->id,
                    'sequence' => $sequence,
                    'line' => $line,
                ], $model, null);
                throw new \RuntimeException('Failed to save Magento cart line: ' . json_encode($model->errors));
            }
        }

        if ($seen === []) {
            $lineClass::updateAll(['status' => 'Deleted'], ['basketid' => $cart->id]);
            return;
        }

        $lineClass::updateAll(['status' => 'Deleted'], ['and', ['basketid' => $cart->id], ['not in', 'sequence', $seen]]);
    }

    private static function logImportFailure(string $stage, array $order, $model = null, ?\Throwable $exception = null): void
    {
        $context = [
            'stage' => $stage,
            'orderNo' => (string) ($order['orderNo'] ?? $order['order_no'] ?? ''),
            'orderId' => $order['orderId'] ?? $order['order_id'] ?? null,
            'modelClass' => $model instanceof Model ? get_class($model) : null,
            'arrayAttributes' => $model instanceof Model ? self::describeArrayAttributes($model) : [],
            'modelAttributes' => $model instanceof Model ? self::summarizeAttributes($model->getAttributes()) : [],
            'orderKeys' => self::summarizeAttributes($order),
        ];

        if ($exception !== null) {
            $context['exception'] = [
                'class' => get_class($exception),
                'message' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
            ];
        }
    }

    private static function describeArrayAttributes(Model $model): array
    {
        $arrayAttributes = [];

        foreach ($model->getAttributes() as $name => $value) {
            if (is_array($value)) {
                $arrayAttributes[$name] = 'array';
            } elseif (is_object($value)) {
                $arrayAttributes[$name] = get_class($value);
            }
        }

        return $arrayAttributes;
    }

    private static function summarizeAttributes(array $attributes): array
    {
        $summary = [];

        foreach ($attributes as $name => $value) {
            if (is_array($value)) {
                $summary[$name] = [
                    'type' => 'array',
                    'keys' => array_slice(array_keys($value), 0, 10),
                ];
            } elseif (is_object($value)) {
                $summary[$name] = [
                    'type' => 'object',
                    'class' => get_class($value),
                ];
            } else {
                $summary[$name] = [
                    'type' => gettype($value),
                    'value' => $value,
                ];
            }
        }

        return $summary;
    }

    public function CreateDeliveryLines() {
        ActionLog::create(['class' => self::class, 'action' => 'import', 'type' => 'info', 'message' => 'Create Delivery Lines', 'jobid' => $this->jobId, 'relatedid' => $this->id]);
        try{
            $shippingcosts = \PortalPeople\shippingcosts\services\ShippingCostCalculatorService::calculateCartCosts($this, 'detailed');        
            $linesCreated = 0;
            foreach ($shippingcosts as $haulier => $data) {
                if (Yii::$app instanceof \yii\console\Application) {
                    \yii\helpers\Console::output('Cart Create Delivery Lines');
                }
                foreach ($data['group_costs'] as $group) {
                    $size = match((float)$group['pallet_size']) {
                        1.0 => 'Pallet',
                        0.5 => 'Half Pallet',
                        0.25 => 'Quarter Pallet',
                        default => 'Parcel'
                    };
                    $description = $data['haulier'] . ' - ' . $group['shipment_type'] . ' - ' . $size;
                    $cartline = \backend\models\Cartlines::find()
                        ->where([
                            'basketid' => $this->id, 
                            'haulierint' => $haulier, 
                            'productid' => '331D4D9B-7F11-9473-1279-CAFDA629360A',
                            'product_description' => $description,
                        ])
                        ->one();
                        $new = false;
                    if (!$cartline) {
                        $cartline = new \backend\models\Cartlines(['id' => \backend\models\Cartlines::Tempid(), 'basketid' => $this->id, 'deliveryline' => 0, 'paid' => true]);
                        $cartline->productid = '331D4D9B-7F11-9473-1279-CAFDA629360A';
                        $new = true;
                    }
                    $cartline->qty = $group['qty'];
                    $cartline->cost = $group['line_subtotal'] / $group['qty'];
                    $cartline->haulierint = $haulier;
                    $cartline->hidden_info = $data['haulier'] .' ' . $group['shipment_type'] . ' Delivery ' . Inflector::pluralize($group['qty'] . "{$size} Pallets");
                    $cartline->product_description = $description;
                    $cartline->erpqty = 1;
                    $cartline->requested_date = $this->delivery_date;
                    if ($cartline->save()) {
                        $new ? $linesCreated++ : false;
                    } 
                }
            }
            ActionLog::create(['class' => self::class, 'action' => 'import', 'type' => 'info', 'message' => "Created {$linesCreated} delivery lines", 'jobid' => $this->jobId, 'relatedid' => $this->id]);
        } catch (\ErrorException $e) {
            ActionLog::create(['class' => self::class, 'action' => 'import', 'type' => 'Error', 'message' => 'Failed to create delivery lines: ' . $e->getMessage(), 'jobid' => $this->jobId, 'relatedid' => $this->id]);
        } 
    }
    
    public function afterSave($insert, $changedAttributes)
    {
        $this->CreateDeliveryLines();
        $this->refresh();
        $total = array_sum(array_column($this->cartlines, 'linetotal'));
        $shipment = $this->ensureShipmentRecord();
       // $payment = $this->ensurePaymentRecord($total);
        $payment = $this->payment;
        $payment?->updateAttributes(['status' => 'Paid', 'camount' => $total, 'amount' => $total]);
        return parent::afterSave($insert, $changedAttributes);
    }

    public function beforeSave($insert)
    {
        $addresses = $this->fulladdresses;
        if (!is_array($addresses)) {
            $addresses = is_string($addresses) && $addresses !== '' ? Json::decode($addresses, true) : [];
        }

        if (!is_array($addresses)) {
            $addresses = [];
        }

        $this->fulladdresses = $addresses;
        $this->imported_data = json_encode($this->imported_data, JSON_UNESCAPED_SLASHES);
        parent::beforeSave($insert);

        return true;
    }

    private function ensureShipmentRecord(): \backend\models\Shipment
    {
        if ($this->shipment instanceof \backend\models\Shipment) {
            return $this->shipment;
        }

        $shipment = \backend\models\Shipment::find()
            ->where(['cart_id' => $this->id, 'shipment_type' => 0])
            ->one();

        if ($shipment instanceof \backend\models\Shipment) {
            return $shipment;
        }

        $shipment = new \backend\models\Shipment();
        $shipment->shipment_type = 0;
        $shipment->cart_id = $this->id;
        $shipment->delivery = $this->shippingmethod ?: 'Eco';
        $shipment->save(false);

        return $shipment;
    }

    private function ensurePaymentRecord(float $total): \backend\models\Paymentresponsesbe
    {
        if ($this->payment instanceof \backend\models\Paymentresponsesbe) {
            return $this->payment;
        }

        $payment = \backend\models\Paymentresponsesbe::find()
            ->where(['basketid' => $this->id])
            ->one();

        if ($payment instanceof \backend\models\Paymentresponsesbe) {
            return $payment;
        }

        $payment = new \backend\models\Paymentresponsesbe();
        $payment->basketid = $this->id;
        $payment->source = 'Magento';
        $payment->paymentmethod = $this->paymentmethod ?: null;
        $payment->camount = $total;
        $payment->amount = $total;
        $payment->status = $this->billingstatus ?: 'Paid';
        if ($payment->hasAttribute('language')) {
            $payment->language = 'en';
        }
        $payment->save(false);

        return $payment;
    }
}

/**
 * Get the date after a specified number of working days.
 *
 * @param int $days Number of working days to add.
 * @param string $format Date format to return.
 * @return string Formatted date string.
 */
function getWorkingDaysAhead($days, $format)
{
    $currentDate = new \DateTime();
    $workingDays = 0;
    while ($workingDays < $days) {
        $currentDate->add(new \DateInterval('P1D'));
        if ($currentDate->format('N') < 6) {
            $workingDays++;
        }
    }
    return $currentDate->format($format);
}
