<?php

namespace YSquare\Shipcloud\Application\Model;

use Exception;
use OxidEsales\Eshop\Application\Model\Country;
use OxidEsales\Eshop\Application\Model\Order;
use OxidEsales\Eshop\Application\Model\OrderArticle;
use OxidEsales\Eshop\Application\Model\State;
use OxidEsales\Eshop\Core\Email;
use OxidEsales\Eshop\Core\Model\BaseModel;
use OxidEsales\Eshop\Core\Model\ListModel;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactory;
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;
use YSquare\Shipcloud\Application\Controller\Admin\AdminRestController;
use YSquare\Shipcloud\Core\Exception\ShipcloudApiException;

class Shipcloud extends BaseModel
{
    protected $adminRest;
    protected $order_articles;
    protected $shipcloudPackageArticle;
    /**
     * @var QueryBuilderFactory
     */
    protected $queryBuilderFactory;

    /**
     * Class constructor, initiates parent constructor (parent::oxBase()).
     */
    public function __construct()
    {
        parent::__construct();
        $this->init('ysshipcloud');
        $this->adminRest = oxNew(AdminRestController::class);
        $container = ContainerFactory::getInstance()->getContainer();
        $this->queryBuilderFactory = $container->get(QueryBuilderFactoryInterface::class);
    }

    public function getStatus()
    {
        if ($this->ysshipcloud__ysshipcloudtrackingnumber->value) {
            return 1;
        } elseif ($this->ysshipcloud__ysshipcloudid->value) {
            return 0;
        } else {
            return -1;
        }
    }

    public function setPackageTemplate($packageTemplate)
    {
        $this->assign(
            [
                "ysshipcloud__yspackagetemplate" => $packageTemplate['name'],
                "ysshipcloud__yspackagetype"     => $packageTemplate['type'],
                "ysshipcloud__yspackagewidth"    => $packageTemplate['width'],
                "ysshipcloud__yspackageheight"   => $packageTemplate['height'],
                "ysshipcloud__yspackagelength"   => $packageTemplate['length'],
            ]
        );
        $this->save();
    }

    public function getShipcloudPackageArticle()
    {
        if (!$this->shipcloudPackageArticle) {
            $queryBuilder = $this->queryBuilderFactory->create();

            $oxid = $this->getId();

            if (!$oxid) {
                $oxid = $this->ysshipcloud__oxid->value;
            }

            $queryBuilder
                ->select('s2oa.*')
                ->from('ysshipcloud2orderarticles', 's2oa')
                ->where('s2oa.YSSHCLID = :oxid')
                ->andWhere('s2oa.OXAMOUNT > 0')
                ->setParameter('oxid', $oxid);

            // order articles
            $this->shipcloudPackageArticle = oxNew(ListModel::class);
            $this->shipcloudPackageArticle->init(Shipcloud2Orderarticle::class);
            $this->shipcloudPackageArticle->selectString($queryBuilder->getSQL(), $queryBuilder->getParameters());
        }

        return $this->shipcloudPackageArticle;
    }

    public function deleteSelf()
    {
        $oxid = $this->getId();
        $msg = [];

        $msg[] = $this->queryBuilderFactory->create()
            ->delete('ysshipcloud2orderarticles')
            ->where('YSSHCLID=?')
            ->setParameter(0, $oxid)
            ->execute();

        $msg[] = $this->queryBuilderFactory->create()
            ->delete('ysshipcloud')
            ->where('OXID=?')
            ->setParameter(0, $oxid)
            ->execute();

        return $msg;
    }

    public function createShipment($create_shipping_label, $address_data): array
    {
        $order = $this->getOrder();

        if (
            $order->oxorder__oxdelfname->value or $order->oxorder__oxdellname->value or
            $order->oxorder__oxdelstreet->value
        ) {
            $deliveryAddressActive = true;
        } else {
            $deliveryAddressActive = false;
        }
        $mail = $order->oxorder__oxbillemail->value;

        if ($deliveryAddressActive) {
            $street = $order->oxorder__oxdelstreet->value;
            $street_no = $order->oxorder__oxdelstreetnr->value;
            $zip_code = $order->oxorder__oxdelzip->value;
            $city = $order->oxorder__oxdelcity->value;
            $country_id = $order->oxorder__oxdelcountryid->value;
            $company = $order->oxorder__oxdelcompany->value;
            $first_name = $order->oxorder__oxdelfname->value;
            $last_name = $order->oxorder__oxdellname->value;
            $care_of = $order->oxorder__oxdeladdinfo->value;
            $state_id = $order->oxorder__oxdelstateid->value;
            $phone = $order->oxorder__oxdelfon->value;
        } else {
            $street = $order->oxorder__oxbillstreet->value;
            $street_no = $order->oxorder__oxbillstreetnr->value;
            $zip_code = $order->oxorder__oxbillzip->value;
            $city = $order->oxorder__oxbillcity->value;
            $country_id = $order->oxorder__oxbillcountryid->value;
            $company = $order->oxorder__oxbillcompany->value;
            $first_name = $order->oxorder__oxbillfname->value;
            $last_name = $order->oxorder__oxbilllname->value;
            $care_of = $order->oxorder__oxbilladdinfo->value;
            $state_id = $order->oxorder__oxdelstateid->value;
            $phone = $order->oxorder__oxbillfon->value;
        }

        $oCountry = oxNew(Country::class);
        $oCountry->loadInLang(0, $country_id);
        $country = $oCountry->oxcountry__oxisoalpha2->value;

        $oState = oxNew(State::class);
        $oState->loadInLang(0, $state_id);
        $state = $oState->oxstates__oxisoalpha2->value;

        // OXID stores address fields HTML-encoded; decode before sending to Shipcloud API
        // to prevent e.g. "&amp;" appearing literally on shipping labels.
        $company    = $this->decodeHtmlEntities($company);
        $first_name = $this->decodeHtmlEntities($first_name);
        $last_name  = $this->decodeHtmlEntities($last_name);
        $care_of    = $this->decodeHtmlEntities($care_of);
        $street     = $this->decodeHtmlEntities($street);
        $city       = $this->decodeHtmlEntities($city);

        $data = [
            "from"                  => [
                "company"    => $this->decodeHtmlEntities($address_data['company'] ?? ''),
                "first_name" => $this->decodeHtmlEntities($address_data['fname'] ?? ''),
                "last_name"  => $this->decodeHtmlEntities($address_data['lname'] ?? ''),
                "care_of"    => $this->decodeHtmlEntities($address_data['addinfo'] ?? ''),
                "street"     => $this->decodeHtmlEntities($address_data['street'] ?? ''),
                "street_no"  => $address_data['streetnr'] ?? '',
                "zip_code"   => $address_data['zip'] ?? '',
                "city"       => $this->decodeHtmlEntities($address_data['city'] ?? ''),
                "country"    => $address_data['country'] ?? '',
            ],
            "to"                    => [
                "company"    => $company,
                "first_name" => $first_name,
                "last_name"  => $last_name,
                "care_of"    => $care_of,
                "street"     => $street,
                "street_no"  => $street_no,
                "zip_code"   => $zip_code,
                "city"       => $city,
                "state"      => $state,
                "country"    => $country,
                "phone"      => $phone,
            ],
            "package"               => [
                "weight" => number_format($this->ysshipcloud__yspackageweight->value, 2),
                "length" => number_format($this->ysshipcloud__yspackagelength->value, 2),
                "width"  => number_format($this->ysshipcloud__yspackagewidth->value, 2),
                "height" => number_format($this->ysshipcloud__yspackageheight->value, 2),
                "type"   => $this->ysshipcloud__yspackagetype->value,
            ],
            "carrier"               => $this->ysshipcloud__yscarrier->value,
            "service"               => $this->ysshipcloud__yscarrierservice->value,
            "reference_number"      => $this->ysshipcloud__ysreferencenumber->value,
            "description"           => $this->ysshipcloud__yspackagecontentdescription->value,
            "create_shipping_label" => $create_shipping_label,
        ];

        if ($create_shipping_label and $this->ysshipcloud__ysshipcloudsendshipcloudmail->value) {
            $data["notification_email"] = $mail;
        }

        if ($create_shipping_label && $this->ysshipcloud__ysshipcloudadvancenotice->value) {
            $data["additional_services"] = [
                [
                    "name"       => "advance_notice",
                    "properties" => [
                        "phone" => $phone,
                    ],
                ],
            ];
        }

        try {
            $data = $this->adminRest->createShipment($data, $this->ysshipcloud__ysshipcloudid->value);

            $params = [];
            $params['ysshipcloud__ysshipcloudid'] = $data['id'];
            $params['ysshipcloud__ysshipcloudtrackingnumber'] = $data['carrier_tracking_no'];
            $params['ysshipcloud__ysshipclouddeliverycosts'] = $data['price'];
            $params['ysshipcloud__ysshipcloudlabelurl'] = $data['label_url'];
            $params['ysshipcloud__ystrackingurl'] = $data['tracking_url'];

            $this->assign($params);
            $result = $this->save();

            if ($create_shipping_label and $this->ysshipcloud__ysshipcloudsendmail->value) {
                $this->sendShipmentMail();
            }

            if ($result) {
                $oShipcloud = oxNew(Shipcloud::class);
                $oShipcloud->load($result);
            }
            return ["exception" => null, "result" => $data, "log" => json_encode($data), "save" => $result];
        } catch (ShipcloudApiException $e) {
            return [
                "exception" => [
                    "code"    => $e->getCode(),
                    "log"     => json_encode($data),
                    "message" => $e->getMessage(),
                ],
            ];
        }
    }

    public function getOrder()
    {
        $order = oxNew(Order::class);
        $this->getOrderArticles();
        if (!count($this->order_articles) == 0) {
            $orderId = reset($this->order_articles->getArray())->oxorderarticles__oxorderid->value;
            $order->load($orderId);
        }
        return $order;
    }

    public function getOrderArticles()
    {
        if (!$this->order_articles or count($this->order_articles) == 0) {
            $queryBuilder = $this->queryBuilderFactory->create();

            $oxid = $this->getId();

            $queryBuilder
                ->select('oa.*')
                ->from('oxorderarticles', 'oa')
                ->innerJoin('oa', 'ysshipcloud2orderarticles', 's2oa', 'oa.OXID = s2oa.OXOARTID')
                ->where('s2oa.YSSHCLID = :oxid')
                ->setParameter('oxid', $oxid);

            // order articles
            $this->order_articles = oxNew(ListModel::class);
            $this->order_articles->init(OrderArticle::class);
            $this->order_articles->selectString($queryBuilder->getSQL(), $queryBuilder->getParameters());
        }
        return $this->order_articles;
    }

    /**
     * @param $order_articles
     * @throws Exception
     */
    public function setOrderArticles($order_articles)
    {
        foreach ($order_articles as $oxid => $order_article) {
            $shipcloud_id = $this->ysshipcloud__oxid->value;

            $shipcloud2order_article = oxNew(Shipcloud2Orderarticle::class);

            $shipcloud2order_article->assign([
                'oxid'     => substr(md5($shipcloud_id . $oxid), 0, 32),
                'ysshclid' => $shipcloud_id,
                'oxoartid' => $oxid,
                'oxamount' => $order_article->getFieldData('oxamount'),
            ]);

            $shipcloud2order_article->save();
        }
    }

    public function sendShipmentMail()
    {
        $order = $this->getOrder();

        /** @var Email $oEmail */
        $oEmail = oxNew(Email::class);
        $result = $oEmail->sendSendedShipcloudMail($order, $this);

        if ($result["send"]) {
            $this->assign(['yssendmaildate' => date("Y-m-d H:i:s", Registry::getUtilsDate()->getTime())]);
            $this->save();
        }

        return ["exception" => null, "result" => $result];
    }

    public function sendPickupRequest($pickupTime, $address_data): array
    {
        $data = [
            "carrier"        => $this->ysshipcloud__yscarrier->value,
            "pickup_time"    => [
                "earliest" => $pickupTime['from'],
                "latest"   => $pickupTime['to'],
            ],
            "pickup_address" => [
                "company"    => $this->decodeHtmlEntities($address_data['company'] ?? ''),
                "first_name" => $this->decodeHtmlEntities($address_data['fname'] ?? ''),
                "last_name"  => $this->decodeHtmlEntities($address_data['lname'] ?? ''),
                "care_of"    => $this->decodeHtmlEntities($address_data['addinfo'] ?? ''),
                "street"     => $this->decodeHtmlEntities($address_data['street'] ?? ''),
                "street_no"  => $address_data['streetnr'] ?? '',
                "zip_code"   => $address_data['zip'] ?? '',
                "city"       => $this->decodeHtmlEntities($address_data['city'] ?? ''),
                "country"    => $address_data['country'] ?? '',
                "phone"      => $address_data['phone'] ?? '',
            ],
            "shipments"      => [
                [
                    "id" => $this->ysshipcloud__ysshipcloudid->value,
                ],
            ],
        ];
        try {
            $data = $this->adminRest->createPickupRequest($data);

            $params = [];
            $params['yspickuptime'] = $pickupTime['from'] . " - " . $pickupTime['to'];

            $address_html = '<p>';
            if ($address_data['company']) {
                $address_html .= $address_data['company'] . '<br>';
            }
            if ($address_data['fname']) {
                $address_html .= $address_data['fname'] . ' ';
            }
            $address_html .= $address_data['lname'] . '<br>';
            if ($address_data['addinfo']) {
                $address_html .= $address_data['addinfo'] . '<br>';
            }
            $address_html .= $address_data['street'] . ' ' . $address_data['streetnr'] . '<br>';
            $address_html .= $address_data['zip'] . ' ' . $address_data['city'] . '<br>';
            $address_html .= $address_data['country'] . '</p>';

            $params['yspickupaddress'] = $address_html;
            $params['yspickupnumber'] = $data['carrier_pickup_number'];
            $this->assign($params);
            $result = $this->save();

            if ($result) {
                $oShipcloud = oxNew(Shipcloud::class);
                $oShipcloud->load($result);
            }
            return ["exception" => null, "result" => $data, "log" => json_encode($data), "save" => $result];
        } catch (ShipcloudApiException $e) {
            return [
                "exception" => [
                    "code"    => $e->getCode(),
                    "log"     => json_encode($data),
                    "message" => $e->getMessage(),
                ],
            ];
        }
    }

    public function deleteShipment()
    {
        try {
            $this->adminRest->delete("/v1/shipments/" . $this->ysshipcloud__ysshipcloudid->value);

            $params = [];
            $params['ysshipcloud__ysshipcloudid'] = '';
            $params['ysshipcloud__ysshipcloudtrackingnumber'] = '';
            $params['ysshipcloud__ysshipclouddeliverycosts'] = '';
            $params['ysshipcloud__ysshipcloudlabelurl'] = '';
            $params['ysshipcloud__yspickuptime'] = '';

            $this->assign($params);
            $result = $this->save();

            if ($result) {
                $oShipcloud = oxNew(Shipcloud::class);
                $oShipcloud->load($result);
            }

            return [
                "exception" => null,
                "save"      => $result,
                "debug"     => ['params' => $params, 'shipcloud' => $oShipcloud],
            ];
        } catch (ShipcloudApiException $e) {
            return ["exception" => ["code" => $e->getCode(), "message" => $e->getMessage()]];
        }
    }

    public function copy($params = [])
    {
        $shipcloud = oxNew(Shipcloud::class);
        $shipcloud->oxClone($this);
        $shipcloud->assign([
            'oxid'                      => Registry::getUtilsObject()->generateUId(),
            'ysshipcloudid'             => '',
            'ysshipcloudtrackingnumber' => '',
            'ysshipclouddeliverycosts'  => '',
            'ysshipcloudlabelurl'       => '',
            'yspickuptime'              => '',
        ]);
        $shipcloud->save();

        $order_articles = $this->getOrderArticles();
        $queryBuilder = $this->queryBuilderFactory->create();

        foreach ($order_articles as $order_article) {
            $orderArticleId = $order_article->oxorderarticles__oxid->value;
            $shipcloudId = $shipcloud->ysshipcloud__oxid->value;

            $amount = $queryBuilder->select('s2o.oxamount')
                ->from('ysshipcloud2orderarticles', 's2o')
                ->innerJoin('s2o', 'ysshipcloud', 's', 's2o.YSSHCLID = s.OXID')
                ->where('s.OXID=:oxid')
                ->andWhere('s2o.OXOARTID=:oartid')
                ->setParameter('oxid', $this->ysshipcloud__oxid->value)
                ->setParameter('oartid', $orderArticleId)
                ->setMaxResults(1)
                ->execute()
                ->fetchColumn(0);

            $s2o = oxNew(Shipcloud2Orderarticle::class);
            $s2o->assign([
                'oxid'     => substr(md5($shipcloudId . $orderArticleId), 0, 32),
                'ysshclid' => $shipcloudId,
                'oxoartid' => $orderArticleId,
                'oxamount' => $amount,
            ]);
            $s2o->save();
        }

        $shipcloud->save();
        return $shipcloud;
    }

    private function decodeHtmlEntities($value)
    {
        return html_entity_decode((string)$value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
}
