<?php
declare(strict_types=1);

namespace Edara\Includes;

use Automattic\WooCommerce\Admin\Overrides\Order;
use WC_Order;

class OrderService extends AbstractService
{
    /**
     * Define the order status map and action
     *
     * @var array|string[]
     */
    protected array $orderStatusTransitions = [
        'pending' => [
            'processing' => [
                'id' => 'wc-processing',
                'actions' => ['updateOrCreateExternalOrder'],
            ],
            'cancelled' => [
                'id' => 'wc-cancelled',
                'actions' => [],
            ],
        ],
        'processing' => [
            'cancelled' => [
                'id' => 'wc-cancelled',
                'actions' => ['cancelTheExternalOrder'],
            ],
            'pending' => [
                'id' => 'wc-pending',
                'actions' => ['deleteTheExternalOrderFromEdara'],
            ],
        ],
        'cancelled' => [
            'processing' => [
                'id' => 'wc-processing',
                'actions' => ['updateOrCreateExternalOrder'],
            ],
        ],
        'shipped' => [
            'completed' => [
                'id' => 'wc-completed',
                'actions' => [],
            ],
        ],
        'completed' => [
            'ask_for_return' => [
                'id' => 'wc-ask_for_return',
                'actions' => ['createSalesReturnRequest'],
            ],
        ],
        'ask_for_return' => [
            'return' => [
                'id' => 'wc-return',
                'actions' => [],
            ],
        ],
    ];

    /**
     * Create external order on Edara
     *
     * @param int $orderId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function updateOrCreateExternalOrder(int $orderId):void
    {
        $externalCode = get_post_meta($orderId, 'external_code_id', true);

        // Check if the sales order is exist on Edara to update
        if ($externalCode) {
            // Take no action
            return;
        }

        // Create external order for the first time
        $this->createExternalOrder($orderId);
    }

    /**
     * @param int $orderId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function createExternalOrder(int $orderId):void
    {
        // $seqOrderNumber = get_post_meta($orderId, '_order_number', true);
        // $order = new WC_Order($orderId);
        // $customerService = new CustomerService();
        // $customerId = $order->get_customer_id();
        // $customerBillingInfo = $this->generateBillingInfo($order);
        // // phpcs:ignore
        // $externalCustomerId = $customerService->findExternalCustomerIdOrCreateNew($customerId, $customerBillingInfo);
        // $shippingAddress = $this->generateShippingInformationFromOrder($order);
        // $currentDateAndTime = (new \DateTime('now', new \DateTimeZone('Africa/Cairo')))->format(EDARA_DATE_TIME_FORMAT);
        // $debug = [];
        // $data = [
        //     'order_status' => 'Pending',
        //     'net_total' => (float)$order->get_total(),
        //     'sub_total' => (float)$order->get_subtotal(),
        //     'paper_number' => "$seqOrderNumber",
        //     'customer_id' => $externalCustomerId,
        //     // @TODO Ware house should be defined
        //     'warehouse_id' => EDARA_MAIN_WAREHOUSE_ID,
        //     'salesstore_id' => null,
        //     'shippment_cost' => $order->get_shipping_total(),
        //     'document_date' => $currentDateAndTime,
        //     'shipping_date' => null,
        //     'discount' => $order->get_total_discount(),
        //     'taxable' => true,
        //     'tax' => 0.0000,
        //     'cash_amount' => 0.0000,
        //     'notes' => $shippingAddress . ' \n' . implode(',', $order->get_customer_order_notes() ?? []),
        //     'address_id' => null,
        //     'payment_status' => $order->needs_payment() ? 'Unpaid' : 'paid',
        // ];
        //
        // foreach ($order->get_items() as $itemId => $item) {
        //     // Check item is a smart bundle
        //     if ($item->meta_exists('_woosb_parent_id')) {
        //         continue;
        //     }
        //
        //     /** @var \WC_Product_Grouped $product */
        //     if ('woosb' === $item->get_product()->get_type()) {
        //         $externalBundleId = get_post_meta($item->get_product()->get_id(), 'external_bundle_id', true);
        //         $data['salesOrder_details'][] = [
        //             'quantity' => $item->get_quantity(),
        //             'bundle_id' => $externalBundleId,
        //         ];
        //         // Then Escape from the loop
        //         continue;
        //     }
        //
        //     $debug[] = [
        //         'product_id' => $item->get_product_id(),
        //         'external_id' => get_post_meta($item->get_product_id(), 'external_id', true)
        //     ];
        //
        //     $productId = $item->get_product_id();
        //     $externalId = get_post_meta($productId, 'external_id', true);
        //
        //     // Special handling in case the post doesn't have the external Id
        //     if (! $externalId) {
        //         $productId = $this->findExternalIdFromTranslatedProduct($productId);
        //         $externalId = get_post_meta($productId, 'external_id', true);
        //     }
        //
        //     $data['salesOrder_details'][] = [
        //         'quantity' => $item->get_quantity(),
        //         'price' => (float) $item->get_product()->get_price(),
        //         'insert_date' => $currentDateAndTime,
        //         'warehouse_id' => EDARA_MAIN_WAREHOUSE_ID,
        //         'stock_item_id' => $externalId,
        //         'stock_item_description' => $item->get_name(),
        //         'tax_rate' => floatval(get_post_meta($productId, ProductTaxRate::EXTERNAL_PRODUCT_TAX_RATE, true)),
        //     ];
        // }
        //
        // // Shipping service
        // if ((float) $order->get_total_fees()) {
        //     $data['salesOrder_details'][] = [
        //         'quantity' => 1,
        //         'price' => (float) $order->get_total_fees(),
        //         'service_item_id' => 1,
        //     ];
        // }
        //
        // #1 In-case order is redirected from payment gateway -> so get the payment
        // $isOnlinePayment = ('accept-online' == $order->get_payment_method());
        // #2 In-case order is updated from admin panel
        // if (is_array($_POST) && isset($_POST['_payment_method']) && $orderId == array_get($_POST, 'post_ID')) {
        //     $isOnlinePayment = ('accept-online' == array_get($_POST, '_payment_method'));
        // }
        //
        // // Make order paid on Edara
        // // if ($isOnlinePayment) {
        // if (true){
        //     $data['salesOrder_installments'][] = [
        //         'days_limit' => 0,
        //         'amount' => (float)$order->get_total(),
        //         'due_date' => $currentDateAndTime
        //     ];
        // }

        // $response = $this->api()->createOrder($data);
        // if (200 === (int) array_get($response, 'status_code')) {
        //     update_post_meta($orderId, 'external_code_id', array_get($response, 'result'));
        // }
        // update_post_meta($orderId, 'order_to_edara_logs', json_encode(['response' => $response ,'request' => $data ,'debug' => $debug]));
        // // Add note to order
        // $order->add_order_note(("Edara: ".array_get($response, 'error_message', array_get($response, 'result'))));
        // $this->checkOrderPriceIsMatched($orderId, $order);
        // @TODO should log error here
    }

    /**
     * Delete external order by document code
     *
     * @param string $orderExternalCode
     * @return bool
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function deleteExternalOrder(string $orderExternalCode):bool
    {
        #1 Get order id to be deleted
        $response = $this->api()->retrieveOrderByCode($orderExternalCode);

        // Order is now processing now and cannot alter or update it
        if ('processing' === strtolower(array_get($response, 'result.order_status', ''))) {
            return false;
        }

        $orderExternalId = array_get($response, 'result.id');

        #2 Delete order by ID
        if ($orderExternalId) {
            $this->api()->deleteOrder($orderExternalId);
            return true;
        }

        return false;
    }

    /**
     * Hook: on moving order to trash
     *
     * @param int $postId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function orderMovedToTrash(int $postId): void
    {
        $externalOrderCode = get_post_meta($postId, 'external_code_id', true);

        #1 Order doesn't exist on Edara
        if (!$externalOrderCode) {
            return;
        }
        #2 Remove the meta data from order if deleted
        if ($this->deleteExternalOrder($externalOrderCode)) {
            delete_post_meta($postId, 'external_code_id', null);
        }
    }

    /**
     * Generate billing array from order
     *
     * @param \WC_Order $order
     * @return array
     */
    private function generateBillingInfo(WC_Order $order): array
    {
        return [
            'address_1' => $order->get_billing_address_1(),
            'address_2' => $order->get_billing_address_2(),
            'first_name' => $order->get_billing_first_name(),
            'last_name' => $order->get_billing_last_name(),
            'email' => $order->get_billing_email(),
            'mobile' => $order->get_billing_phone(),
        ];
    }

    /**
     * Hook on restoring order from trash to create a new order again
     *
     * @param int $postId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function restoreOrderFromTrash(int $postId): void
    {
        /** @var WP_Post $post */
        $post = get_post($postId);

        if ('shop_order' === $post->post_type) {
            $this->updateOrCreateExternalOrder($postId);
        }
    }

    /**
     * @param \WC_Order $order
     * @return string
     */
    private function generateShippingInformationFromOrder(WC_Order $order): string
    {
        $address1 = $order->get_shipping_address_1() ?: $order->get_billing_address_1();
        $address2 = $order->get_shipping_address_2() ?: $order->get_billing_address_2();
        $firstName = $order->get_shipping_first_name() ?: $order->get_billing_first_name();
        $lastName = $order->get_shipping_last_name() ?: $order->get_billing_last_name();
        $city = $order->get_shipping_city() ?: $order->get_billing_city();
        $phone = $order->get_billing_phone();
        $shippingAddress = [
            'Shipping information: ',
            $firstName ? "First name : $firstName " : '',
            $lastName ? "Last name : $lastName " : '',
            $city ? "City : $city " : '',
            $phone ? "Phone : $phone " : '',
            $address1 ? "Address #1: $address1 " : '',
            $address1 ? "Address #2: $address2 " : '',
        ];

        return implode(',', array_filter($shippingAddress));
    }

    /**
     * The workflow for order status
     *
     * @param int $orderId
     * @param string $newStatus
     * @throws \Exception
     */
    public function editOrderStatusWorkflow(int $orderId, string $newStatus):void
    {
        $order = wc_get_order($orderId);
        $oldStatus = $order->get_status();

        $isExist = array_get($this->orderStatusTransitions, "{$oldStatus}.{$newStatus}");
        if (!$isExist) {
            throw new \Exception('Cannot proceed with this transition.');
        }

        #1 Transition is exist
        $actions = array_get($this->orderStatusTransitions, "{$oldStatus}.{$newStatus}.actions");
        foreach ($actions as $action) {
            $this->{$action}($orderId);
        }
    }

    /**
     * Cancel the order on edara
     *
     * @param int $orderId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    protected function cancelTheExternalOrder(int $orderId):void
    {
        $orderExternalCode = get_post_meta($orderId, 'external_code_id', true);

        ##1 Set status to cancelled
        $response = $this->api()->cancelOrder($orderExternalCode);
        if (200 !== (int) array_get($response, 'status_code')) {
            // phpcs:ignore
            // Add the response from Edara if cancellation failed
            wc_get_order($orderId)->add_order_note('Edara cannot cancel order: '.array_get($response, 'error_message'));
            wp_die("Cannot cancel processed order, You have to contact Edara admin.");
        }

        ##2 Delete the saved external code [Cannot alter the cancelled order on Edara]
        if (array_get($response, 'result')) {
            delete_post_meta($orderId, 'external_code_id');
        }
    }

    /**
     * Delete the external order
     *
     * @param int $orderId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    protected function deleteTheExternalOrderFromEdara(int $orderId):void
    {
        $orderExternalCode = get_post_meta($orderId, 'external_code_id', true);
        // If the external code is not defined, Don't do any thing
        if (!$orderExternalCode) {
            return;
        }
        $isDeleted = $this->deleteExternalOrder($orderExternalCode);

        ##1 Remove the meta data from order if deleted
        if ($isDeleted) {
            delete_post_meta($orderId, 'external_code_id');
            return;
        }

        $back = "<input type= 'button' onclick='javascript:history.back();return false;' value='Back'>";
        // phpcs:ignore
        wp_die("Cannot delete processed order, You have to contact Edara admin to change the order status to pending. $back");
    }

    /**
     * Create a sales return request
     *
     * @param int $orderId
     */
    protected function createSalesReturnRequest(int $orderId):void
    {
        $externalOrderCode = get_post_meta($orderId, 'external_code_id', true);
        $externalOrderData = $this->api()->retrieveOrderByCode($externalOrderCode);
        $relatedWorkorderCode = array_get($externalOrderData, 'result.related_workorder_code');
        update_post_meta($orderId, 'external_issue_offering_code', $relatedWorkorderCode);

        $only = [
            'document_code',
            'customer_id',
            'warehouse_id',
            'related_workorder_code',
            'salesOrder_details',
            'discount',
        ];
        $salesReturnData = array_only(array_get($externalOrderData, 'result', []), $only);
        $salesReturnData['document_date'] = date('Y-m-d H:i:s');
        // Create sales return on Edara
        $response = $this->api()->createSalesReturn($salesReturnData);
        if (200 !== (int) array_get($response, 'status_code')) {
            wp_die('Error while creating sales return, plz contract Edara admin to solve the issue.');
        }
        update_post_meta($orderId, 'external_sales_return_code', array_get($response, 'result'));
    }

    /**
     * Get the available for selected status
     *
     * @param string $status
     * @return array
     */
    public function retrieveAvailableStatus(string $status): array
    {
        // Remove the wc- if exist
        if ('wc-' === substr($status, 0, 3)) {
            $status = substr($status, 3, strlen($status));
        }

        $output = [];
        $transitions = array_get($this->orderStatusTransitions, $status, []);
        foreach ($transitions as $transition) {
            $output[] = $transition['id'];
        }

        return $output;
    }

    /**
     * Change order status to shipped
     *
     * @param array $data
     */
    public function orderStatusChangedToShippedCallback(array $data):void
    {
        $externalOrderId = array_get($data, 'data.id');
        $externalOrderCode = array_get($data, 'data.document_code');
        $externalOrderStatus = array_get($data, 'data.order_status');
        // @TODO I should add the related here
        // Escape if the order status is not shipped
        if ('Shipped' !== $externalOrderStatus) {
            return;
        }

        $order = $this->findLocalOrderIdByOrderExternalCode($externalOrderCode);
        // Order is not exist in our database @TODO display notification on admin dashboard
        if (!$order) {
            $myFileLink2 = fopen((ABSPATH . 'orders.logs'), 'a');
            fwrite($myFileLink2, json_encode($data));
            fclose($myFileLink2);
            return;
        }

        $order->update_status('wc-shipped', 'Admin accept the order from Edara.');
        update_post_meta($order->get_id(), 'external_id', $externalOrderId);
    }

    /**
     * Find the local order id by the external order code
     *
     * @param string $externalOrderCode
     * @return bool|mixed|\WC_Order
     */
    // phpcs:ignore
    private function findLocalOrderIdByOrderExternalCode(string $externalOrderCode)
    {
        $args = [
            'post_type' => 'shop_order',
            'meta_key' => 'external_code_id',
            'meta_value' => $externalOrderCode,
            'meta_compare' => '=',
            'numberposts' => '1',
        ];

        $orders = wc_get_orders($args);
        if ($orders) {
            return $orders[0];
        }

        return false;
    }

    /**
     * Validate order can cancelled or not
     *
     * @param $status
     * @param \Automattic\WooCommerce\Admin\Overrides\Order $order
     * @return array|string[]
     */
    public function validToCancelFromFrontEnd($status, Order $order): array
    {
        $status = ['pending'];
        $isValid = $this->canCancelTheExternalOrder($order->get_id());

        if ($isValid) {
            $status[] = 'processing';
            return $status;
        }

        return $status;
    }

    /**
     * Check the external order is still pending
     *
     * @param null $orderId
     * @param null $orderExternalCode
     * @return bool
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function canCancelTheExternalOrder($orderId = null, $orderExternalCode = null):bool
    {
        $orderExternalCode = $orderExternalCode ?: get_post_meta($orderId, 'external_code_id', true);

        if (!$orderExternalCode) {
            return false;
        }

        $order = $this->api()->retrieveOrderByCode($orderExternalCode);

        if ('pending' === strtolower(array_get($order, 'result.order_status', ''))) {
            return true;
        }

        return false;
    }

    /**
     * Cancel order from frontend
     *
     * @param $orderId
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function cancelOrderFromFrontend($orderId):void
    {
        $this->cancelTheExternalOrder($orderId);
    }

    /**
     * Delete sales orders
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function resetSalesOrderOnEdara(): void
    {
        $orders = array_get($this->api()->findOrders(10, 0), 'result');
        foreach ($orders as $order) {
            $this->api()->deleteOrder(array_get($order, 'id'));
        }
    }

    /**
     * @param int $productId
     * @return int
     */
    private function findExternalIdFromTranslatedProduct(int $productId): int {
        // $translatedProductId = wpml_object_id_filter($productId, 'post', FALSE, 'ar');
        $translatedProductId = apply_filters( 'wpml_object_id', $productId, 'post', FALSE, 'ar'  );
        $externalId = get_post_meta($translatedProductId, 'external_id', TRUE);

        if ($externalId) {
            return $externalId;
        }

        return apply_filters( 'wpml_object_id', $productId, 'post', FALSE, 'en'  ) ?: 0;
    }

    /**
     * Find orders by External code Id
     *
     * @param string $externalCodeId
     * @return array
     */
    public function findOrdersByExternalId(string $externalCodeId):array
    {
        $args = [
            'post_type' => 'shop_order',
            'meta_key' => 'external_code_id',
            'meta_value' => $externalCodeId,
            'meta_compare' => '=',
            'numberposts' => '2',
        ];

        return wc_get_orders($args);
    }

    /**
     * Check order price is matched on WP and Edara
     *
     * @param  int  $orderId
     * @param  null  $order
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function checkOrderPriceIsMatched(int $orderId, $order = null): void
    {
        $order = $order ?? wc_get_order($orderId);
        $externalOrderId = get_post_meta($orderId, 'external_code_id', true);

        if (!$externalOrderId) {
            return;
        }

        $data = array_get($this->api()->retrieveOrderByCode($externalOrderId), 'result');
        if (!$data) {
            return;
        }

        if ((float) $order->get_total() === (float) $data['net_total']) {
            return;
        }

        update_post_meta($orderId, 'price_is_not_matched', true);
    }
}
