<?php
namespace Models;

class Chz{
	public $db, $f3, $sets, $web, $chz_url, $cdn_table, $logs, $base64;
	public $cash_time=6; // в часах

	public $errors=array(
		1=>'Ошибка валидации КМ',
		2=>'КМ не содержит GTIN',
		3=>'КМ не содержит серийный номер',
		4=>'КМ содержит недопустимые символы',
		5=>'Ошибка верификации крипто-подписи КМ (формат крипто-подписи не соответствует типу КМ)',
		6=>'Ошибка верификации крипто-подписи КМ (криптоподпись не валидная)',
		7=>'Ошибка верификации крипто-подписи КМ (крипто-ключ не валиден)',
		8=>'КМ не прошел верификацию в стране эмитента',
		9=>'Найденные AI в КМ не поддерживаются',
		10=>'КМ не найден в ГИС МТ',
		11=>'КМ не найден в трансгране'
	);
	
	public function __construct($db, $f3, $sets) {
		$this->cdn_table=new \DB\SQL\Mapper($db, 'chz_cdn');
		$this->db=$db;
		$this->f3=$f3;
		$this->sets=$sets;
		$this->web=\Web::instance();
		$this->chz_url='https://cdn.crpt.ru'; // Боевой
		if($this->f3->get('HOST')=='kassa.loc') {
			$this->chz_url='https://markirovka.sandbox.crptech.ru';
		}
		$this->logs=$this->f3->get('logs_model');
		$this->base64=base64_encode($this->sets['chz_offline_login'].':'.$this->sets['chz_offline_pass']);
	}

	function add_log($request, $response) {
		$txt="Запрос в Честный Знак\n\n".json_encode($request)."\n\n";
		$txt.="Ответ\n\n".json_encode($response);
		$this->logs->save_log($txt);
	}

	// Запрос CDN площадок
	function get_cdn() {
		if(!isset($this->sets['token_chz']) || !$this->sets['token_chz']) {
			return array(
				'success'=>false,
				'code'=>401,
				'check_result'=>-1,
				'txt'=>'checktoken_false_autorization'
			);
		}
		$options=array(
			'method'=>'GET',
			'header'=>array(
				'Content-Type: application/json',
				'X-API-KEY: '.$this->sets['token_chz']
			),
			'timeout'=>2
		);
		$url=$this->chz_url.'/api/v4/true-api/cdn/info';
		$res=$this->web->request($url, $options);
		$this->add_log($options, $res);
		$json=json_decode($res['body'], true);
		if(mb_strpos($res['headers'][0], '401')>0) {
			return array(
				'success'=>false,
				'code'=>'401',
				'txt'=>$json['error_message']??'checktoken_false_autorization'
			);
		}
		if(mb_strpos($res['headers'][0], '200')===false) {
			return array(
				'success'=>false,
				'code'=>$res['headers'][0]
			);
		}

		if(!is_array($json)) {
			return array(
				'success'=>false,
				'code'=>$json['code'],
				'txt'=>$json['error_message']
			);
		}

		if((int)$json['code']!=0) {
			return array(
				'success'=>false
			);
		}

		return array(
			'success'=>true,
			'hosts'=>array_column($json['hosts'], 'host')
		);
	}

	// Проверка CDN площадки
	function host_check($host) {
		$time=microtime(true);
		$options=array(
			'method'=>'GET',
			'header'=>array(
				'Content-Type: application/json',
				'X-API-KEY: '.$this->sets['token_chz']
			),
			'timeout'=>2
		);
		$url=$host.'/api/v4/true-api/cdn/health/check';
		$res=$this->web->request($url, $options);
		$this->add_log($options, $res);
		$json=json_decode($res['body'], true);

		if(mb_strpos($res['headers'][0], '401')>0) {
			return array(
				'success'=>false,
				'code'=>'401',
				'txt'=>$json['error_message']??'checktoken_false_autorization'
			);
		}

		$time=(microtime(true)-$time);
		if(mb_strpos($res['headers'][0], '200')===false) {
			return array(
				'code'=>-1,
				'host'=>$host,
				'time_request'=>$time
			);
		}
		if(!is_array($json)) {
			return array(
				'code'=>-1,
				'host'=>$host,
				'time_request'=>$time
			);
		}

		$json['host']=$host;
		$json['time_request']=$time;
		return $json;
	}

	function load_cdn($query=null, $options=null) {
		$cdn=array();
		$this->cdn_table->reset();
		$this->cdn_table->load($query, $options);
		if($this->cdn_table->dry()) return $cdn;
		do {
			$cdn[$this->cdn_table->host]=$this->cdn_table->cast();
		}
		while($this->cdn_table->skip());
		return $cdn;
	}

	function save_cdn($cdn) {
		if(!$cdn['host']) return;
		$this->cdn_table->reset();
		$this->cdn_table->load(array('host=?', $cdn['host']), array('limit'=>1));
		$this->cdn_table->copyFrom($cdn);
		$this->cdn_table->save();
	}

	// Актуализация списка площадок
	function cdn_actually($cdn) {
		$cdn_actually=$this->get_cdn();
		if($cdn_actually['success']) {
			foreach($cdn_actually['hosts'] as $host) {
				if(!isset($cdn[$host])) {
					$cdn[$host]=array(
						'host'=>$host,
						'code'=>0,
						'avgTimeMs'=>0,
						'time_request'=>0,
						'time_update'=>0
					);
					$this->save_cdn($cdn[$host]);
				}
			}
		}
		return $cdn_actually;
	}

	// проверка CDN
	function check_cdn() {
		$now=time();
		// Список загруженных площадок
		$cdn=$this->load_cdn();
		// Сколько часов назад была актуализация списка площадок
		$d=($now-$this->sets['chz_cdn_update'])/3600;

		// Если прошло больше 6 часов или нет списка площадок, то нужно запросить список площадок
		if($d>=$this->cash_time || empty($cdn)) {
			$res=$this->cdn_actually($cdn);

			if($res['success']) {
				$sql="INSERT OR REPLACE INTO `settings` (`name`, `value`) VALUES ('chz_cdn_update', ?)";
				$val=array($now);
				$this->db->exec($sql, $val);
			}
			else {
				return $res;
			}
			$cdn=$this->load_cdn();
		}
		
		if(empty($cdn)) {
			return array(
				'success'=>false
			);
		}

		// Смотрим какие площадки нужно перепроверить
		foreach ($cdn as $c) {
			if($c['time_update']<$now) {
				$res=$this->host_check($c['host']);
				if($res['code']<0) {
					$res['time_update']=$now;
				}
				elseif($res['code']==429 || $res['code']>=500) {
					$res['time_update']=$now+900;
				}
				else {
					$res['time_update']=$now+3600*$this->cash_time;
				}
				$this->save_cdn($res);
			}
		}

		return array(
			'success'=>true
		);
	}

	// Запрос проверки марки
	function check_mark_code($host, $mark) {
		$mark=str_replace($this->sets['gs1_symbols'], '', $mark);
		$mark=str_replace('\u001d', '', $mark);
		$mark=trim($mark);

		$vars=array(
			'codes'=>array($mark)
		);
		$options=array(
			'method'=>'POST',
			'header'=>array(
				'Content-Type: application/json',
				'X-API-KEY: '.$this->sets['token_chz']
			),
			'content'=>json_encode($vars),
			'timeout'=>2
		);

		$url=$host.'/api/v4/true-api/codes/check';
		$res=$this->web->request($url, $options);
		$json=json_decode($res['body'], true);
		if(isset($json['codes'][0]['cis'])) {
			$json['codes'][0]['cis']=htmlentities($json['codes'][0]['cis']);
			$json['codes'][0]['printView']=htmlentities($json['codes'][0]['printView']);
			$json['codes'][0]['message']=htmlentities($json['codes'][0]['message']);
			$res['body']=json_encode($json);
		}
		$vars['codes']=array(htmlentities($mark));
		$options['content']=json_encode($vars);

		$this->add_log($options, $res);

		if(mb_strpos($res['headers'][0], '401')>0) {
			return array(
				'success'=>false,
				'code'=>'401',
				'txt'=>$json['error_message']??'checktoken_false_autorization'
			);
		}

		if(mb_strpos($res['headers'][0], '200')===false) {
			if(is_array($json)) {
				return $json;
			}
			return array(
				'code'=>-1,
			);
		}

		if(!is_array($json)) {
			return array(
				'code'=>-1,
			);
		}

		return $json;
	}

	// Инициализация ЛМ ЧЗ
	function lm_init() {
		$vars=array(
			'token'=>$this->sets['token_chz']
		);
		$options=array(
			'method'=>'POST',
			'header'=>array(
				'Content-Type: application/json',
				'Authorization: Basic '.$this->base64
			),
			'content'=>json_encode($vars),
			'timeout'=>30
		);

		$url=$this->sets['chz_offline_url'].'/api/v1/init';
		$res=$this->web->request($url, $options);
		if(mb_strpos($res['headers'][0], '200')>0) {
			return array(
				'success'=>true
			);
		}
		$json=json_decode($res['body'], true);
		if(is_array($json) && isset($json['reason'])) {
			$txt=$json['reason'];
		}
		else {
			$txt='Ошибка инициализации в ЛМ ЧЗ';
		}
		return array(
			'success'=>false,
			'txt'=>$txt
		);
	}

	// Статус ЛМ ЧЗ
	function lm_status() {
		$options=array(
			'method'=>'GET',
			'header'=>array(
				'Content-Type: application/json',
				'Authorization: Basic '.$this->base64
			),
			'timeout'=>5
		);

		$url=$this->sets['chz_offline_url'].'/api/v1/status';
		$res=$this->web->request($url, $options);
		if(mb_strpos($res['headers'][0], '200')>0) {
			$json=json_decode($res['body'], true);
			$json['success']=true;
			return $json;
		}
		return array(
			'success'=>false,
			'txt'=>'Онлайн проверка кода маркировки не удалась - проверьте ваше интернет подключение.<br>Офлайн проверка не удалась - проверьте работу Локального Модуля Честного Знака и его настройки.<br>Без проверки продажа запрещена.'
		);
	}

	function lm_check($mark, $line) {
		$mark=str_replace($this->sets['gs1_symbols'], '\u001d', $mark);
		$mark=str_replace('', '\u001d', $mark);
		$mark=trim($mark);
		if(mb_substr($mark, 0, 2)=='01' && mb_substr($mark, 16, 2)=='21' && ($line['marking_group']==3 || $line['marking_group']==12)) {
			$mark=mb_substr($mark, 0, 21);
		}
		else {
			$m=explode('\u001d', $mark);
			if(count($m)>1) {
				$mark=$m[0];
			}
			else {
				$mark=mb_substr($mark, 0, 21);
			}
		}
		$vars=array(
			'cis_list'=>array($mark)
		);
		$options=array(
			'method'=>'POST',
			'header'=>array(
				'Content-Type: application/json',
				'Authorization: Basic '.$this->base64
			),
			'content'=>json_encode($vars),
			'timeout'=>5
		);

		$url=$this->sets['chz_offline_url'].'/api/v1/cis/outCheck';
		$res=$this->web->request($url, $options);
		$this->add_log($options, $res);
		$json=json_decode($res['body'], true);
		if(mb_strpos($res['headers'][0], '200')>0) {
			return $json;
		}
		if(isset($json['reason'])) {
			return array(
				'success'=>false,
				'description'=>$json['reason'],
				'code'=>$json['code']??-1
			);
		}
		return array(
			'success'=>false,
			'code'=>-1,
			'description'=>'Ошибка проверки кода маркировки ЛМ ЧЗ'
		);
	}

	// Оффлайн проверка
	function check_mark_offline($line, $mark, $n=0) {
		// Узнаем статутс ЛМ ЧЗ
		$res=$this->lm_status();
		if(!$res['success']) {
			$this->add_log($options, $res);
			return $res;
		}

		if($res['status']!='ready') {
			$txt=array(
				'not_configured'=>'Статус ЛМ ЧЗ: требуется инициализация. Перейдите в <a href="/settings/marking">настройки</a> и проведите проверку ЛМ ЧЗ.',
				'initialization'=>'Статус ЛМ ЧЗ: идет инициализация. Это может занять какое-то время. Попробуйте позже.',
				'sync_error'=>'Статус ЛМ ЧЗ: ошибка синхронизации. Дождитесь восстановления синхронизации.',
			);
			$txt='Статус ЛМ ЧЗ: '.$txt[$res['status']];
			$this->add_log($options, $res);
			return array(
				'success'=>false,
				'txt'=>$txt
			);
		}
		$res=$this->lm_check($mark, $line);
		if(isset($res['results'])) $res=$res['results'][0];
		return $this->check_mark_result($res, $line);

	}

	function check_mark_result($res, $line) {
		$now=time();
		$success=true;

		if($res['code']!=0) {
			$success=false;
			$txt=($res['description']??'error').'<br>';
		}
		$mark_res=$res['codes'][0];

		if($mark_res['errorCode']==8 && $mark_res['realizable']===false && $mark_res['grayZone']===true) $mark_res['errorCode']=0;

		if($mark_res['errorCode']>0) {
			$success=false;
			$txt=$this->errors[$mark_res['errorCode']]??$mark_res['message'];
		}
		else {
			if($mark_res['found']===false) {
				$success=false;
				$txt.='Код идентификации не найден в ГИС МТ<br>';
			}

			if($mark_res['utilised']===false) {
				$success=false;
				$txt.='Код маркировки эмитирован, но нет информации о его нанесении<br>';
			}

			if($mark_res['verified']===false) {
				$success=false;
				$txt.='Не пройдена криптографическая проверка кода маркировки<br>';
			}

			if($mark_res['sold']===true) {
				$success=false;
				$txt.='Код идентификации выведен из оборота<br>';
			}

			if($mark_res['isBlocked']===true) {
				$success=false;
				$txt.='Код идентификации заблокирован по решению ОГВ<br>';
			}

			if($mark_res['realizable']===false) {
				if($mark_res['grayZone']!==true || ($line['marking_group']!=3 && $line['marking_group']!=12)) {
					$success=false;
					$txt.='Нет информации о вводе в оборот кода идентификации<br>';
				}
			}

			// #TODO Тут есть косяк с 32-битными системами
			if(isset($mark_res['expireDate']) && strtotime($mark_res['expireDate'])>0 && strtotime($mark_res['expireDate'])<=$now) {
				$success=false;
				$txt.='Товар с истекшим сроком годности.<br>Текущая дата '.date('d.m.Y H:i:s', $now).'. Срок годности до '.date('d.m.Y H:i:s', strtotime($mark_res['expireDate'])).'.<br>';
			}
			if(isset($mark_res['soldUnitCount']) && isset($mark_res['innerUnitCount']) && $line['marking_group']==16) {
				if($mark_res['soldUnitCount']>$mark_res['innerUnitCount']) {
					$success=false;
					$txt.='По данным Честного Знака данной номенклатуры было продано больше('.$mark_res['soldUnitCount'].'), чем содержится в упаковке('.$mark_res['innerUnitCount'].')';
				}
			}
		}

		if(isset($mark_res['smp']) || isset($mark_res['mrp'])) {
			$price=$line['price']+($line['price']*($line['vat']/100))*max(0, $line['vat_type']);
			if(isset($mark_res['smp']) && $mark_res['smp']>0) {
				//$success=false;
				//$txt='Продажа ниже минимальной цены. Минимальная цена '.number_format($mark_res['smp']/100, 2, '.', '');
				if($line['vat_type']>0) {
					$mark_res['set_price']=($mark_res['smp']/(100+$line['vat'])*100)/100;
				}
				else {
					$mark_res['set_price']=$mark_res['smp']/100;
				}
			}

			if(isset($mark_res['mrp']) && $mark_res['mrp']>0) {
				//$success=false;
				//$txt='Продажа выше максимальной цены. Максимальная цена '.number_format($mark_res['mrp']/100, 2, '.', '');
				if($line['vat_type']>0) {
					$mark_res['set_price']=($mark_res['mrp']/(100+$line['vat'])*100)/100;
				}
				else {
					$mark_res['set_price']=$mark_res['mrp']/100;
				}
			}
		}

		if($success===false) {
			return array(
				'success'=>false,
				'check_result'=>-1,
				'error_type'=>'chz',
				'txt'=>$txt,
				'result'=>$mark_res,
				'reqId'=>$res['reqId'],
				'reqTimestamp'=>$res['reqTimestamp'],
				'inst'=>$res['inst']??'',
				'version'=>$res['version']??''
			);
		}

		return array(
			'success'=>true,
			'check_result'=>1,
			'result'=>$mark_res,
			'reqId'=>$res['reqId'],
			'reqTimestamp'=>$res['reqTimestamp'],
			'inst'=>$res['inst']??'',
			'version'=>$res['version']??''
		);
	}

	// Проверка марки
	function check_mark($line, $mark) {
		$now=time();
		if($this->sets['chz_alarm']>$now) {
			return array(
				'success'=>true,
				'check_result'=>0,
			);
		}

		if(!isset($this->sets['token_chz']) || !$this->sets['token_chz']) {
			return array(
				'success'=>false,
				'code'=>401,
				'check_result'=>-1,
				'txt'=>'checktoken_false_autorization'
			);
		}
		// Проверяем площадки
		$res=$this->check_cdn();
		if((int)$res['code']==401) {
			if((int)$this->sets['chz_offline']==1) {
				return $this->check_mark_offline($line, $mark);
			}

			$res['check_result']=-1;
			return $res;
		}
		// Получаем список доступных площадок, начиная от самой близкой
		$cdn=$this->load_cdn(array('code=0'), array('order'=>'time_request ASC', 'limit'=>1));

		if(empty($cdn)) {
			$this->add_log(array('CDN'), array('CDN empty'));

			if((int)$this->sets['chz_offline']==1) {
				return $this->check_mark_offline($line, $mark);
			}

			return array(
				'code'=>-1,
			);
		}

		// проверяем марку. Если площадка недоступна, то пробуем на других
		$c=array_pop($cdn);
		$res=$this->check_mark_code($c['host'], $mark);
		if((int)$res['code']==401) {
			if((int)$this->sets['chz_offline']==1) {
				return $this->check_mark_offline($line, $mark);
			}

			$res['check_result']=-1;
			return $res;
		}
		if($res['code']!=0) {
			$c['code']=$res['code'];
			$c['time_request']=$now;
			$c['time_update']=$now+900;
			$this->save_cdn($c);
		}
		if($res['code']<0 || $res['code']>=500 || $res['code']==203) {
			if($res['code']!=203) {
				$this->db->exec("DELETE FROM chz_cdn WHERE host=?", $c['host']);

				if((int)$this->sets['chz_offline']==1) {
					return $this->check_mark_offline($line, $mark);
				}
			}
			else {
				$sql="INSERT OR REPLACE INTO `settings` (`name`, `value`) VALUES (?, ?)";
				$val=array(
					'chz_alarm',
					$now+24*3600
				);
				$this->db->exec($sql, $val);
			}

			return array(
				'success'=>true,
				'check_result'=>0,
			);
		}
		$res=$this->check_mark_result($res, $line);
		return $res;
	}
}
