<?php

namespace App\Service;

use Doctrine\Common\Cache\SQLite3Cache;
use SQLite3;
use stdClass;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Contracts\Cache\ItemInterface;

class Booker
{
    private $cache;
    private $bookerApiUrl;
    private $bookerApiPubkey;
    private $bookerApiPrivkey;
    private $sectionsHierarchy;
    private $bookerClass;
    private $expRetryFactor = 1;

    public function __construct($bookerApiUrl, $bookerApiPubkey, $bookerApiPrivkey, $bookerClass, $sectionsHierarchy, $appEnv)
    {
        $provider = new SQLite3Cache(new SQLite3(__DIR__ . '/../../var/cache/WScache.sqlite'), 'WScache');
        $this->cache = new DoctrineAdapter($provider, '', $appEnv == 'dev' ? 1 : 1800);
        $this->bookerApiUrl = $bookerApiUrl;
        $this->bookerApiPubkey = $bookerApiPubkey;
        $this->bookerApiPrivkey = $bookerApiPrivkey;
        $this->sectionsHierarchy = $sectionsHierarchy;
        $this->bookerClass = $bookerClass;
    }

    private function generateRandomHash()
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < 30; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }

    private function execCurl($soapBody)
    {
        $clearHash = $this->generateRandomHash();
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $this->bookerApiUrl);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain'));
        curl_setopt($curl, CURLOPT_POSTFIELDS, '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><authenticate><publickey>' . $this->bookerApiPubkey . '</publickey><hash>' . hash_hmac('md5', $clearHash, $this->bookerApiPrivkey) . '</hash><clearhash>' . $clearHash . '</clearhash><service>webaccess</service></authenticate></soap:Header><soap:Body><' . $this->bookerClass . '>' . $soapBody . '</' . $this->bookerClass . '></soap:Body></soap:Envelope>');
        $utime = sprintf('%.4f', microtime(TRUE));
        $raw_time = \DateTime::createFromFormat('U.u', $utime);
        $now = $raw_time->format('Y-m-d H:i:s.u');
        error_log('Starting booker call ' . $clearHash . ' started @ ' . $now);
        $return = curl_exec($curl);
        $utime = sprintf('%.4f', microtime(TRUE));
        $raw_time = \DateTime::createFromFormat('U.u', $utime);
        $now = $raw_time->format('Y-m-d H:i:s.u');
        error_log('Starting booker call ' . $clearHash . ' started @ ' . $now);
        if (curl_errno($curl)) {
            $errorMsg = curl_error($curl);
        }
        curl_close($curl);
        if (isset($errorMsg)) {
            error_log('##API ERROR##');
            throw new \Exception($errorMsg);
        }
        if (is_bool($return)) {
            error_log('##API ERROR##');
            throw new \Exception('Json ERROR');
        }
        $returnJsonDecoded = $this->jsonDecodeResponse($return);
        return $returnJsonDecoded;
    }

    private function reallyGetNews()
    {
        return $this->cache->get(__FUNCTION__, function (ItemInterface $item) {
            $return = $this->execCurl('<action>getNews</action><server>slave</server><arg><it1>1</it1><ist2></ist2><ist3></ist3><ist4></ist4><ist5></ist5><ist6></ist6><ist7></ist7><ist8></ist8><ist9></ist9><ist10></ist10><ist11></ist11></arg>');
            $return2 = [];
            foreach ($return as $key => $n) {
                // checking date publication
                $date = $n->date_publication;
                $today = new \DateTime();
                $today = $today->format('Y-m-d');
                if ($today < $date) {
                    continue;
                }

                // creating model object if it doesnt exists or grabbing the old one
                // and storing it in $m
                if (!isset($return2[$n->num_news])) {
                    $m = new stdClass();
                    $m->slides = [];
                } else {
                    $m = $return2[$n->num_news];
                }

                // grabbing news title if not already set
                if (!isset($m->title)) {
                    $m->titre = $n->titre_news;
                }
                if (!isset($m->subTitle)) {
                    $m->sousTitre = $n->sous_titre_news;
                }

                if (isset($n->Prenom) && isset($n->ModelNom)) {
                    $m->modelName = strtoupper($n->Prenom . ' ' . $n->ModelNom);
                }
                if (isset($n->num_model)) {
                    $m->modelId = $n->num_model;
                }

                $slide = new stdClass();

                $slideIncomplete = false;

                switch ($n->num_layout) {
                    case 1:
                        if (empty($n->image_1)) {
                            $slideIncomplete = true;
                        }
                        $slide->type = "image_text";
                        $slide->img1 = $n->image_1;
                        $slide->hd1 = $this->getHdImgName($n->image_1);
                        $slide->txt = $n->texte_1;
                        break;
                    case 2:
                        if (empty($n->image_1) || empty($n->image_2)) {
                            $slideIncomplete = true;
                        }
                        $slide->type = "image_double";
                        $slide->img1 = $n->image_1;
                        $slide->hd1 = $this->getHdImgName($n->image_1);
                        $slide->img2 = $n->image_2;
                        $slide->hd2 = $this->getHdImgName($n->image_2);
                        break;
                    case 3:
                        if (empty($n->image_1)) {
                            $slideIncomplete = true;
                        }
                        $slide->type = "image_landscape";
                        $slide->img1 = $n->image_1;
                        $slide->hd1 = $this->getHdImgName($n->image_1);
                        break;
                    case 4:
                        if (empty($n->Nom_fichier) || empty($n->Nom_fichier_vignette)) {
                            $slideIncomplete = true;
                        }
                        $slide->type = "video";
                        $slide->video = $n->Nom_fichier;
                        $slide->vignette = $n->Nom_fichier_vignette;
                        break;
                    case 5:
                        if (empty($n->texte_1)) {
                            $slideIncomplete = true;
                        }
                        $slide->type = "text";
                        $slide->txt = $n->texte_1;
                        break;
                }

                if (!$slideIncomplete) {
                    $m->slides[] = $slide;
                }

                $return2[$n->num_news] = $m;
            }

            foreach ($return2 as $key => $l) {
                if (empty($l->slides)) {
                    unset($return2[$key]);
                    continue;
                }
                foreach ($l->slides as $k => $s) {
                    if (($s->type == "image_text" || $s->type == "image_double" || $s->type == "image_landscape")) {
                        $l->thumb = $s->img1;
                        $l->thumbHd = $s->hd1;
                        break;
                    }
                    if ($s->type == "video") {
                        $l->thumb = $s->vignette;
                        $l->thumbHd = $s->vignette;
                    }
                }
            }
            return $return2;
        });
    }

    protected function getHdImgName($originalName)
    {
        return substr($originalName, 0, strlen($originalName) - 4) . '_M.JPG';
    }

    private function reallyGetAllModels()
    {
        return $this->cache->get(__FUNCTION__, function (ItemInterface $item) {
            $return = $this->execCurl('<action>getAllModels</action><server>slave</server><arg><it1>1</it1><ist2></ist2><ist3></ist3><ist4></ist4><ist5></ist5><ist6></ist6><ist7></ist7><ist8></ist8><ist9></ist9><ist10></ist10><ist11></ist11></arg>');
            $alreadySeenModels = [];
            foreach ($return as $key => $m) {
                $this->parseMeasurementsOrClothes($return[$key]->measurements);
                $this->parseMeasurementsOrClothes($return[$key]->clothes);
                $instaContainsHost = strstr($m->instagram, 'instagram.com/');
                $return[$key]->instagram = rtrim($instaContainsHost ? substr($instaContainsHost, 14) : $m->instagram, '/');
                if (in_array($m->display, array_keys($alreadySeenModels))) {
                    $alreadySeenModels[$m->display] += 1;
                } else {
                    $alreadySeenModels[$m->display] = 1;
                }
                $return[$key]->displayNb = $alreadySeenModels[$m->display];
            }
            return $return;
        });
    }

    private function reallyGetModelIdBySlugAndNb(string $slug, int $nb)
    {
        return $this->cache->get(__FUNCTION__ . '.' . $slug . '.' . $nb, function (ItemInterface $item) use ($slug, $nb) {
            $models = $this->getAllModels();
            foreach ($models as $key => $model) {
                if ($model->display == $slug && $model->displayNb == $nb) {
                    return $model->modelId;
                }
            }
            return false;
        });
    }

    private function reallyGetAllModelsByHierarchy()
    {
        // INFO : Hierarchy sections are the model attrigute "MODEL_TAGS"
        // dump($this->getAllModels());
        // die();
        return $this->cache->get(__FUNCTION__, function (ItemInterface $item) {
            $models = $this->getAllModels();
            $ret = [];
            $hier = $this->sectionsHierarchy;
            foreach ($models as $key => $model) {
                if (!empty($model->thumbnailUrl) && !empty($model->Num_sexe)) {
                    foreach ($hier as $genderName => $genderSections) {
                        if (
                            $genderName == "women" && $model->Num_sexe == 2
                            ||
                            $genderName == "men" && $model->Num_sexe == 1
                        ) {
                            foreach ($genderSections as $sectionTag => $sectionModels) {
                                if (!empty($model->{'MODEL_TAGS'})) {
                                    if (!is_array($model->{'MODEL_TAGS'})) {
                                        $model->{'MODEL_TAGS'} = array_map('intval', explode('/--/', $model->{'MODEL_TAGS'}));
                                    }
                                    if (in_array($sectionTag, $model->{'MODEL_TAGS'})) {
                                        if (!isset($hier[$genderName][$sectionTag]['models'])) {
                                            $hier[$genderName][$sectionTag]['models'] = [];
                                        }
                                        $hier[$genderName][$sectionTag]['models'][] = $model;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return $hier;
        });
    }

    private function reallyGetSectionsHierarchyWithImages()
    {
        return $this->cache->get(__FUNCTION__, function (ItemInterface $item) {
            $modelsByHierarchy = $this->getAllModelsByHierarchy();
            $hier = $this->sectionsHierarchy;
            foreach ($modelsByHierarchy as $genderName => $genderSections) {
                foreach ($genderSections as $sectionTag => $section) {
                    if (isset($section['models'])) {
                        $randModel = $section['models'][array_rand($section['models'])];
                        $hier[$genderName][$sectionTag]['thumbnailUrl'] = $randModel->modelId . '/' . substr($randModel->thumbnailUrl, 0, strlen($randModel->thumbnailUrl) - 4) . '_M.JPG';
                    }
                }
            }
            // dump($hier);die;
            return $hier;
        });
    }

    private function reallyGetModelsAndLetters($gender, $sectionSlug)
    {
        return $this->cache->get(__FUNCTION__ . '.' . $gender . '.' . $sectionSlug, function (ItemInterface $item) use ($gender, $sectionSlug) {
            $hier = $this->getAllModelsByHierarchy();
            $letters = [];

            if ($gender == 'all' && $sectionSlug == 'all') {
                $models = [];
                foreach ($hier as $genderSlug => $genderSections) {
                    foreach ($genderSections as $sectionTag => $section) {
                        if (isset($section['models'])) {
                            foreach ($section['models'] as $key => $model) {
                                $l = strtolower(substr($model->firstName, 0, 1));
                                $letters[$l] = true;
                                $models[] = $model;
                            }
                        }
                    }
                }
                return [$models, $letters];
            } else {
                foreach ($hier[$gender] as $sectionTag => $section) {
                    if ($section['slug'] == $sectionSlug) {
                        if (isset($section['models'])) {
                            foreach ($section['models'] as $key => $model) {
                                $l = strtolower(substr($model->firstName, 0, 1));
                                $letters[$l] = true;
                            }
                            return [$section['models'], $letters];
                        }
                    }
                }
            }

            return [[], []];
        });
    }

    private function getModelInfo(int $id)
    {
        $return = $this->execCurl('<action>getModelInfo</action><server>slave</server><arg><it1>' . $id . '</it1><ist2>1</ist2><ist3></ist3><ist4></ist4><ist5></ist5><ist6></ist6><ist7></ist7><ist8></ist8><ist9></ist9><ist10></ist10><ist11></ist11></arg>');
        foreach ($return[0] as $key => $m) {
            $this->parseMeasurementsOrClothes($return[0][$key]->measurements);
            $this->parseMeasurementsOrClothes($return[0][$key]->clothes);
            $return[0][$key]->{'MODEL_TAGS'} = array_map('intval', explode('/--/', $return[0][$key]->{'MODEL_TAGS'}));
            $return[0][$key]->hd = substr($return[0][$key]->thumbnailUrl, 0, strlen($return[0][$key]->thumbnailUrl) - 4) . '_M.JPG';
        }
        return $return;
    }

    private function reallyGetModelInfoAndBookAndVideos(int $id)
    {
        return $this->cache->get(__FUNCTION__ . '.' . $id, function (ItemInterface $item) use ($id) {
            $m = $this->getModelInfo($id);
            $b = [];
            $v = [];
            foreach ($m[1] as $key => $media) {
                if ($media->type === "Book") {
                    $b[] = $media;
                } else {
                    $v[] = $media;
                }
            }
            foreach ($b as $key => $img) {
                if (!empty($img->media_name)) {
                    $b[$key]->hd = substr($b[$key]->media_name, 0, strlen($b[$key]->media_name) - 4) . '_M.JPG';
                }
            }
            if (empty($m[0])) {
                throw new NotFoundHttpException("Model doesn't exist");
            }
            $m = $m[0][0];
            return [$m, $b, $v];
        });
    }

    private function reallyGetModelBook(int $id)
    {
        return $this->cache->get(__FUNCTION__ . '.' . $id, function (ItemInterface $item) use ($id) {
            $return = $this->execCurl('<action>getModelBook</action><server>slave</server><arg><it1>' . $id . '</it1><ist2>1</ist2><ist3></ist3><ist4></ist4><ist5></ist5><ist6></ist6><ist7></ist7><ist8></ist8><ist9></ist9><ist10></ist10><ist11></ist11></arg>');
            return $return;
        });
    }

    private function reallyGetModelVideos(int $id)
    {
        return $this->cache->get(__FUNCTION__ . '.' . $id, function (ItemInterface $item) use ($id) {
            $return = $this->execCurl('<action>getModelVideos</action><server>slave</server><arg><it1>' . $id . '</it1><ist2>1</ist2><ist3></ist3><ist4></ist4><ist5></ist5><ist6></ist6><ist7></ist7><ist8></ist8><ist9></ist9><ist10></ist10><ist11></ist11></arg>');
            return $return;
        });
    }

    private function jsonDecodeResponse(string $data)
    {
        $data = str_ireplace(['SOAP-ENV:', 'SOAP:'], '', $data);
        $data = str_ireplace(['ns1:', 'SOAP:'], '', $data);
        $data = new \SimpleXMLElement($data);
        $attName = $this->bookerClass . 'Response';
        $json = json_decode($data->Body->{$attName}->return);
        if (is_null($json)) {
            error_log('##API ERROR##');
            throw new \Exception('Json Error: ' . json_last_error());
        }
        return $json;
    }

    private function parseMeasurementsOrClothes(&$field)
    {
        $field = explode('/--/', $field);
        try {
            foreach ($field as $key => $m) {
                $field[$key] = explode('/-/', $field[$key]);
                array_pop($field[$key]);
            }
        } catch (\Exception $e) {
            throw new \Exception('parseMeasurementsOrClothes ERROR');
        }
    }

    private function clearCache()
    {
        @unlink(__DIR__ . '/../../var/cache/WScache.sqlite');
    }


    // get methods with trycatch
    public function getNews()
    {
        try {
            return $this->reallyGetNews();
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getAllModels()
    {
        try {
            return $this->reallyGetAllModels();
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getModelIdBySlugAndNb(string $slug, int $nb)
    {
        try {
            return $this->reallyGetModelIdBySlugAndNb($slug, $nb);
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getAllModelsByHierarchy()
    {
        try {
            return $this->reallyGetAllModelsByHierarchy();
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getSectionsHierarchyWithImages()
    {
        try {
            return $this->reallyGetSectionsHierarchyWithImages();
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getModelsAndLetters($gender, $sectionSlug)
    {
        try {
            return $this->reallyGetModelsAndLetters($gender, $sectionSlug);
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getModelInfoAndBookAndVideos(int $id)
    {
        try {
            return $this->reallyGetModelInfoAndBookAndVideos($id);
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getModelBook(int $id)
    {
        try {
            return $this->reallyGetModelBook($id);
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
    public function getModelVideos(int $id)
    {
        try {
            return $this->reallyGetModelVideos($id);
        } catch (\Throwable $th) {
            if ($this->expRetryFactor < 10) {
                $this->clearCache();
                sleep($this->expRetryFactor *= 2);
                return call_user_func_array([$this, __FUNCTION__], func_get_args());
            }
        }
    }
}
