<?php

declare(strict_types=1);

namespace App\Services;

use Carbon\Carbon;
use Exception;
use JsonException;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\TelegramService;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Throwable;

class OrderCreateService
{
    private ConnectionInterface $database;
    private CartService $cartService;
    private OcRegistryDecorator $oc;
    private SettingsService $settings;
    private TelegramService $telegramService;
    private LoggerInterface $logger;
    private TelecartCustomerService $telecartCustomerService;
    private OcCustomerService $ocCustomerService;
    private OrderMetaService $orderMetaService;

    public function __construct(
        ConnectionInterface $database,
        CartService $cartService,
        OcRegistryDecorator $registry,
        SettingsService $settings,
        TelegramService $telegramService,
        LoggerInterface $logger,
        TelecartCustomerService $telegramCustomerService,
        OcCustomerService $ocCustomerService,
        OrderMetaService $orderMetaService
    ) {
        $this->database = $database;
        $this->cartService = $cartService;
        $this->oc = $registry;
        $this->settings = $settings;
        $this->telegramService = $telegramService;
        $this->logger = $logger;
        $this->telecartCustomerService = $telegramCustomerService;
        $this->ocCustomerService = $ocCustomerService;
        $this->orderMetaService = $orderMetaService;
    }

    /**
     * @throws Throwable
     * @throws JsonException
     */
    public function create(array $data, array $meta = []): array
    {
        $now = Carbon::now();
        $storeId = $this->settings->get('store.oc_store_id');
        $storeName = $this->settings->config()->getApp()->getAppName();
        $orderStatusId = $this->settings->config()->getOrders()->getOrderDefaultStatusId();
        $customerGroupId = $this->settings->config()->getOrders()->getOcCustomerGroupId();
        $languageId = $this->settings->config()->getApp()->getLanguageId();
        $currencyId = $this->oc->currency->getId($this->oc->session->data['currency']);
        $currencyCode = $this->oc->session->data['currency'];
        $currencyValue = $this->oc->currency->getValue($this->oc->session->data['currency']);

        $cart = $this->cartService->getCart();
        $total = $cart['total'] ?? 0;
        $products = $cart['products'] ?? [];
        $totals = $cart['totals'] ?? [];

        // Получаем telegram_user_id из tgData
        $telegramUserId = Arr::get($data['tgData'] ?? [], 'user.id');
        $telegramUserdata = Arr::get($data['tgData'] ?? [], 'user');

        if (! $telegramUserId) {
            throw new RuntimeException('Telegram user id is required.');
        }

        $customOrderFields = $this->customOrderFields($data);

        $orderData = [
            'store_id' => $storeId,
            'store_name' => $storeName,
            'firstname' => $data['firstname'] ?? '',
            'lastname' => $data['lastname'] ?? '',
            'email' => $data['email'] ?? '',
            'telephone' => $data['telephone'] ?? '',
            'comment' => $data['comment'] ?? '',
            'payment_method' => $data['payment_method'] ?? '',
            'shipping_address_1' => $data['shipping_address_1'] ?? '',
            'shipping_city' => $data['shipping_city'] ?? '',
            'shipping_zone' => $data['shipping_zone'] ?? '',
            'shipping_postcode' => $data['shipping_postcode'] ?? '',
            'total' => $total,
            'order_status_id' => $orderStatusId,
            'ip' => $meta['ip'] ?? '',
            'forwarded_ip' => $meta['ip'] ?? '',
            'user_agent' => $meta['user_agent'] ?? '',
            'date_added' => $now,
            'date_modified' => $now,
            'language_id' => $languageId,
            'currency_id' => $currencyId,
            'currency_code' => $currencyCode,
            'currency_value' => $currencyValue,
            'customer_group_id' => $customerGroupId,
        ];

        try {
            $this->database->beginTransaction();

            $telecartCustomer = $this->telecartCustomerService->saveOrUpdate($telegramUserdata);
            $telecartCustomerId = (int) $telecartCustomer['id'];
            $ocCustomer = $this->ocCustomerService->findOrCreateByTelecartCustomerId($telecartCustomerId, $orderData);
            $ocCustomerId = (int) $ocCustomer['customer_id'];

            $orderData['customer_id'] = $ocCustomerId;

            $this->database->insert(db_table('order'), $orderData);
            $orderId = $this->database->lastInsertId();

            // Insert products
            $this->insertProducts($products, $orderId);

            // Insert totals
            $this->insertTotals($totals, $orderId);

            // Insert history
            $this->insertHistory($orderId, $orderStatusId, $customOrderFields, $now);

            // Insert order meta data
            if ($customOrderFields) {
                $this->orderMetaService->insert($orderId, $storeId, $customOrderFields, $telecartCustomerId);
            }

            $this->telecartCustomerService->increaseOrdersCount($telecartCustomerId);

            $this->database->commitTransaction();
        } catch (Throwable $exception) {
            $this->database->rollBackTransaction();
            throw $exception;
        }

        $this->cartService->flush();

        $orderData['order_id'] = $orderId;
        $orderData['total_numeric'] = $orderData['total'] ?? 0;
        $orderData['total'] = $cart['total_text'] ?? '';

        $this->sendNotifications($orderData, $data['tgData']);

        $dateTimeFormatted = '';
        try {
            $dateTimeFormatted = $now->format('d.m.Y H:i');
        } catch (Exception $exception) {
            $this->logger->error($exception->getMessage(), ['exception' => $exception]);
        }

        return [
            'id' => $orderData['order_id'],
            'created_at' => $dateTimeFormatted,
            'total' => $orderData['total'],
            'final_total_numeric' => $orderData['total_numeric'],
            'currency' => $currencyCode,
            'products' => $products,
        ];
    }

    private function sendNotifications(array $orderData, array $tgInitData): void
    {
        $variables = [
            '{store_name}' => $orderData['store_name'],
            '{order_id}' => $orderData['order_id'],
            '{customer}' => $orderData['firstname'] . ' ' . $orderData['lastname'],
            '{email}' => $orderData['email'],
            '{phone}' => $orderData['telephone'],
            '{comment}' => $orderData['comment'],
            '{address}' => $orderData['shipping_address_1'],
            '{total}' => $orderData['total'],
            '{ip}' => $orderData['ip'],
            '{created_at}' => $orderData['date_added'],
        ];

        $chatId = $this->settings->config()->getTelegram()->getChatId();
        $template = $this->settings->config()->getTelegram()->getOwnerNotificationTemplate();

        if ($chatId && $template) {
            $message = $this->telegramService->prepareMessage($template, $variables);
            try {
                $this->telegramService->sendMessage($chatId, $message);
            } catch (Throwable $exception) {
                $this->logger->error(
                    'Telegram sendMessage to owner error.',
                    [
                        'exception' => $exception,
                        'chat_id' => $chatId,
                        'message' => $message,
                    ],
                );
            }
        }

        $allowsWriteToPm = Arr::get($tgInitData, 'user.allows_write_to_pm', false);
        $customerChatId = Arr::get($tgInitData, 'user.id');
        $template = $this->settings->config()->getTelegram()->getCustomerNotificationTemplate();

        if ($allowsWriteToPm && $customerChatId && $template) {
            $message = $this->telegramService->prepareMessage($template, $variables);
            try {
                $this->telegramService->sendMessage($customerChatId, $message);
            } catch (Throwable $exception) {
                $this->logger->error(
                    "Telegram sendMessage to customer error.",
                    [
                        'exception' => $exception,
                        'chat_id' => $chatId,
                        'message' => $message,
                    ],
                );
            }
        }
    }

    private function formatHistoryComment(array $customFields): string
    {
        $additionalString = '';
        if ($customFields) {
            $additionalString = "\n\nДополнительная информация по заказу:\n";
            foreach ($customFields as $field => $value) {
                $additionalString .= $field . ': ' . $value . "\n";
            }
        }

        return "Заказ оформлен через Telegram Mini App.$additionalString";
    }

    private function customOrderFields(array $data): array
    {
        return Arr::except($data, [
            'firstname',
            'lastname',
            'email',
            'telephone',
            'comment',
            'shipping_address_1',
            'shipping_city',
            'shipping_zone',
            'shipping_postcode',
            'payment_method',
            'tgData',
        ]);
    }

    public function insertTotals(array $totals, int $orderId): void
    {
        foreach ($totals as $total) {
            $this->database->insert(db_table('order_total'), [
                'order_id' => $orderId,
                'code' => $total['code'],
                'title' => $total['title'],
                'value' => $total['value'],
                'sort_order' => $total['sort_order'],
            ]);
        }
    }

    /**
     * @param int $orderId
     * @param int $orderStatusId
     * @param array $customOrderFields
     * @param Carbon $now
     * @return void
     */
    public function insertHistory(int $orderId, int $orderStatusId, array $customOrderFields, Carbon $now): void
    {
        $history = [
            'order_id' => $orderId,
            'order_status_id' => $orderStatusId,
            'notify' => 0,
            'comment' => $this->formatHistoryComment($customOrderFields),
            'date_added' => $now,
        ];

        $this->database->insert(db_table('order_history'), $history);
    }

    private function insertProducts($products, int $orderId): void
    {
        foreach ($products as $product) {
            $this->database->insert(db_table('order_product'), [
                'order_id' => $orderId,
                'product_id' => $product['product_id'],
                'name' => $product['name'],
                'model' => $product['model'],
                'quantity' => $product['quantity'],
                'price' => $product['price_numeric'],
                'total' => $product['total_numeric'],
                'reward' => $product['reward_numeric'],
            ]);

            $orderProductId = $this->database->lastInsertId();
            foreach ($product['option'] as $option) {
                $this->database->insert(db_table('order_option'), [
                    'order_id' => $orderId,
                    'order_product_id' => $orderProductId,
                    'product_option_id' => $option['product_option_id'],
                    'product_option_value_id' => $option['product_option_value_id'],
                    'name' => $option['name'],
                    'value' => $option['value'],
                    'type' => $option['type'],
                ]);
            }
        }
    }
}
