<?php

namespace App\Services\Shopify;

use App\Helpers\StringHelper;
use App\Jobs\Nav\CreateOrder;
use App\Jobs\Shopify\ArchiveProduct;
use App\Models\Company;
use App\Models\Customer;
use App\Models\LicenseExtensionProduct;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Variant;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use RuntimeException;
use stdClass;

/**
 * Service class that holds all orders related Shopify interaction and business logic
 */
class OrdersService extends BaseService
{
    /**
     * Creates or updates an Order based on a Shopify order data
     *
     * @param stdClass $orderData
     * @return Order | false
     */
    public function createOrder(stdClass $orderData)
    {
        DB::beginTransaction();
        try {
            $order = Order::where('shopify_gid', $orderData->admin_graphql_api_id)
                ->first();

            if (empty($orderData->customer)) {
                throw new RuntimeException('Shopify Order ' . $orderData->name . ' from shopify has no customer!');
            }

            $customer = Customer::where('shopify_gid', $orderData->customer->admin_graphql_api_id)
                ->first();

            if (!$customer) {
                throw new RuntimeException('Shopify Order customer (' . $orderData->customer->admin_graphql_api_id . ') not found: ' . $orderData->name);
            }

            if (!$order) {
                $order = new Order();
                $order->customer_id = $customer->id;
                $order->company_id = $customer->company_id;
                $order->shopify_gid = $orderData->admin_graphql_api_id;
                $order->number = $orderData->name;
            }

            $order->order_created_at = (new Carbon($orderData->created_at))->setTimezone('UTC');
            $order->order_updated_at = $orderData->updated_at ? (new Carbon($orderData->updated_at))->setTimezone('UTC') : null;
            $order->email = $orderData->email;
            $order->financial_status = $orderData->financial_status;
            $order->currency = $orderData->currency;
            $order->external_reference = $orderData->po_number;

            if ($orderData->shipping_address) {
                $order->shipping_name = StringHelper::cleanWhiteSpaces(mb_substr($orderData->shipping_address->name, 0, 254, 'utf-8'));
                $order->shipping_first_name = StringHelper::cleanWhiteSpaces($orderData->shipping_address->first_name);
                $order->shipping_last_name = StringHelper::cleanWhiteSpaces($orderData->shipping_address->last_name);
                $order->shipping_company = $orderData->shipping_address->company;
                $order->shipping_address1 = $orderData->shipping_address->address1;
                $order->shipping_address2 = $orderData->shipping_address->address2;
                $order->shipping_city = $orderData->shipping_address->city;
                $order->shipping_province = $orderData->shipping_address->province;
                $order->shipping_province_code = $orderData->shipping_address->province_code;
                $order->shipping_country = $orderData->shipping_address->country;
                $order->shipping_country_code = $orderData->shipping_address->country_code;
                $order->shipping_zip = $orderData->shipping_address->zip;
                $order->shipping_phone = $orderData->shipping_address->phone;
            }

            if ($orderData->billing_address) {
                $order->billing_name = StringHelper::cleanWhiteSpaces(mb_substr($orderData->billing_address->name, 0, 254, 'utf-8'));
                $order->billing_first_name = StringHelper::cleanWhiteSpaces($orderData->billing_address->first_name);
                $order->billing_last_name = StringHelper::cleanWhiteSpaces($orderData->billing_address->last_name);
                $order->billing_company = $orderData->billing_address->company;
                $order->billing_address1 = $orderData->billing_address->address1;
                $order->billing_address2 = $orderData->billing_address->address2;
                $order->billing_city = $orderData->billing_address->city;
                $order->billing_province = $orderData->billing_address->province;
                $order->billing_province_code = $orderData->billing_address->province_code;
                $order->billing_country = $orderData->billing_address->country;
                $order->billing_country_code = $orderData->billing_address->country_code;
                $order->billing_zip = $orderData->billing_address->zip;
                $order->billing_phone = $orderData->billing_address->phone;
            }

            $order->same_address = empty(array_diff_assoc((array) $orderData->billing_address, (array) $orderData->shipping_address));

            $order->accepts_marketing = $orderData->buyer_accepts_marketing;
            $order->note = $orderData->note;
            $order->note_attributes = $orderData->note_attributes;
            $order->total = $orderData->total_price;
            $order->total_discount = $orderData->total_discounts;

            $totalShipping = 0;
            $totalShippingDiscounts = 0;
            $shippingType = null;
            foreach ($orderData->shipping_lines as $shippingLine) {
                $totalShipping += $shippingLine->price;
                if (!$shippingType) {
                    $shippingType = $shippingLine->title;
                }
                foreach ($shippingLine->discount_allocations as $shippingDiscount) {
                    $totalShippingDiscounts += $shippingDiscount->amount;
                }
            }
            $order->shipping_type = $shippingType;
            $order->shipping_total = $totalShipping;
            $order->shipping_discount = $totalShippingDiscounts;

            $order->total_tax = $orderData->total_tax;
            if (!empty($orderData->tax_lines)) {
                $order->tax_info = $orderData->tax_lines;
            }

            $order->to_sync = 1;

            $order->save();

            foreach ($orderData->line_items as $i => $lineItemData) {
                $orderItem = OrderItem::where('shopify_gid', $lineItemData->admin_graphql_api_id)
                    ->first();

                $variantGid = "gid://shopify/ProductVariant/$lineItemData->variant_id";

                $variant = Variant::where('shopify_gid', $variantGid)->first();

                $extensionProduct = LicenseExtensionProduct::where('variant_gid', $variantGid)
                    ->first();

                if (!$variant && !$extensionProduct) {
                    throw new RuntimeException('Shopify product variant or extension ' . $lineItemData->variant_id . ' / ' . $lineItemData->sku . ' does not exist!');
                }

                if (!$orderItem) {
                    $orderItem = new OrderItem();
                    $orderItem->shopify_gid = $lineItemData->admin_graphql_api_id;
                    $orderItem->order_id = $order->id;

                    if ($variant) {
                        $orderItem->product_id = $variant->product_id;
                        $orderItem->variant_id = $variant->id;
                        $orderItem->nav_no = $variant->nav_no;
                    } elseif ($extensionProduct) {
                        $orderItem->nav_no = $lineItemData->sku;
                        $orderItem->license_extension_product_id = $extensionProduct->id;
                    }
                }

                $orderItem->pos = ($i +  1);
                $orderItem->quantity = $lineItemData->quantity;
                $orderItem->total = $lineItemData->price * $lineItemData->quantity;

                $totalDiscount = 0;
                foreach ($lineItemData->discount_allocations as $discountAllocation) {
                    $totalDiscount += $discountAllocation->amount;
                }
                $orderItem->discount = $totalDiscount;

                $orderItem->save();

                if ($extensionProduct) {
                    if ($extensionProduct->status == LicenseExtensionProduct::STATUS_PENDING) {
                        $extensionProduct->status = LicenseExtensionProduct::STATUS_COMPLETED;
                        $extensionProduct->save();

                        ArchiveProduct::dispatch($extensionProduct->product_gid);
                    }
                }
            }

            DB::commit();

            // Marks order as integrated in shopify
            $this->addOrderTag($order, 'int');

            CreateOrder::dispatch($order);

            return $order;
        } catch (\Throwable $e) {
            DB::rollBack();
            Log::error($e);
            throw($e);
        }
        return false;
    }

    /**
     * Add tag to an order in shopify
     *
     * @param Order $order
     * @param string $tag
     * @return bool
     */
    public function addOrderTag(Order $order, string $tag)
    {
        $query = '
            mutation OrderTagsAdd($order_id: ID!, $order_tags: [String!]!) {
                tagsAdd(id: $order_id, tags: $order_tags) {
                    node {
                        id
                    }
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'order_id' => $order->shopify_gid,
            'order_tags' => [$tag]
        ]);

        if (!$this->_checkResponse($response, 'tagsAdd')) {
            return false;
        }

        return true;
    }

    /**
     * Creates a draft order for a given company / customer in Shopify
     *
     * @param Company $company
     * @param Customer $customer
     * @param array $data
     * @return string - Draft order GID
     */
    public function createDraftOrder(Company $company, Customer $customer, array $data)
    {
        $lineItems = [];
        foreach ($data['lineItems'] as $lineItem) {
            $variantGid = 'gid://shopify/ProductVariant/' . $lineItem['variantId'];
            /* No need for bundle specific support since shopify already supports bundles for draft orders
            $variant = Variant::where('shopify_gid', $variantGid)->first();

            if ($variant && $variant->product->is_bundle) {
                foreach ($variant->componentVariants as $componentItem) {
                    $lineItems[] = [
                        'variantId' => $componentItem->shopify_gid,
                        'quantity' => $lineItem['quantity'],
                        'customAttributes' => [
                            [
                                'key' => 'bundle',
                                'value' => $variant->product->title
                            ],
                            [
                                'key' => 'bundle_sku',
                                'value' => $variant->nav_no
                            ]
                        ]
                    ];
                }
            } else {
            */
            $customAttributes = [];
            if (!empty($lineItem['properties'])) {
                foreach ($lineItem['properties'] as $key => $value) {
                    $customAttributes[] = [
                        'key' => $key,
                        'value' => $value
                    ];
                }
            }
            $lineItems[] = [
                'variantId' => $variantGid,
                'quantity' => $lineItem['quantity'],
                'customAttributes' => $customAttributes
            ];
            //}
        }

        $billingAddress = $company->billingAddress;
        $address = [
            'address1' => $billingAddress->address1,
            'address2' => $billingAddress->address2,
            'city' => $billingAddress->city,
            'company' => $billingAddress->name,
            'countryCode' => $billingAddress->country_code,
            'phone' => $billingAddress->phone,
            'provinceCode' => $billingAddress->state_code,
            'zip' => $billingAddress->zip
        ];

        $input = [
            'purchasingEntity' => [
                'purchasingCompany' => [
                    'companyId' => $company->shopify_gid,
                    'companyContactId' => $customer->contact_gid,
                    'companyLocationId' => $company->location_gid
                ]
            ],
            'lineItems' => $lineItems,
            'marketRegionCountryCode' => $billingAddress->country_code,
            'billingAddress' => $address,
            'shippingAddress' => $address,
            'customAttributes' => [
                [
                    'key' => 'tax_exempt',
                    'value' => $data['isTaxExempt'] ? '1' : '0'
                ]
            ]
        ];

        if (!empty($data['note'])) {
            $input['note'] = $data['note'];
        }

        $query = '
            mutation draftOrderCreate($input: DraftOrderInput!) {
                draftOrderCreate(input: $input) {
                    draftOrder {
                        id
                    }
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'input' => $input
        ]);

        if (!$this->_checkResponse($response, 'draftOrderCreate')) {
            return false;
        }
        return $response['body']['data']['draftOrderCreate']['draftOrder']['id'];
    }

    /**
     * Sends a request to Shopify to send the draft order to the email provided
     *
     * @param string $drafOrderGid
     * @param string $toEmail
     * @return bool
     */
    public function invoiceDraftOrder(string $drafOrderGid, string $toEmail)
    {
        $query = '
            mutation draftOrderInvoiceSend($id: ID!, $email: EmailInput) {
                draftOrderInvoiceSend(id: $id, email: $email) {
                    draftOrder {
                        id
                    }
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'id' => $drafOrderGid,
            'email' => ['to' => $toEmail]
        ]);

        if (!$this->_checkResponse($response, 'draftOrderInvoiceSend')) {
            return false;
        }
        return true;
    }

    public function getDraftOrder(string $draftOrderGid)
    {
        $query = '
            query draftOrder($id: ID!) {
                draftOrder(id: $id) {
                    id
                    customer {
                        id
                    }
                    lineItems(first:99) {
                        edges {
                            node {
                                id
                                variant {
                                    id
                                }
                                quantity
                                customAttributes {
                                    key,
                                    value
                                }
                            }
                        }
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'id' => $draftOrderGid
        ]);

        return $response['body']['data']['draftOrder'];
    }

    /**
     * Adds a product to an existing draft order
     *
     * @param string $drafOrderGid
     * @param string $variantGid
     * @param int $quantity
     * @return bool
     */
    public function addProductToDraftOrder(string $draftOrderGid, string $variantGid, int $quantity = 1, array $properties = null)
    {
        $draftOrder = $this->getDraftOrder($draftOrderGid);

        $lineItems = [];
        foreach ($draftOrder['lineItems']['edges'] as $lineItem) {
            if (empty($lineItem['node']['variant'])) {
                continue;
            }
            $lineItems[] = [
                'variantId' => $lineItem['node']['variant']['id'],
                'quantity' => $lineItem['node']['quantity'],
                'customAttributes' => $lineItem['node']['customAttributes']
            ];
        }

        $newItem = [
            'variantId' => $variantGid,
            'quantity' => $quantity
        ];

        if ($properties) {
            $newItem['customAttributes'] = array_map(function ($property, $value) {
                return [
                    'key' => $property,
                    'value' => $value
                ];
            }, array_keys($properties), array_values($properties));
        }

        $lineItems[] = $newItem;

        $input = [
            'lineItems' => $lineItems
        ];

        $query = '
            mutation draftOrderUpdate($id: ID!, $input: DraftOrderInput!) {
                draftOrderUpdate(id: $id, input: $input) {
                    draftOrder {
                        id
                    }
                    userErrors {
                        field
                        message
                    }
                }
            }
        ';

        $response = $this->api->graph($query, [
            'id' => $draftOrderGid,
            'input' => $input
        ]);

        if (!$this->_checkResponse($response, 'draftOrderUpdate')) {
            return false;
        }

        return true;
    }
}
