<?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';

/**
 * Pixelcms_articles model.
 *
 * @package  pixelcms_articles
 * @since    1.0
 */

class TraktorpoolOption {
    public int $id;
    public string $name;
    public ?string $unit;
    public string $type;
    public mixed $value;
    public string $show;
}

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

    public function  __construct(string $number) {
        preg_match('/^\+(\d{2})\s0?(15[0-9]{2}|1[567][0-9]|\d{3,5})\s?([\d\s-]+?)$/', $number, $matches);
        [, $country_code, $area_code, $call_number] = $matches;

        $this->country_code = '+' . number_format($country_code, 0, '', ' ');
        $this->area_code = $area_code;
        $this->call_number = $call_number;
    }
}

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->description ?? "$contact->first_name $contact->last_name";
        $this->email = $contact->email;

        if (isset($contact->phone_number)) {
            $this->phone = new PhoneNumber($contact->phone_number);
        }
        if (isset($contact->mobile_number)) {
            $this->mobile = new PhoneNumber($contact->mobile_number);
        }
        if (isset($contact->fax_number)) {
            $this->fax = new PhoneNumber($contact->fax_number);
        }

        if (isset($contact->image_url)) {
            $this->image = "{$contact->image_url}?width=250&quality=85";
        }
    }
}

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

    public function  __construct($dealer) {
        $this->city = $dealer->city;
        $this->zip = $dealer->postcode;
    }
}
class iTP_Num {
    public int $id;
    public string $name;
    public ?string $unit;
    /**
     * @var int|float $value
     */
    public $value;
}
class TP_Num {
    public int $id;
    public float $value;
    public string $show;
    public string $name;

    public function  __construct($attribute) {
        $this->id = $attribute->id;
        $this->name = $attribute->name;
        $this->value = $attribute->value;
        if (isset($attribute->unit)) {
            $unit = ($attribute->unit === 'h') ? 'Std.' : $attribute->unit;
            $this->show = "$this->value $unit";
        } else {
            $this->show = (string) $this->value;
        }
    }
}

class Vat {
    public float $value = 0;
    public bool $separable = false;

    public function  __construct(?float $vatrate) {
        if ($vatrate) {
            $this->value = $vatrate / 100;
            $this->separable = true;
        }
    }
}

class TPImage {
    public int $id;
    public ?string $href;

    public function __construct($image)
    {
        $this->id = $image->id;
        $this->href = $image->url;
    }
}

class Manufacturer {
    public int $id;
    public ?string $value;

    public function __construct($manufacturer)
    {
        $this->id = $manufacturer->id;
        $this->value = $manufacturer->name ?? JText::_( 'COM_PIXELCMS_OTHER' );
    }
}

class TPAttrs {
    const Workhours = 314;
    const Power = 424;
    const CuttingWidth = 372;
    const FrontWheels = 243;
    const BackWheels = 244;
    const FrontWheelsCondition = 365;
    const BackWheelsCondition = 364;
}

class Pixelcms_articlesModelTracktorpool_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 $menuId;
    protected $limit;
    protected $filters = [];

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

    protected $visible = [
        'print_btn',
        'price_style',
        'disable_title_image',
    ];

    public function __construct(array $config = array())
    {
        $this->input = $jinput = JFactory::getApplication()->input;

        $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');

        $this->api_key = $params->get('tracktorpool_apikey');
        $this->customer_id = $params->get('tracktorpool_customer_id');
        $this->username = $params->get('tracktorpool_username');
        $this->password = $params->get('tracktorpool_password');

        $this->limit = $params->getInt('limit', 500);

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

        $this->language = $langCode;
	    $this->ads = self::prepareAds();
    }

    private function prepareAds()
    {
        $ads = self::checkCache()->ads;
        $skip_contacts = false;

        foreach ($ads as $key => &$ad) {
            $ad->power = null;
            $ad->workhours = null;
            $ad->equipment = [];
            $ad->additional = [];
            $ad->cutting_width = null;
            $ad->front_wheels = (object) [];
            $ad->back_wheels = (object) [];
            $ad->free_text = $ad->description ?? "";
            $ad->dealer_internal_id = $ad->identification_id;

            if (isset($ad->location)) {
                $ad->dealer = new Dealer($ad->location);
            }

            $ad->condition = (object) [
                'id' => $ad->condition ?? 'unknown',
            ];

            $ad->manufacturer = new Manufacturer($ad->manufacturer);

            $ad->images = array_map(function ($image) {
                return new TPImage($image);
            }, $ad->images ?? []) ?? [];

            if (!$skip_contacts) {
                try {
                    $ad->contact_persons = $this->getContactPersons($ad->contact_persons);
                } catch (\Throwable $e) {
                    // If Customer Data is badly formatted this can fail.
                    // We will ignore it for all ads then.
                    $skip_contacts = true;
                }
            }


            if (isset($ad->attributes) && is_array($ad->attributes)) {
                $lookup = array_flip(array_column($ad->attributes, 'id'));

                /** @var TraktorpoolOption $option */
                foreach ($ad->attributes as $key => $option) {
                    if ($option->type === 'bool') {
                        if ($option->value) {
                            $ad->equipment[] = $option->name;
                        }
                        // We don't need to do anything else with it so go to next
                        continue;
                    }

                    // Try to convert values to nice looking values
                    if ($option->type === 'int' || $option->type === 'float') {
                        // Okay no clue why this can be null sometimes, just skip it then!
                        if ($option->value === null) {
                            continue;
                        }
                        $option = new TP_Num($option);
                    } else {
                        $option->show = $option->value;
                    }

                    switch ($option->id) {
                        case TPAttrs::Power:
                            $ad->power = (object)[
                                'kW' => $option->value,
                                'PS' => round($option->value * 1.36),
                            ];
                            break;
                        case TPAttrs::Workhours:
                            $ad->workhours = $option;
                            break;
                        case TPAttrs::CuttingWidth:
                            $ad->cutting_width = $option->value;
                            break;
                        case TPAttrs::FrontWheels:
                            $ad->front_wheels = (object) [
                                'name' => $option->name,
                                'type' => $option->value,
                                'quality' => (isset($lookup[365])) ? $ad->attributes[$lookup[365]]->value : null,
                            ];
                            break;
                        case TPAttrs::BackWheels:
                            $ad->back_wheels = (object) [
                                'name' => $option->name,
                                'type' => $option->value,
                                'quality' => (isset($lookup[364])) ? $ad->attributes[$lookup[364]]->value : null,
                            ];
                            break;
                        case TPAttrs::BackWheelsCondition:
                        case TPAttrs::FrontWheelsCondition:
                            // Skip these
                            break;
                        default:
                            $ad->additional[] = new Class($option->name, $option->show) {
                                public string $name;
                                public string $show;

                                public function __construct($label, $show)
                                {
                                    $this->name = trim($label);
                                    $this->show = trim($show);
                                }
                            };
                    }
                }
            }


            if ($ad->equipment) {
                $ad->equipment = array_unique($ad->equipment);
                asort($ad->equipment);
            }

            if ($ad->price && $ad->price->value && $ad->price->value > 0) {
                $ad->vat = new Vat($ad->price->vat);

                $ad->price->values = (object) [
                    'netto' => $ad->price->value,
                    'brutto' => $ad->price->value * ($ad->vat->value + 1),
                ];

                // TODO: Check if special prices still work
                if (isset($ad->price->specialprice) && is_float($ad->price->specialprice)) {
                    $ad->offer = true;
                    $ad->price->values->old = $ad->price->special_price;
                } else {
                    $ad->offer = false;
                }
            }

            $query = http_build_query(array_merge($this->params->toArray(), [
                'articleId' => $ad->id,
                'Itemid' => $this->menuId,
            ]));
            $ad->link = JRoute::_("index.php?$query");

            if ($ad->manufacturer->id === 0) {
                $ad->manufacturer->value = JText::_( 'COM_PIXELCMS_OTHER' );
            }
        }

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

        return $ads;
    }

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

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

        return $relatedAds;
    }

	private function getDefault($var, $default)
	{
		return (isset($var)) ? $var : $default;
    }

    private static function fisherYatesShuffle(&$items, $seed)
    {
        if (is_string($seed)) {
            mt_srand(crc32($seed));
        } else {
            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;
    }

    private function prepareFilters()
    {
        $this->filters = [
            'status' => ['all', 'active'][$this->params->getInt('traktorpool_status', 0)],
            'sort' => $this->params->get('sort', 'desc'),
            'sortby' => $this->params->get('sortby', 'date'),
        ];

        if ($min_price = $this->params->get('traktorpool_min_price', null)) {
            $this->filters['net_price_min'] = $min_price;
        }

        // Convert to array
        $condition = $this->params->get('traktorpool_condition', []);
        $condition = is_array($condition) ? $condition : [$condition];

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

	/**
	 *
	 * @return mixed
	 *
	 * @since version
	 * @throws Exception
	 */
	private function getAds()
    {
        $parameters = $this->filters;

        if ($this->limit) {
            $parameters['limit'] = $this->limit;
        }

        if ($this->params->get('traktorpool_status', 1)) {
            $parameters['status'] = 'active';
        }

    	$ads = $this->cURL("https://connect.traktorpool.de/V3/offerer/$this->customer_id/ads", $parameters);

        if (($condition = $this->params->get('traktorpool_condition', null)) && $condition !== 'false') {
            $ads->ads = array_filter($ads->ads, function ($ad) use ($condition) {
                return in_array($ad->condition, $condition);
            });
        }

        return $ads;
    }

    private function getContactPersons(?array $contacts)
    {
        if (!is_array($contacts) || count($contacts) === 0) {
            $contact = $this->getContactPerson();
            return ($contact) ? [$this->getContactPerson()] : null;
        }
        return array_map(function($person) {
            return $this->getContactPerson($person->id);
        }, $contacts);
    }

	/**
	 * @param $id
	 *
	 * @return mixed
	 *
	 * @since version
	 * @throws Exception
	 */
	private function getContactPerson($id = null)
	{
		if (isset($this->contact_persons[$id])) {
			return $this->contact_persons[$id];
		}

		if ($cachedContacts = $this->cache->get("contacts", $this->module)) {
            $this->contact_persons = unserialize($cachedContacts);
            return $this->contact_persons[$id];
        }

        $contacts = $this->cURL("https://connect.traktorpool.de/V2/offerer/$this->customer_id/contact_persons");
        $contacts = array_map(function ($contact) {
            return new Contact($contact);
        }, $contacts);

        $contactsById = array_column($contacts, null, 'id');

        $this->cache->store(serialize($contactsById), "contacts", $this->module);

		return $contactsById[$id];
	}

    private function checkCache($force = false)
    {
        $cacheKey = $this->language . $this->prepareFilters() . "limit:$this->limit";
	    $ads = $this->cache->get($cacheKey, $this->module);

	    if (empty($ads) || $force) {
		    $ads = $this->getAds();
		    $this->cache->store( serialize($ads), $cacheKey, $this->module);
	    } else {
		    $ads = unserialize($ads);
	    }

	    return $ads ?? [];
    }

    public function revalidateCache()
    {
        $this->ads = $this->checkCache(true);
    }

	/**
	 * @param       $url
	 * @param array $parameters
	 *
	 * @return mixed
	 *
	 * @since version
	 * @throws Exception
	 */
	protected function cURL($url, $parameters = [])
	{
		$parameters = array_merge([
			'api_key'=> $this->api_key,
            'format' => 'json',
			'language' => substr($this->language, 0, 2),
		], $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 => "",
		];

		$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 json_decode($body);
	}

    public function getData()
    {
        if ($this->articleId === 'rand') {
            return $this->ads[array_rand($this->ads)];
        }
        if ($this->articleId) {
            return array_reduce($this->ads, function ($carry, $article) {
                if ($article->id === (int) $this->articleId) {
                    $carry = $article;
                }
                return $carry;
            });
        }
        return $this->ads;
    }
}
