<?php defined('BASEPATH') OR exit('No direct script access allowed');

class E_invoice extends BaseEinvoice implements EinvoiceInterface
{
	private $url 		= 'https://gstsandbox.charteredinfo.com/eivital/dec/v1.04';

	private $inv_url 	= 'https://gstsandbox.charteredinfo.com/eicore/dec/v1.03';

	private $ewb_url 	= 'https://gstsandbox.charteredinfo.com/eiewb/dec/v1.03';

	private $ewb_action_url = 'https://gstsandbox.charteredinfo.com/ewaybillapi/dec/v1.03/ewayapi';

	private $search_gstn = 'https://gstapi.charteredinfo.com/commonapi/v1.1/search';

	private $isEwbOnly = false;

	private $irp = 1;

	private $ci = null;
	private $cId = 0;
	private $bId = 0;

	public function __construct($manifest)
	{
		if (defined('ENVIRONMENT') && ENVIRONMENT == 'production')
		{
			$this->url 		= 'https://einvapi.charteredinfo.com/eivital/dec/v1.04';

			$this->inv_url 	= 'https://einvapi.charteredinfo.com/eicore/dec/v1.03';

			$this->ewb_url 	= 'https://einvapi.charteredinfo.com/eiewb/dec/v1.03';

			$this->ewb_action_url ='https://einvapi.charteredinfo.com/v1.03/dec/ewayapi';
		}

		$this->isEwbOnly = (strpos(get_company("salModCodes"), "IR") === FALSE && strpos(get_company("salModCodes"), "EB") !== FALSE);

		parent::__construct($manifest);

		$this->ci =& get_instance();
		$this->cId = get_company('id');
		$this->bId = get_user_data("defaultbId");
	}

	public function validate($data)
	{

		// validations
		if (preg_match('/^(0|\/|-)/',$data['tNo'])) {
			//Document number should not be starting with 0, / and -. If so, then request is rejected.
			throw new Exception("EINVOICE_TNO_ERROR.");
			return false;
		}

		if (sal_mod_active(BRANCH_MOD))
		{
			if ($this->bId!=$data['bId'])
			{
				throw new Exception("BRANCH_SWAPPED.");
				return false;
			}
		}
		if (strtotime($data['tDate']) < strtotime("2022-09-01")) {
			throw new Exception("Invalid date.");
			return false;
		}

		if (!in_array($data['tType'], array(SALINV,SALRETAIL,CRNOTE,DBNOTE))) {
			throw new Exception("Invalid document type.");
			return false;
		}

		if (in_array($data['status'], array('DRAFT','CANCELLED'))) {
			throw new Exception("Invalid document status.");
			return false;
		}

		if (!in_array($data['categoryCode'], array('B2B','EXP'))) {
			throw new Exception("Invalid document category.");
			return false;
		}

		if (!empty($data['isLocked'])) {
			throw new Exception("Transaction already posted to e-invoice");
			return false;
		}

		$hsn_sac = array();
		foreach ($data["details"] as $row)
        {
			if (strlen($row['itemHS'])<4)
			{
				throw new Exception(sprintf(lang('EINVOICE_HSN_REQUIRED'), $row['itemName']));
				return false;
			}
			if ($row['itemType']=='S' && !preg_match('/^(99)/',$row['itemHS']))
			{
				throw new Exception(sprintf(lang('SERVICE_HSN_REQUIRED'), $row['itemName']));
				return false;
			}

			$hsn_sac[] = $row['itemHS'];
		}

		if ($hsn_sac)
		{
			$this->ci->load->model('itemsModel');
			$hsn_sac = array_unique($hsn_sac); // unique hsn code
			$er_hs=$this->ci->itemsModel->valid_hsn($hsn_sac);
			if ($er_hs!==true) {
				throw new Exception(sprintf(lang('HSN_INVALID'), implode(',',$er_hs)));
				return false;
			}
		}

		return true;
	}


	private function validateReferredEinvoice($trans)
	{
		// Only for Credit Note
		if (!in_array($trans['tType'], [CRNOTE])) {
			return $trans;
		}

		$this->ci->db->select("
			t.id,
			t.tNo,
			t.classCode,
			JSON_EXTRACT(t.info, '$.S_IRN.AckNo') AS s_irn,
			JSON_EXTRACT(t.info, '$.C_IRN') AS c_irn
		", false);

		$this->ci->db->from('trans AS t');
		$this->ci->db->join('trans_ref AS tref', 'tref.refTransId = t.id', 'left');
		$this->ci->db->where([
			't.cId'        => $this->cId,
			'tref.transId'=> $trans['id']
		]);
		$this->ci->db->where_in('t.tType', [SALINV, SALRETAIL]);
		$this->ci->db->where_not_in('t.status', [SCANCEL, SDRAFT]);

		$refTrans = $this->ci->db->get()->result_array();

		// Default class code
		$trans['classCode'] = 'REG';

		if (!$refTrans) {
			return $trans;
		}

		foreach ($refTrans as $ref) {

			// Not e-invoiced
			if (empty($ref['s_irn'])) {
				throw new Exception(sprintf(
					lang('REF_SALES_NOT_EINVOICE'),
					$ref['tNo']
				));
				return false;
			}

			// Cancelled e-invoice
			if (!empty($ref['c_irn'])) {
				throw new Exception(sprintf(
					lang('REF_SALES_EINVOICE_CANCELLED'),
					$ref['tNo']
				));
				return false;
			}

			// Use ref invoice class code (SEZ / REG / etc.)
			$trans['classCode'] = $ref['classCode'];
		}

		return $trans;
	}

	public function post($data)
	{
		$transId = $data["id"];

		if(!$this->validate($data)){
			return false;
		}

		$data=$this->validateReferredEinvoice($data);
		if(!$data){
			return false;
		}
		
		// $formattedData = $this->format($data);
		$ein = $this->format($data);

		$content = $this->json_decimals_round($ein);

		if (defined('EINVDEBUG')) {
			$file_path = 'temp' . DS . 'export' . DS . get_company('id');

			if (!is_dir($file_path)) {
				mkdir($file_path, 0755, true);
			}

			$file_path=FCPATH.$file_path.DS.'einvoice_'.$transId.'.json';			
			file_put_contents($file_path,$content);
			if (filesize($file_path)>2097152) {
				// $this->message = lang('EINVOICE_JSON_SIZE');
				throw new Exception(lang('EINVOICE_JSON_SIZE'));
				return false;
			}
		}

		// validate schema
		require_once APPPATH . 'third_party/einvoice/JSON_Schema_Validator.php';

		$schemaContent = file_get_contents(APPPATH . 'third_party/einvoice/einvoice.json');
		$schema = json_decode('{"type":"object", "properties":""}');
		$schema->properties = json_decode($schemaContent);

		// $data_sch = file_get_contents($file_path);
		// $data_sch = json_decode($data_sch);
		$data_sch = json_decode($content);

		$objValidator = new JSON_Schema_Validator($schema);
		$objValidator->validate($data_sch);

		if (count($objValidator->errors) > 0)
		{
			throw new Exception(implode("\n",$objValidator->errors));
			return false;
		}
// print_R($content);exit;
		$t_res = $this->get_token(false);

		if ($t_res) {
			$params = array();
			$params['url'] = $this->inv_url . '/Invoice';
			$params['transId'] = $transId;
			$res = $this->curl_request($params, $content);

			if (!empty($res['Data'])) {
				return $this->apply_irn($res, $data);
			}
		}
	}

	public function cancel($invoiceId)
	{
		
	}

	public function get_status($invoiceId)
	{
		
	}

	public function get_details($invoiceId)
	{
		
	}

	function decrypt($val)
	{
		$ciphering 		= "AES-128-CTR";
		$encryption_iv 	= '1202209171116461';
		$encryption_key = "outputbooks";
		$options	= 0;

		return openssl_decrypt($val, $ciphering, $encryption_key, $options, $encryption_iv);
	}

	function get_token($t_only=true) {
		// t_only token only
		$user 		= get_user_data();
		$einv_auth 	= !empty($user['einv_auth'])?$user['einv_auth']:array();

		if ($einv_auth && !empty($einv_auth['TokenExpiry'])) {
			if (strtotime($einv_auth['TokenExpiry'])>strtotime(get_timezone_date('Y-m-d H:i:s'))) {
				return $t_only?$einv_auth['AuthToken']:$einv_auth;
			}
		}

		// asp config
		$einv 	= $this->get_einv_asp_config();

		if (empty($einv['einvId']) || empty($einv['einvPassword']))
		{
			throw new Exception(lang('EINVOICE_ASP_CONFIG_ERROR'));
			return false;
		} else {
			if (strpos($einv['einvId'],'##')!==false)
			{
				$einv['einvId'] = $this->decrypt($einv['einvId']);
			}
			if (strpos($einv['einvPassword'],'##')!==false)
			{
				$einv['einvPassword'] = $this->decrypt($einv['einvPassword']);
			}
		}
		// einvoice api config
		// $config 	= $this->get_config(true);
		// $settings 	= ($config && $config[0])?$config[0]:array();

		$settings = $this->config;
		
		if (empty($settings['username']) || empty($settings['password']))
		{
			throw new Exception(lang('EINVOICE_API_CONFIG_ERROR'));
			return false;
		} else {
			if (strpos($settings['username'],'##')!==false)
			{
				$settings['username'] = $this->decrypt($settings['username']);
			}
			if (strpos($settings['password'],'##')!==false)
			{
				$settings['password'] = $this->decrypt($settings['password']);
			}
		}

		if (empty($einv_auth)) {
			$user['einv_auth'] = array(
				'username' 	=> $settings['username'],
				'einv_pwd'		=> $settings['password'],
				'aspid'			=> $einv['einvId'],
				'password'		=> $einv['einvPassword'],
				'irp'			=> $einv['einvIrp'],
				'gstin'			=> get_company('config')['gstin']
			);
			set_user_data($user);
		}

		// $this->req_params = '?aspid='.$einv['einvId'].'&password='.$einv['einvPassword'].'&Gstin='.get_company('config')['gstin'];
		// get new token
		$params = array();
		$params['url'] = (!$this->isEwbOnly)?$this->url.'/auth':str_replace('/ewayapi','/auth?action=ACCESSTOKEN',$this->ewb_action_url);
		if (!$res = $this->curl_request($params)) {
			$this->message.= lang('EINVOICE_TOKEN_ERROR');
			return false;
		}
		if (!$this->isEwbOnly)
		{
			$user['einv_auth'] = array_merge($user['einv_auth'],array(
				'AuthToken' 	=> $res['Data']['AuthToken'],
				'TokenExpiry' 	=> $res['Data']['TokenExpiry'],
				'Sek' 			=> $res['Data']['Sek'],
				'client_id' 	=> $res['Data']['ClientId'],
			));
		} else {
			$user['einv_auth'] = array_merge($user['einv_auth'],array(
				'AuthToken' 	=> $res['authtoken'],
				'TokenExpiry' 	=> gmdate('Y-m-d h:i:s',strtotime('+1 day')),
				'Sek' 			=> $res['sek'],
				'client_id'		=> ''
			));
		}
		set_user_data($user);

		return $t_only?$user['einv_auth']['AuthToken']:$user['einv_auth'];
	}

	function get_einv_asp_config()
	{
		$this->config["gstin"] = $this->config["gstn"];
		$this->config["username"] = $this->config["user_name"];

		return $this->config;
/*
		$res = array('einvId' => '','einvPassword' => '','einvIrp' => 1);
		$this->ci->db->where_in('code',array('einvId','einvPassword','einvIrp'));
		$tmp =  $this->ci->db->select('*')->from('compconfig')->where(array('cId' => $this->cId))->get()->result_array();

		if ($tmp)
		{
			foreach ($tmp as $row)
			{
				$res[$row['code']] = $row['value'];
			}
		}

		return $res;
*/
	}

	function curl_request($params = array(),$data=array()){
		$req_url = $params['url'];
		$ch = curl_init();
		$headers = array('Content-Type: application/json; charset=utf-8');

		$user 		= get_user_data();
		if (!empty($user['einv_auth'])) {
			$this->irp = (isset($params['irp'])?$params['irp']:$user['einv_auth']['irp']);
			if (!empty($user['einv_auth']['AuthToken']))
			{
				$headers[] = "ClientId:".$user['einv_auth']['client_id'];
				$req_url.=(strrpos($req_url,'?')===false?'?':'&').'AuthToken='.$user['einv_auth']['AuthToken'];
			}
			$headers[] = "Gstin:".$user['einv_auth']['gstin'];
			$headers[] = "aspid:".$user['einv_auth']['aspid'];
			$headers[] = "password:".$user['einv_auth']['password'];
			if (!$this->isEwbOnly)
			{
				$headers[] = "user_name:".$user['einv_auth']['username'];
				$headers[] = "eInvPwd:".$user['einv_auth']['einv_pwd'];
			} else {
				$headers[] = "username:".$user['einv_auth']['username'];
				$headers[] = "ewbpwd:".$user['einv_auth']['einv_pwd'];
			}
			$headers[] = "IRP:".$this->irp;
		}
		curl_setopt($ch, CURLOPT_URL, $req_url);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_HEADER, 0);
			
		if (empty($data)) {
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); 
		} else {
			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 
			curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
		}
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	
		// Timeout in seconds
		curl_setopt($ch, CURLOPT_TIMEOUT, 30);
	
		/* execute request */
		$result = curl_exec($ch);
		$info = curl_getinfo($ch);
		
		if (defined('EINVDEBUG'))
		{
			$output ="=====================".base_url(uri_string())."=====================".PHP_EOL."Data : ".print_r($params,true)."\n*******response****\n".print_r($result,true).PHP_EOL.print_r($info,true).PHP_EOL;
			@file_put_contents(TMP_PATH . "einvoice_curl.log", $output, FILE_APPEND);
		}
		/* close cURL resource */
		curl_close($ch);
		
		$res = json_decode($result, true);

		if (empty($res['Status']))
		{
			$error_code = '';
			$message = "";
			if (!empty($res['Message'])) {
				$message = stripslashes($res['Message']);
				$res = false;
			} else if(!empty($res['ErrorDetails'])) {
				foreach ($res['ErrorDetails'] as $err) {
					$message = $err['ErrorCode'].'='.stripslashes($err['ErrorMessage'])."\n";
					$error_code = $err['ErrorCode'];
				}
				$message = trim($message,"\n");
				$res = false;
			}
			if (!empty($res['error']))
			{
				$error_code = trim($res['error']['error_cd'],',');
				$message .= trim($res['error']['message'],"\n");
				$res = false;
			}
			if (!$res) {
				throw new Exception($error_code . " : " . $message);
				return false;
			}
		}

		if (!empty($params['transId']) && $result)
		{
			/* ES - success, EE - error */
			activity_log(array('tId' => !empty($params['transId'])?$params['transId']:-1, 'aEvent' => $res?'ES':'EE','aValue' => $result));
		}

		return $res;
	}

	private function apply_irn($res, $trans) {
		if (empty($res['Data'])) {
			return false;
		}

		$tmp = json_decode($res['Data']);

		$info = json_decode($trans['info']);

		if (!$info) {
			$info = new stdClass();
		}

		$info->S_IRN = array(
			'AckNo' => $tmp->AckNo,
			'AckDt' => $tmp->AckDt,
			'Irn' => $tmp->Irn,
			'SignedInvoice' => $tmp->SignedInvoice,
			'SignedQRCode' => $tmp->SignedQRCode,
			'Irp' => $this->irp
		);
		$this->ci->db->where(array('id' => $trans['id'], 'cId' => $trans['cId']));
		$this->ci->db->set('info', json_encode($info));
		$this->ci->db->set('isLocked', SEINV);
		$this->ci->db->update('trans');

		return $info;
	}

	function json_decimals_round($ein)
	{
		$content = json_encode($ein);
		// 2 decimal round off
		preg_match_all('/[val|CgstAmt|SgstAmt|IgstAmt|CesAmt|AssVal|CgstVal|SgstVal|IgstVal|CesVal|StCesVal|Discount|OthChrg|RndOffAmt|TotInvVal|TotInvValFc|AssAmt|totAmt]":(-?[0-9]+\.[0-9]{3,300})/',$content,$matches);

		if (!empty($matches[1]))
		{
			foreach ($matches[1] as $key=>$val) {
				if ($val) {
					$content = str_replace($val,round($val,2),$content);
				}
			}
		}

		// 3 decimal round off
		preg_match_all('/[Qty|FreeQty|UnitPrice]":(-?[0-9]+\.[0-9]{4,300})/',$content,$matches);

		if (!empty($matches[1]))
		{
			foreach ($matches[1] as $key=>$val) {
				if ($val) {
					$content = str_replace($val,round($val,3),$content);
				}
			}
		}
		
		return $content;
	}
}

