<?php
/**
 * @package    pixelcms_articles
 *
 * @author     christophf <your@email.com>
 * @copyright  A copyright
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @link       http://your.url.com
 */

use Joomla\Registry\Registry;

defined('_JEXEC') or die;


require_once 'BaseModel.php';

class Adapter {
    function __construct(object $data)
    {
        foreach($data as $key => $val) {

            if(property_exists($this, $key)) {
                $this->$key = $val;
            }
        }
    }
}

class PriceAdapter extends Adapter {
    public $currency;
    public $tax;
    public $special_price;
    public $gross_price;
    public $net_price;
    public $tax_type;
}

/**
 * @package     Technikboerse
 *
 * @property PriceAdapter $price_meta
 * @since version
 */
class ProductAdapter extends Adapter {
    public $id;
    public $title;
    public $price_meta;
    public $manufacturer;
    public $model;
    public $category;
    public $images;
    public $attributes;
    public $manufacturing_date;
    public $horsepower;
    public $kw;
    public $working_hours;
    public $contactpersons; // details only
    public $description; // details only
    public $type;
    public $dealer;
    public $location;
}

class PhoneNumber {
    public $area_code;
    public $country_code;
    public $call_number;

    public function  __construct(object $contact) {
        $this->country_code = $this->getCountryCode($contact->full_number);
        $this->area_code = $contact->areacode;
        $this->call_number = $contact->number;
    }

    private function getCountryCode(string $number): string
    {
        preg_match('/^\+\d*/', $number, $matches);
        return $matches[0];
    }
}

class Contact {
    public $id;
    public $description;
    public $email;
    public $phone;
    public $mobile;
    public $fax;
    public $image;


    public function  __construct(object $contact) {
        $this->id = $contact->id;
        $this->description = "$contact->first_name $contact->last_name";
        $this->email = $contact->email;

        $this->phone = ($contact->phone->full_number) ? new PhoneNumber($contact->phone) : null;
        $this->mobile = ($contact->mobile->full_number) ? new PhoneNumber($contact->mobile) : null;
        $this->fax = ($contact->fax->full_number) ? new PhoneNumber($contact->fax) : null;

        $this->image = $contact->image;
    }
}

class Category {
    public $id;
    public $name;

    public function  __construct(ProductAdapter $product) {
        $this->id = $product->category->id;
        $this->name = $product->category->name;
    }
}

class Manufacturer {
    public $id;
    public $value;

    public function  __construct(ProductAdapter $product) {
        $this->value = (string) $product->manufacturer;
        $this->id = hexdec(substr(md5($this->value), 1, 3));
    }
}

class Condition {
    public $id = 'used';

    private static $mapping = [
        'Gebrauchtmaschine' => 'used',
        'Neumaschine' => 'new',
    ];

    public function  __construct(ProductAdapter $product) {
        $this->id = static::$mapping[$product->type] ?? $this->id;
    }
}

class Workhours {
    public $unit = 'Std.';
    public $value;
    public $show;

    public function  __construct(ProductAdapter $product) {
        $this->value = (float) $product->working_hours;
        $this->show = "$this->value $this->unit";
    }
}

class Vat {
    public $value;
    public $separable;

    public function  __construct(ProductAdapter $product) {
        $this->value = (float) $product->price_meta->tax;
        $this->separable = $product->price_meta->tax_type !== 'tax_cannot_be_stated_separately';
    }
}

class Power {
    public $PS;
    public $kW;

    public function  __construct(ProductAdapter $product) {
        $this->PS = (float) $product->horsepower;
        $this->kW = (float) $product->kw ?? round($product->horsepower / 1.36);
    }
}

class Dealer {
    public $id;
    public $name;
    public $city;
    public $zip;

    public function  __construct(ProductAdapter $product) {
        $this->id = $product->dealer->id;
        $this->name = $product->dealer->company;
        $this->city = $product->location->city;
        $this->zip = $product->location->zip;
    }
}

class Price {
    public $currency = 'EUR';
    public $specialprice = false;
    public $values;

    public function __construct(ProductAdapter $product)
    {
        $this->currency = $product->price_meta->currency;
        $vat = (float) $product->price_meta->tax;

        // Handle Exclusive pricing
        if ($product->price_meta->tax_type === 'exclusive') {
            $netto = (float) ($product->price_meta->special_price) ? $product->price_meta->special_price : $product->price_meta->net_price;
            $oldprice = (float) ($product->price_meta->special_price) ? $product->price_meta->net_price : $product->price_meta->special_price;
            $brutto = round($netto * (1 + $vat));

            $has_special_price = $oldprice !== $netto && $oldprice;
        } else {
            $brutto = (float) ($product->price_meta->special_price) ? $product->price_meta->special_price : $product->price_meta->gross_price;
            $oldprice = (float) ($product->price_meta->special_price) ? $product->price_meta->gross_price : $product->price_meta->special_price;
            $netto = round($brutto / (1 + $vat));

            $has_special_price = $oldprice !== $brutto && $oldprice;
        }

        // Only products with prices get calculated
        if ($brutto > 0) {
            $this->values = (object) [
                'brutto' => $brutto,
                'netto' => $netto,
            ];

            if ($has_special_price) {
                $this->values->old = $oldprice;
                $this->specialprice = true;
            }
        }
    }
}

class Product {
    private $product;
    public $id;
    public $category;
    public $price;
    public $vat;
    public $properties; // attributes
    public $manufacturer;
    public $model;
    public $year;
    public $power; // optional
    public $workhours; // optional
    public $link;
    public $offer;
    public $images;
    public $free_text; // no
    public $contact_persons; // no
    public $condition;
    public $dealer;
    public $equipment = [];
    public $additional = [];
    public $dealer_internal_id;

    public function  __construct(ProductAdapter $product) {
        $this->product = $product;
        $this->category = new Category($product);

        $this->id = $product->id;

        $this->images = $this->getImages();

        $this->condition = new Condition($product);

        $this->dealer = new Dealer($product);

        $this->model = $product->model;
        $this->year = $product->manufacturing_date;

        $this->manufacturer = new Manufacturer($product);
        $this->price = new Price($product);
        $this->vat = new Vat($product);

        if($this->hasProperty('working_hours')) {
            $this->workhours = new Workhours($product);
        }

        if($this->hasProperty('horsepower')) {
            $this->power = new Power($product);
        }

        if ($this->hasProperty('contactpersons')) {
            $this->contact_persons = $this->getContactPersons();
        }

        if ($this->hasProperty('description')) {
            $this->free_text = $product->description;
        }

        if ($this->hasProperty('internal_id')) {
            $this->dealer_internal_id = $product->internal_id;
        }

        if ($this->hasProperty('attributes')) {
            $attributes = $product->attributes;

            natcasesort($attributes);

            foreach ($attributes as $attribute) {
                if (preg_match('/(?<label>^[^:]*):\s(?<value>.*)/', $attribute, $matches)) {
                    // Skip these attributes
                    if (in_array(trim($matches['label']), ['Leistung', 'Baujahr'])) continue;

                    $this->additional[] = new Class($matches) {
                        public string $name;
                        public string $show;

                        public function __construct($conf)
                        {
                            $this->name = trim($conf['label']);
                            $this->show = trim($conf['value']);
                        }
                    };
                } else {
                    $this->equipment[] = (string) $attribute;
                }
            }
        }

        $this->offer = $this->price->specialprice;
    }

    private function hasProperty($prop): bool
    {
        return isset($this->product->{$prop});
    }

    private function getContactPersons()
    {
        return array_map(function($contact) {
            return new Contact($contact);
        }, $this->product->contactpersons);
    }

    private function getImages(): array
    {
        return array_map(function($image) {
            return (object) ['href' => $image];
        }, $this->product->images);
    }
}

class Pixelcms_articlesModelTechnikboerse_products  extends BaseModel
{
    protected $module = 'traktorpool';
    protected $api_key;
    protected $articleId;
    protected $customer_id;
    protected $contact_persons;
    protected $password;
    protected $username;
    protected $language;
    protected $url;
    protected $ads;
    protected $allParams;
    protected $linkParams;
    protected $menuId;
    protected $filters = [];

    protected $defaults = [
        'print_btn' => 1,
        'price_style' => 0,
        'speed', 750,
        'disable_title_image' => 0,
    ];

    protected $hidden = [
        'tracktorpool_apikey',
        'tracktorpool_customer_id',
        'tracktorpool_username',
        'tracktorpool_password',
        'traktorpool_condition',
        'traktorpool_status',
    ];

    protected $visible = [];

    public function __construct(array $config = array())
    {
        $lang = JFactory::getLanguage();
        $langCode = $lang->getTag();
        $langCodeShort = substr($langCode, 0, 2);

        parent::__construct($config);

        /** @var ExtendedRegistry $params - Settings for current Menu Item */
        $params = $this->getState('params');

        $this->menuId = $params->getInt('Itemid');
        $menu = JFactory::getApplication()->getMenu()->getItem($this->menuId);

        $this->linkParams = [];
        $this->allParams = [];
        foreach ($menu->query as $key => $item) {
            if (in_array($key, $this->visible)) {
                $this->linkParams[$key] = $item;
            }
            $this->allParams[$key] = $item;
        }

        $this->api_key = $params->get('tracktorpool_apikey');
        $this->customer_id = $params->get('tracktorpool_customer_id');

        //$this->print_btn = $jinput->getInt('print_btn');
        $this->articleId = $params->getInt('articleId', null);

        $this->language = $langCode;
    }

    private function getAds()
    {
        $ads = self::getCachedAds();

        foreach ($ads as $ad) {
            $query = http_build_query(array_merge($this->linkParams, [
                'articleId' => $ad->id,
                'Itemid' => $this->menuId,
            ]));
            $ad->link = JRoute::_("index.php?$query");
        }

        usort($ads, function($b, $a) {
            if ($a->id == $b->id) {
                return 0;
            }
            return ($a->id < $b->id) ? -1 : 1;
        });

        return $ads;
    }

    private function prepareFilters()
    {
        if (($condition = $this->params->get('traktorpool_condition', null)) && $condition !== 'false') {
            $this->filters['machinetype'] = ['new' => 'n', 'used' => 'b'][$condition[0]];
        }

        // Empty filter don't proceed
        if (!$this->filters) return [];

        return implode('.', array_merge(...array_map(function($value) {
            return (is_array($value)) ? $value : [$value];
        }, array_values($this->filters))));
    }

    public function getRelatedAds()
    {
        $ads = $this->getAds();
        $ad = $this->getData();
        $relatedAds =  array_values(array_filter($ads, function ($relatedAd) use ($ad) {
            return !!($ad->category->id == $relatedAd->category->id && $ad->id != $relatedAd->id);
        }));

        self::fisherYatesShuffle($relatedAds, $ad->id);

        return $relatedAds;
    }

    private static function fisherYatesShuffle(&$items, $seed)
    {
        @mt_srand($seed);
        $items = array_values($items);
        for ($i = count($items) - 1; $i > 0; $i--)
        {
            $j = @mt_rand(0, $i);
            $tmp = $items[$i];
            $items[$i] = $items[$j];
            $items[$j] = $tmp;
        }
    }

    public function getParams()
    {
        return $this->params;
    }

    /**
     *
     * @return mixed
     *
     * @since version
     * @throws Exception
     */
    private function fetchAds()
    {
        $options = array_merge([
            'limit' => 500,
        ], $this->filters);

        if ($this->customer_id && is_numeric($this->customer_id) && $this->customer_id > 0) {
            $options['dealer_id'] = $this->customer_id;
        }

        if ($this->params['zip_code']) {
            $options['radius_zipcode'] = $this->params['zip_code'];
            // $options['radius_distance_km'] = $this->customer_id;
        }

        $response = json_decode($this->cURL("https://api.technikboerse.com/ver1/ads", $options));

        return array_map(function($product) {
            return new Product(new ProductAdapter($product));
        }, $response->data->items);

        return array_reduce($products, 'array_merge', []);
    }

    private function getCachedAds($force = false)
    {
        $cacheKey = "technikboerse.{$this->prepareFilters()}.$this->customer_id";

        if ($this->params['zip_code']) {
            $cacheKey .= '.' . $this->params['zip_code'];
            // $options['radius_distance_km'] = $this->customer_id;
        }

        $ads = $this->cache->get($cacheKey, $this->module);

        if (empty($ads) || $force) {
            $ads = $this->fetchAds();
            $this->cache->store( json_encode($ads), $cacheKey, $this->module);
        } else {
            $ads = json_decode($ads);
        }

        return $ads;
    }

    private function getDetails(int $id)
    {
        $product = json_decode($this->cURL("https://api.technikboerse.com/ver1/ads/$id"));

        if ($product->data->status === 0) {
            throw new \Exception('Maschine nicht gefunden', 404);
        }

        return new Product(new ProductAdapter($product->data));
    }

    private function getCachedDetails(int $id)
    {
        $cacheKey = "technikboerse.$id";
        $ads = $this->cache->get($cacheKey, $this->module);

        if (empty($ads)) {
            $ads = $this->getDetails($id);
            $this->cache->store( json_encode($ads), $cacheKey, $this->module);
        } else {
            $ads = json_decode($ads);
        }

        return $ads;
    }

    /**
     * @param       $url
     * @param array $parameters
     *
     * @return mixed
     *
     * @since version
     * @throws Exception
     */
    protected function cURL($url, $parameters = [])
    {
        $query = http_build_query($parameters);

        $options = [
            CURLOPT_URL => "$url?$query",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_POSTFIELDS => "",
            CURLOPT_HTTPHEADER => [
                "Accept: application/json",
                'Cache-Control: no-cache',
                "Authorization: Bearer $this->api_key"
            ],
        ];

        $ch = curl_init();
        curl_setopt_array( $ch, $options );

        try {
            $raw_response  = curl_exec( $ch );

            // validate CURL status
            if(curl_errno($ch))
                throw new Exception(curl_error($ch), 500);

            $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($raw_response, 0, $header_size);
            $body = substr($raw_response, $header_size);

            $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if ($status_code != 200) {
                throw new Exception("Response with Status Code [" . $status_code . "].", 500);
            }
        } catch(Exception $ex) {
            if ($ch != null) curl_close($ch);
            throw new Exception($ex);
        }

        if ($ch != null) curl_close($ch);

        return $body;
    }

    public function getData()
    {
        if ($this->articleId) {
            return $this->getCachedDetails($this->articleId);
        }
        return $this->getAds();
    }
}
