<?php

namespace KupShop\OrderingBundle\Util\Discount;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Email\GenerateCouponEmail;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Pdf\HTMLToPDF;
use KupShop\KupShopBundle\Util\System\PathFinder;
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
use Picqer\Barcode\BarcodeGeneratorSVG;
use Query\Operator;
use Symfony\Component\HttpFoundation\Response;

class DiscountGenerator
{
    use \DatabaseCommunication;
    public $alphabet;
    /** @var GenerateCouponEmail */
    private $emailService;

    public function __construct(GenerateCouponEmail $emailService)
    {
        $this->emailService = $emailService;
    }

    public function generateNewCoupons(
        $id_discount,
        $qty = 1,
        $id_order = null,
        $id_order_item = null,
        $userId = null,
        bool $activated = true,
        $price = null,
        $currency = null,
    ) {
        $result = ['count' => 0, 'coupons' => []];

        $discount = sqlFetch($this->selectSQL('discounts', ['id' => $id_discount, 'condition_type' => 'generate_coupon']));
        if ($discount && ($qty > 0)) {
            if ($id_order) {
                $qty_generated = sqlNumRows(
                    sqlQueryBuilder()
                        ->select('*')
                        ->from('discounts_coupons')
                        ->where(Operator::equals(['id_discount' => $id_discount, 'id_order_purchased' => $id_order]))
                        ->andWhere(
                            Operator::orX(
                                Operator::equals(['id_order_item' => $id_order_item]),
                                Operator::isNull('id_order_item'))
                        )->execute()
                );
                if ($qty_generated < $qty) {
                    $qty -= $qty_generated;
                } else {
                    // already generated
                    return $result;
                }
            }

            $prefix = $discount['condition_value'];

            list($date_from, $date_to) = $this->getValidDates($discount);

            $date_activated = null;
            if ($activated) {
                $now = new \DateTime();
                $date_activated = $now->format('Y-m-d H:i:s');
            }

            for ($i = 0; $i < $qty; $i++) {
                $code = $prefix.$this->generateRandomCode();
                try {
                    $res = $this->insertSQL('discounts_coupons', [
                        'id_discount' => $id_discount,
                        'code' => $code,
                        'date_from' => $date_from,
                        'date_to' => $date_to,
                        'date_activated' => $date_activated,
                        'id_order_purchased' => $id_order,
                        'id_order_item' => $id_order_item,
                        'id_user' => $userId,
                        'price' => $price,
                        'currency' => $currency?->getId(),
                        'data' => $price ? json_encode(['original_price' => "{$price} {$currency?->getSymbol()}"]) : null,
                    ]);
                    $result['coupons'][] = $code;
                } catch (UniqueConstraintViolationException) {
                    $res = 0;
                    $i--;
                }
                $result['count'] += $res;
            }
        }

        return $result;
    }

    public function generateRandomCode(int $count = 5)
    {
        if (!isset($this->alphabet)) {
            $this->alphabet = array_merge(range('A', 'H'), range('1', '9'), range('J', 'N'), range('9', '1'), range('P', 'Z'));
        }
        $a = $this->alphabet;
        shuffle($a);
        $code = '';
        $keys = array_rand($a, $count);
        foreach ($keys as $key) {
            $code .= $a[$key];
        }
        $this->alphabet = $a;

        return $code;
    }

    public function generatePDFCoupon($coupon, $discount_data)
    {
        if (!empty($discount_data['SVG'])) {
            if (($price = floatval($coupon['price'] ?? null)) && ($price > 0)) {
                $currency = Contexts::get(CurrencyContext::class)->getOrDefault($coupon['currency']);
                $price = toDecimal($price)->printValue(decimal_mark: $currency->getPriceDecimalMark());
                $price = rtrim(rtrim($price, '0'), $currency->getPriceDecimalMark()); // rm trailing zeros
                $price = "{$price} {$currency->getSymbol()}";
            }
            $svg = replacePlaceholders($discount_data['SVG'], [
                'KOD' => $coupon['code'],
                'PLATNOST' => $coupon['date_to'],
                'CAROVY_KOD' => $this->getSVGBarcode($coupon['code']),
                'CENA' => $price,
            ]);
            $pdf_absolute_url = '';
            if (!empty($discount_data['bg_img'])) {
                $pdf_absolute_url = ($discount_data['bg_img']['path'] ?? '').($discount_data['bg_img']['original'] ?? '');
                if ($pdf_absolute_url) {
                    $time = filemtime($pdf_absolute_url);
                    $pdf_absolute_url = str_replace('../data/', 'data/', $pdf_absolute_url);
                    $pdf_absolute_url = $GLOBALS['cfg']['Addr']['full'].$pdf_absolute_url.'?'.$time;
                }
            }
            // $pdf_absolute_url = 'https://dev-discounts.wpj.biz/data/coupon/2.pdf';

            $pdf = new HTMLToPDF();

            return $pdf->generateSVG($svg, $pdf_absolute_url, true);
        }

        return false;
    }

    public function getPDFResponse($id)
    {
        $coupon = sqlFetch($this->selectSQL('discounts_coupons', ['id' => $id], ['*, DATE_FORMAT(date_to, "%d.%m.%Y") AS date_to']));
        if ($coupon) {
            $discount = sqlFetch($this->selectSQL('discounts', ['id' => $coupon['id_discount'], 'condition_type' => 'generate_coupon']));
            if ($discount) {
                $discount_data = json_decode($discount['data'], true);
                $filename = $coupon['code'].'.pdf';
                $pdf = $this->generatePDFCoupon($coupon, $discount_data);
                if ($pdf) {
                    $response = new Response($pdf);
                    $response->headers->set('Content-type', 'application/pdf');
                    $response->headers->set('Content-Disposition', "inline; filename=\"{$filename}\"");

                    return $response;
                }
            }
        }

        return new Response('Chyba! Nepodařilo se vygenerovat PDF náhled, zkuste to, prosím, později.');
    }

    public function sendCouponsByEmail(\Order $order)
    {
        $email = $order->invoice_email;
        if (empty($email)) {
            return false;
        }

        $coupons = sqlQueryBuilder()->select('*')
            ->from('discounts_coupons', 'dc')
            ->andWhere(\Query\Operator::equals(['dc.id_order_purchased' => $order->id]))
            ->sendToMaster()
            ->execute()->fetchAll();
        if (empty($coupons)) {
            return;
        }
        $this->emailService->setOrder($order);
        $message = $this->emailService->getEmail();
        $sent = $this->emailService->sendEmail($message);

        $comment = 'Vygenerované poukazy: '.implode(', ', array_column($coupons, 'code'));
        $discounts = array_column($coupons, 'id_discount', 'id_discount');
        foreach ($discounts as &$discount) {
            $discount = '<a href="javascript:nw(\'OrderDiscountsCoupons\', '.$discount.', \'&flap=3&id_order='.$order->order_no.'\');">odkaz</a>';
        }
        $comment .= '<br>'.implode(', ', $discounts);
        if ($sent) {
            $comment .= "<br>Zpráva zákazníkovi odeslána na email {$email}";
        } else {
            $comment .= "<br>Nepodařilo se odeslat zprávu zákazníkovi na email {$email}";
        }

        $order->logHistory($comment);

        return $sent;
    }

    public function generateDefaultDesign($discount)
    {
        if ($ID = getVal('ID', $discount)) {
            $pathFinder = PathFinder::getService();
            $defaultBgPath = $pathFinder->enginePath('bundles/KupShop/OrderingBundle/Resources/public/discount/default.pdf');

            $discount_data = getVal('data', $discount);
            if (empty($discount_data['bg_img']) && file_exists($defaultBgPath)) {
                $path = $this->getCouponDataPath();
                $filename = $ID.'.pdf';
                $ret = copy($defaultBgPath, $path.$filename);
                if ($ret) {
                    $img = ['path' => $path, 'original' => $filename];
                    $img = $this->generatePNG($ID, $img);
                    $discount_data['bg_img'] = $img;
                }
            }
            if (empty($discount_data['SVG']) || empty($discount_data['JSON'])) {
                $cfg = Config::get();
                $url = rtrim($cfg['Addr']['print'], '/');
                $logo = $cfg['Addr']['full'].'templates/images/'.($cfg['Modules']['orders']['invoice_logo'] ?? 'logo.png');
                $json_file = $pathFinder->enginePath('bundles/KupShop/OrderingBundle/Util/Discount/default.json');
                if (file_exists($json_file)) {
                    $json = file_get_contents($json_file);
                    $json = str_replace('{WEB}', $url, $json);
                    $json = json_decode($json, true);
                    $json['objects'][7]['src'] = $logo;
                    $discount_data['JSON'] = $json;
                }
                $svg_file = $pathFinder->enginePath('bundles/KupShop/OrderingBundle/Util/Discount/default.svg');
                if (file_exists($svg_file)) {
                    $svg = file_get_contents($svg_file);
                    $svg = str_replace('{WEB}', $url, $svg);
                    $discount_data['SVG'] = $svg;
                }
            }

            return $discount_data;
        }

        return false;
    }

    public function generateCouponPhoto($id_discount, $id_product)
    {
        $discount = sqlFetch($this->selectSQL('discounts', ['id' => $id_discount, 'condition_type' => 'generate_coupon']));
        if ($discount) {
            $discount_data = json_decode($discount['data'], true);
            if (!empty($discount_data['SVG'])) {
                $code = '1234567890';
                $date_to = date('d.m.Y', time() + 86400 * 731);
                $coupon = ['id' => null, 'code' => $code, 'date_to' => $date_to];

                $pdf = $this->generatePDFCoupon($coupon, $discount_data);
                if ($pdf) {
                    $cfg = \KupShop\KupShopBundle\Config::get();
                    $filePath = realpath($cfg['Path']['data'].'tmp/');
                    $success = file_put_contents($filePath.$code.'.pdf', $pdf);

                    if ($success) {
                        $original = new \Imagick();
                        $original->setResolution(96, 96);
                        $original->setColorspace(\Imagick::COLORSPACE_SRGB);
                        $original->readImage(realpath($filePath.$code.'.pdf'));
                        $tmpname = $filePath.$code.'.png';
                        $original->setImageFormat('png');
                        $original->stripImage();
                        if ($original->writeImage($tmpname)) {
                            $filePath = $tmpname;
                        } else {
                            $success = false;
                        }
                    }

                    if ($success) {
                        $img = new \Photos('product');
                        $img->newImage();

                        $fileName = 'poukaz'.$code.'.png';
                        $fileName = "{$cfg['Path']['photos']}".$fileName;
                        $fileName = '../'.$fileName;

                        // simulujeme upload obrazku
                        $file = [
                            'name' => $fileName,
                            'tmp_name' => $filePath,
                        ];

                        // uploadovat velky obrazek
                        $img->uploadImage($file, false);
                        $img->insertImageIntoDB();

                        // ID nove fotky
                        $img->insertProductRelation($img->getID(), $id_product, 'Y', 'Y');
                        $success = true;
                    }

                    return $success;
                } else {
                    return translate('PDFcouponError', 'OrderDiscountsCoupons');
                }
            } else {
                return translate('PDFcouponError', 'OrderDiscountsCoupons');
            }
        }

        return false;
    }

    /**
     * @param $img array
     *
     * @return array
     */
    public function generatePNG($ID, $img = [])
    {
        $filename = ($img['path'] ?? '').($img['original'] ?? '');
        if ($filename && file_exists($filename)) {
            $original = new \Imagick();
            $original->setResolution(96, 96);
            $original->setColorspace(\Imagick::COLORSPACE_SRGB);
            $original->readImage(realpath($filename));
            $original_size = $original->getImageGeometry();
            $img['size'] = $original_size;
            $filename = $ID.'.png';
            $original->setImageFormat('png');
            $original->stripImage();
            if ($original->writeImage(realpath($img['path']).'/'.$filename)) {
                $img['png'] = $filename.'?'.time();
            }
        } else {
            $img = [];
        }

        return $img;
    }

    public function getCouponDataPath()
    {
        $path = $GLOBALS['cfg']['Path']['data'].'coupon/';

        return $path;
    }

    private function getSVGBarcode(string $coupon): string
    {
        $generator = new BarcodeGeneratorSVG();
        $barcode = $generator->getBarcode($coupon, $generator::TYPE_CODE_128, 2, 30);

        $xml = new \SimpleXMLElement($barcode);
        $barcodeWidth = $xml->attributes()['width'] ?? 0;
        $barcodeHeight = $xml->attributes()['height'] ?? 0;

        $xml->addAttribute('x', ($barcodeWidth / 2) * (-1));
        $xml->addAttribute('y', ($barcodeHeight / 2) * (-1));

        $dom = dom_import_simplexml($xml);

        // oriznu <?xml ...> hlavicku, abych to mohl vlozit do jiz existujiciho SVG
        return $dom->ownerDocument->saveXML($dom->ownerDocument->documentElement);
    }

    public function getCouponsAndActivate(int $id_discount, int $qty, $id_order = null, $id_order_item = null, $activate = true): array
    {
        assert($qty > 0);
        $result = ['count' => 0, 'coupons' => []];
        sqlGetConnection()->transactional(
            function () use ($id_discount, $qty, $id_order, $id_order_item, &$result, $activate) {
                foreach (sqlQueryBuilder()->select('dc.id, dc.code, d.data')
                             ->from('discounts_coupons', 'dc')
                             ->leftJoin('dc', 'discounts', 'd', 'dc.id_discount = d.id')
                             ->where(Operator::equals(['id_discount' => $id_discount]))
                             ->andWhere(Operator::isNull('dc.date_activated'))
                             ->andWhere(Operator::orX(Operator::equals(['id_order_item' => $id_order_item]), Operator::isNull('id_order_item')))
                             ->setMaxResults($qty)
                             ->orderBy('dc.id', 'ASC')
                             ->forUpdate()
                             ->execute() as $coupon) {
                    $qb = sqlQueryBuilder()->update('discounts_coupons')
                        ->where(Operator::equals(['id' => $coupon['id']]));

                    if ($activate) {
                        $qb->set('date_activated', 'NOW()');
                    }

                    list($date_from, $date_to) = $this->getValidDates($coupon);

                    $qb->directValues(['date_from' => $date_from]);

                    if ($date_to) {
                        $qb->directValues(['date_to' => $date_to]);
                    }

                    if (!empty($id_order)) {
                        $qb->directValues(['id_order_purchased' => $id_order]);
                    }
                    if (!empty($id_order_item)) {
                        $qb->directValues(['id_order_item' => $id_order_item]);
                    }
                    $qb->execute();
                    $result['coupons'][] = $coupon['code'];
                    $result['count']++;
                }
            });

        return $result;
    }

    public function getValidDates($discount): array
    {
        $discount['data'] = json_decode($discount['data'], true);

        $valid_months = getVal('valid_months', $discount['data']);
        $valid_days = getVal('valid_days', $discount['data']);

        if ($valid_months) {
            $now = new \DateTime();
            $date_from = $now->format('Y-m-d H:i:s');
            // Zaokrouhlit na konec mesice
            $date1 = new \DateTime('first day of this month');
            $date_to = $date1->add(\DateInterval::createFromDateString(($valid_months + 1).' months -1 days'))->format('Y-m-d 23:59:59');
        } elseif ($valid_days) {
            $now = new \DateTime();
            $date_from = $now->format('Y-m-d H:i:s');

            $date1 = new \DateTime();
            $date_to = $date1->add(\DateInterval::createFromDateString($valid_days.' days'))->format('Y-m-d 23:59:59');
        } else {
            $date_from = getVal('date_from', $discount);
            $date_to = getVal('date_to', $discount);
        }

        return [$date_from, $date_to];
    }

    /**
     * @return mixed
     */
    public function generateCoupons(\Order $order, bool $activated = true): int
    {
        $generated = 0;
        $currency = Contexts::get(CurrencyContext::class)->getOrDefault($order->getCurrency());
        foreach ($order->fetchItems() as $item) {
            if (!empty($item['product'])) {
                $data = $item['product']->getData();
                if (!empty($data['generate_coupon']) && ($data['generate_coupon'] == 'Y') && !empty($data['generate_coupon_discount'])
                    && ($item['pieces'] > 0)) {
                    $couponResult = $this->getCouponsAndActivate($data['generate_coupon_discount'],
                        $item['pieces'],
                        $order->id,
                        $item['id'],
                        $activated);

                    if ($item['pieces'] > $couponResult['count']) {
                        $couponResultNew = $this->generateNewCoupons($data['generate_coupon_discount'],
                            $item['pieces'],
                            $order->id,
                            $item['id'],
                            null,
                            $activated,
                            $item['piece_price']['value_with_vat'],
                            $currency
                        );
                        $couponResult['coupons'] = array_merge($couponResultNew['coupons'], $couponResult['coupons']);
                        $couponResult['count'] += $couponResultNew['count'];
                    }
                    $generated += $couponResult['count'];

                    $orderItemInfo = ServiceContainer::getService(OrderItemInfo::class);
                    $orderItemInfo->setCustomDataItem($item, 'created_coupons', $couponResult['coupons']);
                }
            }
        }

        return $generated;
    }
}
