<?php

namespace Modules\PrintSuppliers\Controllers;

use App\Customer;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Modules\PrintSuppliers\Models\Order;
use Modules\PrintSuppliers\Models\OrderItem;
use Modules\PrintSuppliers\Models\OrderMeta;
use Modules\PrintSuppliers\Models\OrderMatching;
use Modules\PrintSuppliers\Models\Profile;
use Modules\PrintSuppliers\Models\Shipper;
use Modules\PrintSuppliers\Models\Template;
use Modules\PrintSuppliers\Models\TemplateGelato;
use GuzzleHttp\Client;

class GelatoController extends Controller
{
    private $gelatoApiUrl;
    private $gelatoConfig;
    private $mapStatuses;
    private $printingName = 'gelato';
    private $shipmentKey = 'shipment';
    private $shipmentDataKey = 'shipment_data';
    private $framedImageWorkerUrl;
    private $exchangeRate = [
        'USD' => 1,
        'EUR' => 0.92902,
        'AUD' => 1.39186,
        'CAD' => 1.26784,
        'GBP' => 0.79106,
        'JPY' => 127.50836,
        'BRL' => 4.70355,
        'CHF' => 0.9594,
        'DKK' => 6.91109,
        'INR' => 77.55017,
        'NOK' => 9.40691,
        'NZD' => 1.52722,
        'PLN' => 4.26003,
        'SEK' => 9.77146
    ];

    public function __construct() {
        $this->gelatoConfig = loadGelatoConfigs(Config::get('print-suppliers::connection.gelato'));
        $this->gelatoApiUrl = Config::get('print-suppliers::connection.gelato.api_url', 'https://product.gelatoapis.com/v3/');
        $this->mapStatuses = Config::get('print-suppliers::statuses.galato');
        $this->framedImageWorkerUrl = 'https://printerval.com/print-suppliers/framed-image';
    }

    public function callGelatoApi(Request $request)
    {
        $endPoint = $request->input('end_point');
        $method = $request->input('method', 'GET');
        $params = $request->input('params', []);
        $baseApi = $request->input('base_api');

        return $this->internalCallApi($endPoint, $method, $params, $baseApi);
    }

    public function internalCallApi($endPoint, $method = 'GET', $params = [], $baseApi = null)
    {
        if ($baseApi) {
            $this->gelatoApiUrl = $baseApi;
        }
        
        $client = new Client([
            'headers' => [
                'X-API-KEY' => $this->gelatoConfig['api_token'],
                'Content-Type' => 'application/json'
            ]
        ]);

        try {

            if ($method == 'POST') {
                $httpResponse = $client->post($this->gelatoApiUrl . $endPoint, [
                    'body' => json_encode($params, JSON_UNESCAPED_UNICODE)
                ]);
            } else {
                $httpResponse = $client->request($method, $this->gelatoApiUrl . $endPoint, $params);
            }

            $responseBody = $httpResponse->getBody();

        } catch (\Exception $ex) {
            $responseBody = '[]';
        }

        return json_decode($responseBody, true);
    }

    public function gelatoWebhook(Request $request) {
        $input = $request->all();
        $retVal['status'] = 'fail';

        if (!empty($input['object']) && $input['object'] == "trackingCode") {
            $partCodes = explode('_', $input['orderReferenceId']);
            $orderCode = end($partCodes);

            $originOrder = Order::query()
                                ->where('code', '=', $orderCode)
                                ->whereNotIn('status', ['CANCELED', 'REQUEST_RETURN', 'RETURNED'])
                                ->where('payment_status', '=', 'PAID')
                                ->orderBy('id', 'DESC')
                                ->first();

            if (empty($originOrder)) {
                $orderItem = OrderItem::query()
                                    ->where('resource_id', '=', $input['orderId'])
                                    ->first();
                if (!empty($orderItem->order_id)) {
                    $originOrder = Order::query()
                                        ->where('id', '=', $orderItem->order_id)
                                        ->whereNotIn('status', ['CANCELED', 'REQUEST_RETURN', 'RETURNED'])
                                        ->where('payment_status', '=', 'PAID')
                                        ->orderBy('id', 'DESC')
                                        ->first();
                }
            }

            if (empty($originOrder->id)) {
                $retVal['message'] = 'Order Not Found.';
                return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
            }

            $shipments = $this->_getShipmentData($originOrder->id); 
            $shipmentsValue = $shipments->value;
            $existsCode = false;
            $dataUpdate['shipments'] = [];
            foreach ($shipments->value as $shipment) {
                if ($shipment['code'] == $input['trackingCode']) {
                    $existsCode = true;
                }
            }
            if (!$existsCode) {
                $shipperId = $this->_saveShipper($input['carrierName']);
                OrderMeta::updateOrCreate([ 'order_id' => $originOrder->id, 'key' => $this->shipmentKey ], [ 'value' => $shipperId ]);
                $tempShipment = [
                    'id' => $shipperId,
                    'code' => $input['trackingCode'],
                    'url' => $input['trackingUrl']
                ];
                $dataUpdate['status'] = 'DELIVERING';
                array_push($shipmentsValue, $tempShipment);
                array_push($dataUpdate['shipments'], $tempShipment);
            }

            if (!empty($shipmentsValue)) {
                $shipments->value = json_encode($shipmentsValue);
                $shipments->save();
            }

            if (!empty($dataUpdate['shipments'])) {
                $this->_saveOrder($originOrder, $dataUpdate, 'Webhook');
            }
            $retVal['status'] = 'successful';
            return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
        } 
        
        else if (!empty($input['orderId'])) {
            $endPoint = 'orders/' . $input['orderId'];
            $gelatoOrder = $this->triggerSyncRequest($this->gelatoApiUrl . $endPoint, 'GET', [], [
                'X-API-KEY: ' . $this->gelatoConfig['api_token'],
                'Content-Type: application/json'
            ]);

            if (empty($gelatoOrder['id'])) {
                $retVal['message'] = 'Empty resource ID & DATA.';
                return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
            }

            if (empty($gelatoOrder['fulfillmentStatus']) || empty($this->mapStatuses[$gelatoOrder['fulfillmentStatus']])) {
                $retVal['message'] = 'Status not match from Config.';
                $retVal['detail'] = [$this->mapStatuses, $gelatoOrder];
                return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
            }

            if (!empty($gelatoOrder['orderReferenceId'])) {
                $partCodes = explode('_', $input['orderReferenceId']);
                $gelatoOrderCode = end($partCodes);

                $originOrder = Order::query()
                                    ->where('code', '=', $gelatoOrderCode)
                                    ->whereNotIn('status', ['CANCELED', 'REQUEST_RETURN', 'RETURNED'])
                                    ->first();
                if (!empty($originOrder->id)) {
                    $this->_updateOrder($originOrder, $gelatoOrder, 'Webhook');
                    $retVal['status'] = 'successful';
                    return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
                }
            }

            $orderItem = OrderItem::query()
                                    ->where('resource_id', '=', $gelatoOrder['id'])
                                    ->first();
            if (!empty($orderItem->order_id)) {
                $originOrder = Order::where('id', '=', $orderItem->order_id)
                                    ->whereNotIn('status', ['CANCELED', 'REQUEST_RETURN', 'RETURNED'])
                                    ->where('payment_status', '=', 'PAID')
                                    ->orderBy('id', 'DESC')
                                    ->first();
                if (!empty($originOrder->id)) {
                    $this->_updateOrder($originOrder, $gelatoOrder, 'Webhook');
                    $retVal['status'] = 'successful';
                    return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
                }
            }
            $retVal['message'] = 'Order Not Found.';
        }

        return response()->json($retVal, 200, [], JSON_NUMERIC_CHECK);
    }

    private function _saveShipper($shipperName) {
        $shipper = Shipper::firstOrCreate([
            'name' => $shipperName
        ], [
            'name' => $shipperName, 
            'meta_data' => 'Created by Webhook'
        ]);
        return $shipper->id;
    }

    private function _getShipmentData($orderId) {
        $shipments = OrderMeta::where('order_id', '=', $orderId)
                        ->where('key', '=', $this->shipmentDataKey)
                        ->first();

        if (empty($shipments)) {
            $shipments = new OrderMeta;
            $shipments->order_id = $orderId;
            $shipments->key = $this->shipmentDataKey;
            $shipments->value = [];
        } else {
            $shipments->value = json_decode($shipments->value, true);
        }
        return $shipments;
    }

    private function _updateOrder($order, $gelatoOrder, $refer = null) {
        $dataUpdate = [];

        if ($gelatoOrder['shipment']['packages']) {
            $shipments = $this->_getShipmentData($order->id);  
            $shipmentsValue = $shipments->value;    
            foreach ($gelatoOrder['shipment']['packages'] as $package) {
                if (!empty($gelatoOrder['shipment']['carrierName'])) {
                    $existsCode = false;
                    foreach ($shipments->value as $shipment) {
                        if ($shipment['code'] == $package['trackingCode']) {
                            $existsCode = true;
                        }
                    } 

                    if (!$existsCode) {
                        $shipperId = $this->_saveShipper($gelatoOrder['shipment']['carrierName']);
                        OrderMeta::updateOrCreate([ 'order_id' => $order->id, 'key' => $this->shipmentKey ], [ 'value' => $shipperId ]);
                        $tempShipment['id'] = $shipperId;
                        if (!empty($package['trackingCode'])) {
                            $tempShipment['code'] = $package['trackingCode'];
                        }
                        if (!empty($package['trackingUrl'])) {
                            $tempShipment['url'] = $package['trackingUrl'];
                        }
                        array_push($shipmentsValue, $tempShipment);
                        if (empty($dataUpdate['shipments'])) {
                            $dataUpdate['shipments'] = [];
                        }
                        array_push($dataUpdate['shipments'], $tempShipment);
                    }
                }
            }

            if (!empty($shipmentsValue)) {
                $shipments->value = json_encode($shipmentsValue);
                $shipments->save();
            }
        }

        if (!empty($this->mapStatuses[$gelatoOrder['fulfillmentStatus']])) {
            $dataUpdate['status'] = $this->mapStatuses[$gelatoOrder['fulfillmentStatus']];
        }

        if (!empty($dataUpdate)) {
            $this->_saveOrder($order, $dataUpdate, $refer);
        }

        $orderUpdate = [];
        $exchangeRate = DB::table('option')->where('key', '=', 'exchange.rate')->first();
        if (!empty($exchangeRate->value)) {
            $this->exchangeRate = json_decode($exchangeRate->value, true);
        }

        $exchangeRatio = 1;
        if (!empty($gelatoOrder['receipts'])) {
            $receipt = $gelatoOrder['receipts'][0];
            $exchangeRatio = $this->exchangeRate[$receipt['currency']];

            $orderUpdate['cost'] = $receipt['productsPriceInitial'] / $exchangeRatio;
            $discountShipping = 0;
            if (!empty($receipt['shippingPriceDiscount'])) {
                $discountShipping = $receipt['shippingPriceDiscount'];
            }
            $orderUpdate['shipping_cost'] = ($receipt['shippingPriceInitial'] - $discountShipping) / $exchangeRatio;
            $orderUpdate['tax_cost'] = $receipt['totalVat'] / $exchangeRatio;
        }
        if (isset($gelatoOrder['financialStatus']) && ($gelatoOrder['financialStatus'] == 'canceled' || $gelatoOrder['financialStatus'] == 'refunded')) {
            $orderUpdate['shipping_cost'] = 0;
            $orderUpdate['tax_cost'] = 0;
            $orderUpdate['cost'] = 0;
        }

        if (!empty($orderUpdate)) {
            try {
                OrderMatching::updateOrCreate([ 
                    'order_code' => $order->code, 
                    'printing_code' => $this->printingName 
                ], $orderUpdate);
            } catch (\Exception $ex) {
                OrderMatching::query()
                            ->where('order_code', '=', $order->code)
                            ->where('printing_code', '=', $this->printingName)
                            ->update($orderUpdate);
            }
            
        }

        /** tính tổng lại giá vốn, vì 1 đơn hàng có thể in ở nhiều nhà in */
        $totalOrderMatching = OrderMatching::where('order_code', '=', $order->code)
                                ->select(
                                    DB::raw('SUM(cost) AS cost'), 
                                    DB::raw('SUM(shipping_cost) AS shipping_cost'), 
                                    DB::raw('SUM(tax_cost) AS tax_cost')
                                )
                                ->groupBy('order_code')
                                ->first();

        if (!empty($totalOrderMatching->cost)) {
            $orderUpdate['cost'] = $totalOrderMatching->cost;
        }
        if (!empty($totalOrderMatching->shipping_cost)) {
            $orderUpdate['shipping_cost'] = $totalOrderMatching->shipping_cost;
        }
        if (!empty($totalOrderMatching->tax_cost)) {
            $orderUpdate['tax_cost'] = $totalOrderMatching->tax_cost;
        }

        $orderUpdate['resource_id'] = $gelatoOrder['id'];
        Order::where('id', '=', $order->id)->update($orderUpdate);

        //update order item
        if (!empty($gelatoOrder['items'])) {
            foreach ($gelatoOrder['items'] as $lineItem) {
                $itemUpdate = [];

                if (!empty($lineItem['price'])) {
                    $itemUpdate['cost'] = $lineItem['price'] / $exchangeRatio;
                }
                
                $itemUpdate['printing_code'] = $this->printingName;
                if (!empty($lineItem['itemReferenceId'])) {
                    $itemUpdate['resource_item_id'] = $lineItem['itemReferenceId'];
                }
                $itemUpdate['resource_id'] = $gelatoOrder['id'];

                $isOrderItem = false;
                $orderItems = OrderItem::query()
                                        ->where('order_id', '=', $order->id)
                                        ->get();

                $orderItemsPrint = OrderItem::query()
                                            ->where('order_id', '=', $order->id)
                                            ->where('printing_code', '=', $this->printingName)
                                            ->get();

                $oldData = null;
                if (count($orderItems) == 1) {
                    $oldData = clone $orderItems[0];
                    $orderItems[0]->update($itemUpdate);
                    $isOrderItem = true;
                }
                if (!$isOrderItem && count($orderItemsPrint) == 1) {
                    $oldData = clone $orderItemsPrint[0];
                    $orderItemsPrint[0]->update($itemUpdate);
                    $isOrderItem = true;
                }
                if (!$isOrderItem && !empty($lineItem['productUid'])) {
                    $oldData = OrderItem::query()
                        ->where('resource_item_id', '=', md5($lineItem['productUid']))
                        ->where('order_id', '=', $order->id)
                        ->first();
                    $status = OrderItem::query()
                                    ->where('resource_item_id', '=', md5($lineItem['productUid']))
                                    ->where('order_id', '=', $order->id)
                                    ->update($itemUpdate);
                    if ($status) $isOrderItem = true;
                }
                if (!$isOrderItem && !empty($lineItem['itemReferenceId']) && is_numeric($lineItem['itemReferenceId'])) {
                    $oldData = OrderItem::query()
                        ->where('resource_item_id', '=', $lineItem['itemReferenceId'])
                        ->where('order_id', '=', $order->id)
                        ->first();
                    $status = OrderItem::query()
                                    ->where('resource_item_id', '=', $lineItem['itemReferenceId'])
                                    ->where('order_id', '=', $order->id)
                                    ->update($itemUpdate);
                                    
                    if ($status) $isOrderItem = true;
                }
                
                if (isset($oldData->printing_code) && $oldData->printing_code == 'printify') {
                    \DB::table("order_matching_item")->insert([
                        "order_id" => $order->id,
                        "order_item_id" => $oldData->id,
                        "resource_id" => $oldData->resource_id,
                        "resource_item_id" => $oldData->resource_item_id,
                        "cost" => $oldData->cost,
                        "shipping_cost" => $oldData->shipping_cost,
                        "printing_code" => $oldData->printing_code,
                        "status" => "PENDING",
                        "created_at" => new \Datetime(),
                        "updated_at" => new \Datetime(),
                    ]);
                }
            }
        }

    }

    public function updateGltOrderByPrintify($id, $order) {
        $endPoint = 'orders/' . $id;
        $gelatoOrder = $this->triggerSyncRequest($this->gelatoApiUrl . $endPoint, 'GET', [], [
            'X-API-KEY: ' . $this->gelatoConfig['api_token'],
            'Content-Type: application/json'
        ]);
        $orderUpdate = [];
        $exchangeRate = DB::table('option')->where('key', '=', 'exchange.rate')->first();
        if (!empty($exchangeRate->value)) {
            $this->exchangeRate = json_decode($exchangeRate->value, true);
        }
        \Log::info("Gelato order " . json_encode($gelatoOrder));

        $exchangeRatio = 1;
        if (!empty($gelatoOrder['receipts'])) {
            $receipt = $gelatoOrder['receipts'][0];
            $exchangeRatio = $this->exchangeRate[$receipt['currency']];

            $orderUpdate['cost'] = $receipt['productsPriceInitial'] / $exchangeRatio;
            $discountShipping = 0;
            if (!empty($receipt['shippingPriceDiscount'])) {
                $discountShipping = $receipt['shippingPriceDiscount'];
            }
            $orderUpdate['shipping_cost'] = ($receipt['shippingPriceInitial'] - $discountShipping) / $exchangeRatio;
            $orderUpdate['tax_cost'] = $receipt['totalVat'] / $exchangeRatio;
        }
        if (isset($gelatoOrder['fulfillmentStatus']) && $gelatoOrder['fulfillmentStatus'] == 'canceled') {
            $orderUpdate['cost'] = 0;
            $orderUpdate['shipping_cost'] = 0;
            $orderUpdate['tax_cost'] = 0;
        }
        $printingUpdateData = $orderUpdate;

        if (!empty($orderUpdate)) {
            try {
                OrderMatching::updateOrCreate([ 
                    'order_code' => $order->code, 
                    'printing_code' => $this->printingName 
                ], $orderUpdate);
            } catch (\Exception $ex) {
                OrderMatching::query()
                            ->where('order_code', '=', $order->code)
                            ->where('printing_code', '=', $this->printingName)
                            ->update($orderUpdate);
            }
            
        }

        /** tính tổng lại giá vốn, vì 1 đơn hàng có thể in ở nhiều nhà in */
        $totalOrderMatching = OrderMatching::where('order_code', '=', $order->code)
                                ->select(
                                    DB::raw('SUM(cost) AS cost'), 
                                    DB::raw('SUM(shipping_cost) AS shipping_cost'), 
                                    DB::raw('SUM(tax_cost) AS tax_cost')
                                )
                                ->groupBy('order_code')
                                ->first();

        if (!empty($totalOrderMatching->cost)) {
            $orderUpdate['cost'] = $totalOrderMatching->cost;
        }
        if (!empty($totalOrderMatching->shipping_cost)) {
            $orderUpdate['shipping_cost'] = $totalOrderMatching->shipping_cost;
        }
        if (!empty($totalOrderMatching->tax_cost)) {
            $orderUpdate['tax_cost'] = $totalOrderMatching->tax_cost;
        }

        $orderUpdate['resource_id'] = $gelatoOrder['id'];
        Order::where('id', '=', $order->id)->update($orderUpdate);
        return $printingUpdateData;
    }

    private function _saveOrder($order, $dataUpdate, $refer = null) {
        $headers = [ 'Content-Type: application/json' ];
        $updateUrl = Config::get('sa.api_url') . '/api/update-order/' . $order->id;
        if (!empty($refer)) {
            array_push($headers, "Referal: $refer");
        }
        if (!empty($dataUpdate)) {
            $this->triggerSyncRequest($updateUrl, 'POST', $dataUpdate, $headers);
        }
    }

    public function buildTemplate()
    {
        set_time_limit(-1);

        $profiles = Profile::query()->where('type', 'gelato')->get(['product_sku_id as sku_id', 'options']);

        $templates = [];
        foreach ($profiles as $profile) {

            $skuAttr = $this->getFulfillSkuAttrs($profile->sku_id);

            $variantId = (isset($profile['options']['productUid'])) ? $profile['options']['productUid'] : null;

            $template = [
                'product_sku_id' => $profile->sku_id,
                'category_id' => $skuAttr[0],
                'color' => $skuAttr[1],
                'size' => $skuAttr[2],
                'type' => $skuAttr[3],
                'style' => $skuAttr[4],
                'product_uid' => $variantId
            ];

            \DB::table('gelato_history')->insert($template);

        }

    }

    public function gelatoProductAttrs($productUid = null)
    {
        if (!$productUid) {
            $productUid = request()->input('product_uid');
        }

        if (!$productUid) { return []; }

        $gelatoProduct = $this->sourceProductAttrs($productUid);
        $gelatoProduct['spreads'] = $this->sourcePrintSpreads($productUid);
        $gelatoProduct['productUid'] = $productUid;

        return $gelatoProduct;
    }

    public function autoFulfill(Request $request)
    {
        $responseData = [
            'status' => self::STATUS_FAIL
        ];

        $orderId = $request->input('order_id');

        if ($orderId) {

            try {

                $order = $this->findOrderDetail($orderId);

                if ($order && (!isset($order['resource_id']) || !$order['resource_id'])) {

                    $items = [];
                    $itemIds = [];
                    foreach ($order['items'] as $orderItem) {
                        $itemIds[] = $orderItem['id'];
                        $item = $this->buildItem($orderItem);
                        if ($item) {
                            $items[] = $item;
                        }
                    }

                    if (count($order['items']) == count($items)) {

                        $orderSchema = $this->orderSchema($order);

                        $orderSchema['items'] = $items;

                        $fulfillResponse = $this->fulfill($order, $orderSchema);

                        if ($fulfillResponse) {

                            $responseData = [
                                'result' => $fulfillResponse
                            ];

                            if (isset($fulfillResponse['id'])) {

                                $responseData['status'] = self::STATUS_SUCCESSFUL;

                                OrderItem::query()->whereIn('id', $itemIds)
                                    ->update([
                                        'printing_code' => 'gelato'
                                    ]);

                                // update resource_id
                                $objectOrder = Order::query()->find($order['id']);
                                $objectOrder->resource_id = $fulfillResponse['id'];
                                $objectOrder->save();

                            }

                        }

                    } else {
                        $responseData = [
                            'status' => self::STATUS_FAIL,
                            'msg' => 'Không build được đủ line items'
                        ];
                    }

                } else {
                    $msg = 'Không tìm thấy chi tiết đơn Printerval hoặc đơn đã được fulfill, kiểm tra resource_id trong đơn hàng!';
                    if (isset($order['resource_id']) && $order['resource_id']) {
                        $msg = 'Đơn fullfill trùng lặp!';
                    } elseif (!$order) {
                        $msg = 'Không tìm thấy chi tiết đơn Printerval';
                    }

                    $responseData = [
                        'status' => self::STATUS_FAIL,
                        'msg' => $msg
                    ];
                }

            } catch (\Exception $ex) {

                $errorMsg = 'Error on line '. $ex->getLine().' in '.$ex->getFile() .': '.$ex->getMessage();
                $responseData = [
                    'status' => self::STATUS_FAIL,
                    'msg' => $errorMsg
                ];

            }

        }

        return $responseData;
    }

    /**
     * @throws Exception
     */
    private function fulfill($order, $orderSchema)
    {
        // get order by orderReferenceId to check dub
        $refId = $orderSchema['orderReferenceId'];
        $gelatoRes = $this->triggerSyncRequest('https://order.gelatoapis.com/v3/orders:search', 'POST', [
            "orderReferenceId" => $refId
        ], [
            'X-API-KEY: ' . $this->gelatoConfig['api_token']
        ]);

        if (!isset($gelatoRes['orders']) || !count($gelatoRes['orders'])) {

            $gelatoRes = $this->triggerSyncRequest('https://order.gelatoapis.com/v3/orders', 'POST', $orderSchema, [
                'X-API-KEY: ' . $this->gelatoConfig['api_token']
            ]);

            $this->logFulfill([
                'order_id' => $order['id'],
                'data' => $orderSchema
            ]);

            if ($gelatoRes) return $gelatoRes;

        } else {
            throw new Exception('Duplicate order: ' . $orderSchema['orderReferenceId']);
        }

        return null;
    }

    private function logFulfill($logData)
    {
        DB::table('log')->insert([
            'actor_email' => 'system',
            'target_id' => $logData['order_id'],
            'target_type' => 'ORDER',
            'event_type' => 'GELATO_AUTO_FULFILL',
            'data' => json_encode($logData['data']),
            'created_at' => date('Y-m-d H:i:s')
        ]);
    }

    public function tempBuildTemplate()
    {
        set_time_limit(-1);

        // build template

        $gelatoH = \DB::table('gelato_history')->get();

        $templates = [];
        foreach ($gelatoH as $history) {

            if (!$history->category_id) continue;

            $templates[] = [
                'category_id' => $history->category_id,
                'color' => $history->color,
                'size' => $history->size,
                'type' => $history->type,
                'style' => $history->style,
                'enable' => 1,
                'product_uid' => $history->product_uid
            ];
        }

        if (count($templates)) {
            foreach ($templates as $template) {

                $existTemplate = TemplateGelato::query()
                    ->where('category_id', $template['category_id'])
                    ->where('color', $template['color'])
                    ->where('size', $template['size'])
                    ->where('style', $template['style'])
                    ->first();

                if (!$existTemplate) {
                    $existTemplate = new TemplateGelato();
                    $this->refreshTemplate($existTemplate, $template['product_uid']);
                } else if($existTemplate->product_uid != $template['product_uid']) {
                    $this->refreshTemplate($existTemplate, $template['product_uid']);
                }

                $existTemplate->fill($template);
                $existTemplate->save();

                \Log::info('template', $template);
            }
        }
    }

    private function sourceProductAttrs($productUid): array
    {
        $product = $this->internalCallApi('products/' . $productUid);
        return [
            'attributes' => (isset_not_empty($product['attributes'])) ? $product['attributes'] : [],
            'isStockable' => (isset_not_empty($product['isStockable'])) ? $product['isStockable'] : false
        ];
    }

    private function sourcePrintSpreads($productUid): array
    {
        $params = [
            'product_uid' => $productUid,
            'type' => 'json'
        ];
        $params = http_build_query($params);

        $gelatoResponse = $this->triggerSyncRequest('https://preflight-cdn.live.gelato.tech/product/template?' . $params, 'GET');

        $spreads = [];
        if (isset_not_empty($gelatoResponse['spreads'])) {
            foreach ($gelatoResponse['spreads'] as $spread) {
                $isValidInput = isset_not_empty($spread['name'], $spread['pages']) && isset($spread['x'], $spread['y']);
                if ($isValidInput && $spread['x'] == 0 && $spread['y'] == 0) {
                    foreach ($spread['pages'] as $page) {
                        if (isset($page['width'], $page['height'])) {
                            $spreads[$spread['name']] = (intval($page['width']) * 10) . 'x' . (intval($page['height']) * 10);
                        }
                    }
                }
            } // end foreach $gelatoResponse spreads
        }

        return $spreads;
    }

    /**
     * @throws Exception
     */
    private function buildItem($orderItem)
    {
        $lineItem = null;

       /* if ($profile && isset($profile['options']) && 0) { // Không dùng profile
            $lineItem = $this->getItemByProfile($orderItem, $profile['options']);
        }*/

        $printLocation = $this->getPrintLocation($orderItem);

        $paramsMap = [
            'category_id',
            'color',
            'size',
            'type',
            'style',
            'position',
        ];
        $input = $this->getFulfillSkuAttrs($orderItem['product_sku_id']);
        $input[] = $printLocation;

        $params = [];
        foreach ($paramsMap as $index => $key) {
            $params[$key] = $input[$index];
        }
        // request
        $ffServiceEndpoint = 'https://fulfillment.service.printerval.com/print-suppliers/template/gelato';
        $params = http_build_query($params);

        $templateGelato = $this->triggerSyncRequest($ffServiceEndpoint . '?' . $params, 'GET', [], []);

        if (isset($templateGelato['template']) && count($templateGelato['template'])) {

            $template = $templateGelato['template'];
            $design = $this->getFulfillDesign($orderItem['product_sku_id'], $template[1], $orderItem['product_id'], null, 'front');

            if ($design) {
                $lineItem = $this->getlineItemByTemplate($orderItem, $template, $design);
            } else {
                throw new Exception('Design missing: ' . $orderItem['product_sku_id']);
            }

        } else {
            throw new Exception('Template missing: ' . $orderItem['product_sku_id']);
        }

        return $lineItem;
    }

    private function getPrintLocation($orderItem) {
        $position = 'front';
        $config = $orderItem['configurations'];
        if ($config) {
            $configObj = json_decode($config);
            if ($configObj && !empty($configObj->print_location)) {
                $position = $configObj->print_location;
            } else if ($configObj && !empty($configObj->is_print_back)) {
                $position = 'back';
            }
        }
        return $position;
    }

    protected function getItemByProfile($orderItem, $profileOption) {
        $item = [
            'itemReferenceId' => $orderItem['id'],
            'productUid' => null,
            'fileUrl' => null,
            'quantity' => $orderItem['quantity'],
        ];

        $fileUrl = (isset($profileOption['fileUrl'])) ? $profileOption['fileUrl'] : null;
        if (isset($profileOption['cropParams'])) {
            $cropParams = http_build_query($profileOption['cropParams']);
            $fileUrl = $this->framedImageWorkerUrl . '?' . $cropParams;
        }

        if ($fileUrl) {
            $item['fileUrl'] = $fileUrl;
        }

        if (isset($profileOption['productUid'])) {
            $item['productUid'] = $profileOption['productUid'];
        }

        return $item;
    }

    protected function getLineItemByTemplate($orderItem, $template, $design)
    {
        return [
            'itemReferenceId' => $orderItem['id'],
            'productUid' => $template[0],
            'fileUrl' => $design->image_url,
            'quantity' => $orderItem['quantity'],
        ];
    }

    private function getTemplate($skuId)
    {
        $skuAttributes = $this->getFulfillSkuAttrs($skuId);


    }

    public function searchGelatoProduct($skuAttributes)
    {
        $locate = 'fr';
//        $catalogConfigs = Config::get("print-suppliers::gelato_$locate.catalog_map");

        $catalogConfigs = [
            '7' => [
                'uid' => 't-shirts',
                'default_filters' => [
                    "GarmentCategory" => "t-shirt",
                    "GarmentPrint" => "4-0",
                    "State" => "published",
                    "ProductStatus" => "activated"
                ]
            ]
        ];

        $productUid = null;

        if (isset($catalogConfigs[$skuAttributes[0]])) {

            $filterByCategory = $catalogConfigs[$skuAttributes[0]];

            $gelatoCatalog = $this->internalCallApi('catalogs/' . $filterByCategory['uid']);
            $gelatoFilters = $this->getGelatoFilters($gelatoCatalog['productAttributes']);

            // fill default
            foreach ($gelatoFilters as $k => $filter) {
                if (isset($filterByCategory['default_filters'][$k])) {
                    $gelatoFilters[$k] = $filterByCategory['default_filters'][$k];
                }
            }

            // fill index 1 => GarmentColor
            $mappedColor = $this->getColorMapped($skuAttributes[1], $locate); // $skuAttributes[1] as sku color
            $garmentColors = $this->gelatoAttributes($gelatoCatalog['productAttributes'], 'GarmentColor');

            $gColor = array_filter($garmentColors, function ($gColor) use ($mappedColor) { // tìm, khớp màu với bên gelato
                return strtolower($gColor['title']) == strtolower($mappedColor);
            });

            if (count($gColor)) {
                $gColor = end($gColor);
                $gelatoFilters['GarmentColor'] = $gColor['productAttributeValueUid'];
            };

            // fill index 2 => GarmentSize
            $mapSize = $skuAttributes[2]; // $skuAttributes[2] as sku size
            $garmentSizes = $this->gelatoAttributes($gelatoCatalog['productAttributes'], 'GarmentSize');

            $gSize = array_filter($garmentSizes, function ($gSize) use ($mapSize) { // tìm, khớp size với gelato
                return strtolower($gSize['productAttributeValueUid']) == strtolower($mapSize);
            });

            if (count($gSize)) {
                $gSize = end($gSize);
                $gelatoFilters['GarmentSize'] = $gSize['productAttributeValueUid'];
            };

            // fill index 3 => GarmentCut // gelato chỉ có 3 loại: kids/unisex/womens
            $typeMap = Config::get("print-suppliers::gelato_$locate.type_map");
            $gelatoFilters['GarmentCut'] = 'unisex';

            if ($typeMap) {
                $mapCut = $skuAttributes[3]; // $skuAttributes[3] as sku type
                foreach ($typeMap as $k => $type) { // tìm khớp type với gelato
                    if (strtolower($mapCut) == strtolower($k)) {
                        $gelatoFilters['GarmentCut'] = $type;
                        break;
                    }
                }
            }

            // fill index 4 => GarmentQuality + GarmentSubcategory
            $skuStyle = $skuAttributes[4];
            $skuStyle = explode(' ', $skuStyle);
            $skuStyle = array_map(function ($style) {
                return str_slug($style, '');
            }, $skuStyle);

            // GarmentQuality
            $garmentQuality = $this->gelatoAttributes($gelatoCatalog['productAttributes'], 'GarmentQuality');
            $hasClassicQuality = false;
            $garmentQualityMap = array_map(function ($quality) use (&$hasClassicQuality) {
                if ($quality['productAttributeValueUid'] == 'classic') {
                    $hasClassicQuality = true;
                }
                return [
                    'id' => $quality['productAttributeValueUid'],
                    'search' => str_slug($quality['title'], '')
                ];
            }, $garmentQuality);

            $garmentQualityId = $this->mapSearchGarment($skuStyle, $garmentQualityMap);
            if (!$garmentQualityId && $hasClassicQuality) {
                $garmentQualityId = 'classic';
            }

            if ($garmentQualityId) {
                $gelatoFilters['GarmentQuality'] = $garmentQualityId;
            }

            // GarmentSubcategory
            $garmentSubcategory = $this->gelatoAttributes($gelatoCatalog['productAttributes'], 'GarmentSubcategory');
            $garmentSubcategoryMap = array_map(function ($subCategory) {
                return [
                    'id' => $subCategory['productAttributeValueUid'],
                    'search' => str_slug($subCategory['title'], '')
                ];
            }, $garmentSubcategory);

            $garmentSubcategoryId = $this->mapSearchGarment($skuStyle, $garmentSubcategoryMap);
            if ($garmentSubcategoryId) {
                $gelatoFilters['GarmentSubcategory'] = $garmentSubcategoryId;
            }

            // search product
            $endpoint = "catalogs/{$filterByCategory['uid']}/products:search";
            foreach ($gelatoFilters as $k => $filter) {
                if ($filter == null) unset($gelatoFilters[$k]);
            }

            $products = $this->internalCallApi($endpoint, 'POST', [
                'attributeFilters' => $gelatoFilters
            ]);

            if (isset($products['products']) && count($products['products']) == 1) {
                $product = end($products['products']);
                if (isset($product['productUid'])) {
                    $productUid = $product['productUid'];
                }
            }
        }

        return $productUid;
    }

    private function mapSearchGarment($skuStyle, $garmentMap)
    {
        $returnId = null;
        $foundIds = [];

        try {

            foreach ($skuStyle as $style) {
                $foundEachWord = array_filter($garmentMap, function($quality) use ($style) {
                    return (strpos($quality['search'], $style) !== false);
                });
                $foundEachWord = array_map(function($found) {
                    return $found['id'];
                }, $foundEachWord);
                $foundIds[$style] = array_values($foundEachWord);
            }

            $foundIds = array_values($foundIds);
            $foundIds = array_filter($foundIds);

            $intersect = [];
            if (count($foundIds) > 1) {
                $intersect = array_intersect(...$foundIds);
            } else {
                $foundIds = end($foundIds);
                if (count($foundIds) == 1) {
                    $intersect = $foundIds;
                }
            }

            if (count($intersect) == 1) {
                $returnId = end($intersect);
            }

        } catch (\Exception $_) {}

        return $returnId;
    }

    private function gelatoAttributes($productAttributes, $keyId)
    {
        foreach ($productAttributes as $attribute) {
            if (isset($attribute['productAttributeUid']) && $attribute['productAttributeUid'] == $keyId) {
                return $attribute['values'];
            }
        }
        return null;
    }

    private function getGelatoFilters($productAttributes)
    {
        $filters = [];
        foreach ($productAttributes as $attribute) {
            $filters[$attribute['productAttributeUid']] = null;
        }
        return $filters;
    }

    private function getColorMapped($color, $locate)
    {
        $colorConfigs = Config::get("print-suppliers::gelato_$locate.color_map");
        if ($colorConfigs) {
            $localColors = array_keys($colorConfigs);
            $mapColors = array_values($colorConfigs);
            $foundColor = array_filter($localColors, function($localColor) use($color) {
                return strtolower($localColor) == strtolower($color);
            });

            if (!count($foundColor)) {
                $foundColor = array_filter($mapColors, function($mapColor) use($color) {
                    return strtolower($mapColor) == strtolower($color);
                });
            }

            if (count($foundColor)) {
                return end($foundColor);
            }
        }
        return $color;
    }

    private function orderSchema($orderDetail): array
    {
        $orderReferenceId = "API";
        if(env('APP_LOCALE')) {
            $orderReferenceId .= '_' . env('APP_LOCALE');
        }
        $orderReferenceId .= "_" . $orderDetail['code'];

        $fullname = $orderDetail['customer']['full_name'];
        $phone = $orderDetail['customer']['phone'];

        if (isset($orderDetail['attributes']['customer_info'])) {
            $fullname = $orderDetail['attributes']['customer_info']['full_name'];
            $phone = $orderDetail['attributes']['customer_info']['phone'];
        }

        if (isset($orderDetail['attributes']['gift_info']['name']) && $orderDetail['attributes']['gift_info']['name']) {
            $fullname = $orderDetail['attributes']['gift_info']['name'];
            $phone = $orderDetail['attributes']['gift_info']['phone'];
        }

        $customerName = getCustomerName($fullname);
        $lastName = $customerName['last_name'];
        $firstName = $customerName['first_name'];

        $gelatoRegionFile = app_path('Modules/PrintSuppliers/Resources/Assets/json/gelato_regions.json');
        if (file_exists($gelatoRegionFile) && isset($orderDetail['country']['iso'])) {
            $strJsonFileContents = file_get_contents($gelatoRegionFile);
            $gelatoRegions = json_decode($strJsonFileContents, true);
            $customerCountry = $orderDetail['country']['iso'];

            foreach ($gelatoRegions as $key => $region) {
                $iso = explode('_', $key);
                if (strtolower($orderDetail['state_name']) == strtolower($region) && strtolower($customerCountry) == array_first($iso)) {
                    $orderDetail['state_name'] = end($iso);
                    break;
                }
            }
        }

        return [
            'orderType' => 'draft',
            'orderReferenceId' => $orderReferenceId,
            'currency' => 'EUR',
            'items' => [],
            'shipmentMethodUid' => ($orderDetail['shipping_type'] == 'express' ? 'express' : 'normal'),
            'shippingAddress' => [
                'firstName' => $firstName,
                'lastName' => $lastName,
                'addressLine1' => $orderDetail['delivery_address'],
                'addressLine2' => $orderDetail['optional_address'],
                'state' => $orderDetail['state_name'],
                'city' => $orderDetail['city_name'],
                'postCode' => $orderDetail['zip_code'],
                'country' => $orderDetail['country']['iso'],
                'email' => 'info@megaads.vn',
                'phone' => $phone
            ]
        ];
    }

    public function submitProduction($orderId = null)
    {
        $responseData = [
            'status' => self::STATUS_FAIL
        ];

        if (!$orderId) {
            $orderId = request()->input('order_id');
        }

        if ($orderId) {
            $gelatoRes = $this->triggerSyncRequest('https://order.gelatoapis.com/v3/orders/' . $orderId, 'PATCH', [
                "orderType" => "order"
            ], [
                'X-API-KEY: ' . $this->gelatoConfig['api_token']
            ]);

            if (isset($gelatoRes['id'])) {
                $responseData['status'] = self::STATUS_SUCCESSFUL;
            }
            $responseData['result'] = $gelatoRes;
        }

        return $responseData;
    }

    /**
     * Save data after fulfill complete
     * */
    public function storeFulfillTemplate(Request $request)
    {
        $templates = [];
        $profiles = $request->input();

        foreach ($profiles as $profile) {

            $skuAttr = $this->getFulfillSkuAttrs($profile['product_sku_id']);

            if (isset($skuAttr[0], $skuAttr[1], $skuAttr[2], $skuAttr[3]) && $skuAttr[0] && $skuAttr[1] && $skuAttr[2] && $skuAttr[3]) {

                $enable = 1;
                if ($skuAttr[0] == 33) $enable = 0;

                $productUid = (isset($profile['options']['productUid'])) ? $profile['options']['productUid'] : null;

                $templates[] = [
                    'category_id' => $skuAttr[0],
                    'color' => $skuAttr[1],
                    'size' => $skuAttr[2],
                    'type' => $skuAttr[3],
                    'style' => $skuAttr[4],
                    'enable' => $enable,
                    'product_uid' => $productUid
                ];

            }

        }

        if (count($templates)) {
            foreach ($templates as $template) {

                $existTemplate = TemplateGelato::query()
                    ->where('category_id', $template['category_id'])
                    ->where('color', $template['color'])
                    ->where('size', $template['size'])
                    ->where('style', $template['style'])
                    ->first();

                if (!$existTemplate) {
                    $existTemplate = new TemplateGelato();
                    $this->refreshTemplate($existTemplate, $template['product_uid']);
                } else if($existTemplate->product_uid != $template['product_uid']) {
                    $this->refreshTemplate($existTemplate, $template['product_uid']);
                }

                $existTemplate->fill($template);
                $existTemplate->save();

            }
        }

        return $templates;
    }

    private function refreshTemplate(&$template, $productUid)
    {
        $productGelato = $this->gelatoProductAttrs($productUid);
        $resolution = '';
        if (isset($productGelato['spreads']['front'])) {
            $resolution = $productGelato['spreads']['front'];
        }
        $template->attributes = json_encode($productGelato['attributes']);
        $template->resolution = $resolution;
    }
}
