<?php

namespace Mtc\Plugins\DeliveryCouriers\Classes;

use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\App;
use MTC\Core\Setting;
use Mtc\Shop\Order\Shipment;

/**
 * Class Courier
 *
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 */
class Courier
{
    /**
     * Cache key for service_list
     */
    const SERVICE_CACHE_KEY = 'courier_service_list';

    public static $supported_label_formats = [
        'html' => 'html',
        'epl' => 'epl',
        'clp' => 'clp',
        'zpl2' => 'zpl2',
    ];

    /**
     * Get the API url for the given endpoint
     *
     * @return string
     */
    protected static function apiLocation($endpoint)
    {
        if (DELIVERY_SERVICES_LIVE === true) {
            return 'https://www.delivery.mtcassets.com/' . $endpoint;
        }
        return 'http://www.delivery.mtcdevserver3.com/' . $endpoint;
    }

    /**
     * Get the appropriate auth token for the API
     *
     * @return mixed
     */
    protected static function getToken()
    {
        return DELIVERY_SERVICES_LIVE === true ? DELIVERY_SERVICES_TOKEN_LIVE : DELIVERY_SERVICES_TOKEN_TEST;
    }

    /**
     * Initialize site as a new Client on the Delivery API
     *
     * @param $request
     * @throws \Exception
     */
    public static function initialize($request)
    {
        $client = new Client();

        try {
            $response = $client->post(self::apiLocation('clients'), [
                'body' => $request,
            ]);
        } catch (ClientException $exception) {
            if ($exception->getCode() < 500) {
                throw new \Exception((string)$exception->getResponse()->getBody());
            }
        }

        $response_data = json_decode((string)$response->getBody());
        if ($response_data && $response_data->status === 'ok') {
            Setting::query()
                ->where('key', 'DELIVERY_SERVICES_TOKEN_LIVE')
                ->update([
                    'value' => $response_data->live_token
                ]);
            Setting::query()
                ->where('key', 'DELIVERY_SERVICES_TOKEN_TEST')
                ->update([
                    'value' => $response_data->test_token
                ]);

            Setting::export();
        } else {
            throw new \Exception(json_encode(['Failed to initialize']));
        }
    }

    /**
     * Get current details from customer config on the API
     *
     * @return mixed
     */
    public static function getCustomersConfig()
    {
        $client = new Client();

        try {
            $response = $client->post(self::apiLocation('clients/show'), [
                'form_params' => [
                    'api_token' => self::getToken()
                ],
            ]);
            return json_decode((string)$response->getBody());
        } catch (ClientException $exception) {
            error_log((string)$exception->getResponse()->getBody());
        }

    }

    /**
     * @return mixed
     */
    public static function getCourierServiceList()
    {
        $client = new Client();

        try {
            $response = $client->post(self::apiLocation('couriers'), [
                'form_params' => [
                    'api_token' => self::getToken()
                ],
            ]);
            return json_decode((string)$response->getBody());
        } catch (ClientException $exception) {
            error_log((string)$exception->getResponse()->getBody());
        }

    }

    /**
     * Save courier details
     *
     * @param $request
     */
    public static function saveCourierData($request)
    {
        CourierCollectLocation::query()->firstOrCreate([], $request['location']);

        collect($request['courier'])
            ->each(function ($courier_details, $courier_id) {
                $courier = DeliveryCourier::query()->firstOrNew([
                    'api_courier_id' => $courier_id
                ]);

                // Save details only if enabled or exists - skip over creating entries which are not necessary
                if ($courier_details['active'] == 0 && !$courier->exists) {
                    return;
                }

                $courier->fill($courier_details);
                $courier->save();
            });
    }

    /**
     * filter out available services from the full list of supported services on the API
     *
     * @param array $service_list
     */
    public static function filterAvailableServices($all_couriers)
    {
        $services = collect([]);
        self::filterAvailableCouriers($all_couriers)
            ->each(function ($courier) use ($services) {
                collect($courier->services)
                    ->each(function ($service) use ($services, $courier) {
                        $service->courier_name = $courier->name;
                        $services->push($service);
                    });
            });
        return $services;
    }

    /**
     * filter out available services from the full list of supported services on the API
     *
     * @param array $service_list
     */
    public static function filterAvailableCouriers($all_couriers)
    {
        $courier_list = DeliveryCourier::query()
            ->where('active', 1)
            ->pluck('api_courier_id')
            ->toArray();

        return collect($all_couriers)
            ->filter(function ($courier) use ($courier_list) {
                return in_array($courier->id, $courier_list);
            });

    }

    /**
     * Send the order to Courier
     *
     * @param \Order $order
     */
    public static function sendOrder(\Order $order, $extra_params = [])
    {
        try {
            /** @var DeliveryCourierOrderMap $order_map */
            $order_map = App::make(DeliveryCourierOrderMap::class);
            $order_data = $order_map->map($order);

            if (!empty($extra_params['courier'])) {
                $order_data['courier'] = $extra_params['courier'];
            }

            if (!empty($extra_params['boxes'])) {
                $order_data['packages'] = $extra_params['boxes'];
            }

            if (isset($extra_params['order_value'])) {
                $order_data['order_total'] = $extra_params['order_value'];
            }

            $order_data['api_token'] = self::getToken();

            $client = new Client();
            $response = $client->post(self::apiLocation('orders'), [
                'form_params' => $order_data,
                'connect_timeout' => 5,
            ]);
            return true;
        } catch (ClientException $exception) {
            error_log((string)$exception->getResponse()->getBody());
        } catch (\Exception $exception) {
            error_log((string)$exception->getMessage());
        }
        return false;
    }

    /**
     * Send the order to Courier
     *
     * @param \Order $order
     */
    public static function estimateOrder(\Order $order, $extra_params = [])
    {
        try {
            /** @var DeliveryCourierOrderMap $order_map */
            $order_map = App::make(DeliveryCourierOrderMap::class);
            $order_data = $order_map->map($order);

            if (!empty($extra_params['courier'])) {
                $order_data['courier'] = $extra_params['courier'];
            }

            if (!empty($extra_params['boxes'])) {
                $order_data['packages'] = $extra_params['boxes'];
            }

            $order_data['api_token'] = self::getToken();
            $order_data['delivery_cost'] = 0;

            $client = new Client();
            $response = $client->post(self::apiLocation('estimate'), [
                'body' => $order_data,
                'connect_timeout' => 5,
            ]);

            if ($response->getStatusCode() === 200) {
                return json_decode((string) $response->getBody());
            }
        } catch (ClientException $exception) {
            error_log((string)$exception->getResponse()->getBody());
        } catch (\Exception $exception) {
            error_log((string)$exception->getMessage());
        }

        return false;
    }

    /**
     * New shipment created, save as exported
     *
     * @param \Order $order
     */
    public static function createShipmentForOrder(\Order $order, $shipment_info)
    {
        return Shipment::query()
            ->create([
                'order_id' => $order->getId(),
                'service' => $shipment_info->service,
                'carrier' => $shipment_info->courier,
                'order_status_id' => $order->status,
            ]);
    }

    /**
     * Update order from web-hook entry-point
     *
     * @param $auth_token
     * @param $data
     * @return array
     */
    public static function updateOrders($auth_token, $data)
    {
        if ($auth_token !== self::getToken()) {
            error_log('Token Mismatch');
            return [
                'error' => 'Token Mismatch'
            ];
        }

        $packages = collect($data);
        if ($packages->count() > 1) {
            Shipment::query()
                ->where('order_id', $packages->first()['order_id'])
                ->delete();

            $packages->each(function ($package) {
                self::setShipment($package);
            });
        } else {
            self::setShipment($packages->first(), true);
        }

        return [
            'status' => 'ok'
        ];
    }

    /**
     * Set the
     * @param $package
     * @param bool $update
     * @return bool
     */
    protected static function setShipment($package, $update = false)
    {
        if (!$update) {
            $shipment = new Shipment();
        } else {
            $shipment = Shipment::query()
                ->firstOrNew([
                    'order_id' => $package['order_id']
                ]);
        }

        $shipment->fill([
            'order_id' => $package['order_id'],
            'carrier' => $package['courier'],
            'service' => $package['service'],
            'date' => Carbon::createFromFormat(Carbon::DEFAULT_TO_STRING_FORMAT, $package['created_at']),
            'label' => $package['label'],
            'label_format' => $package['label_format'],
            'tracking_no' => $package['tracking_no'],
        ]);
        $shipment->save();
    }

    /**
     * Update order from web-hook entry-point
     *
     * @param $auth_token
     * @param $data
     * @return array
     */
    public static function setErrorOnOrder($auth_token, $data)
    {
        if ($auth_token !== self::getToken()) {
            error_log('Token Mismatch');
            return [
                'error' => 'Token Mismatch'
            ];
        }
        Shipment::query()
            ->where('order_id', $data['id'])
            ->where('tracking_no', '')
            ->delete();

        Shipment::query()
            ->updateOrCreate([
                'order_id' => $data['id']
            ], [
                'order_id' => $data['id'],
                'shipment_data' => $data
            ]);

        return [
            'status' => 'ok'
        ];
    }

}