<?php
use Firebase\JWT\JWT;

class MiddlewareModelClass extends ModelClassEx {

    /**
     * 
     * @var String
     */
    private $token;
    
    /**
     * 
     * @var array
     */
    private $invalid;
    
    /**
     * 
     * @var object
     */
    private $midconf;
    
    /**
     * 
     * @var String
     */
    private $purposecls;
    
	public function __construct() {
		parent::__construct();
		header("Content-type: text/json");
	}

	public function init() {
		try {
		    $this -> loadPurposes();
			$this -> setParameter();
			$this -> validate();
		} catch (Exception $e) {
			throw new Exception(NO_STRING);
		}
	}

	private function loadPurposes(){	    
	    $dirFiles = array_diff(
	        scandir(__DIR__."/purposes"), 
	        array('.', '..'));
	    
	    foreach($dirFiles as $dir){
	        require_once "purposes/{$dir}";
	    }
	}
	
	private function setParameter() {
	    $this -> token     = $this -> getDataGet("token");
	    $this -> midconf   = $this -> getConfiguration();
	}

	private function getConfiguration(){
	    $devSetting = $this -> getRowData($this -> accessSelect("SELECT_DEV_SETTING", ["middleware_conf"]));
	    
	    if($this -> isLoopData($devSetting)){
	        $enabled = $this -> getColumnData($devSetting, "dev_setting_enabled");
	        
	        if(!$enabled)
	            return false;
	        
	        if($this -> checkJSONString($this -> getColumnData($devSetting, "dev_setting_value")))
	            return json_decode($this -> getColumnData($devSetting, "dev_setting_value"));
	    }else
	        return false;
	}
	
	private function decodeToken(){
	    try{
	        return JWT::decode(
	            $this -> token, 
	            $this -> midconf -> token_secretkey, 
	            [$this -> midconf -> algorithm]);
	    }catch(Exception $e){
	        return false;
	    }
	}
	
	public function validate() {
	    $this -> invalid = [];
	    	    
	    if(!$this -> midconf){
	        header("HTTP/1.1 500 Internal Server Error");
	        die("Internal Issue Occurred");
	    }
	    
	    if(trim($this -> token) != NO_STRING){
	        $decodedToken = $this -> decodeToken();
	        
	        if($decodedToken){
	            if(is_object($decodedToken))
	               $this -> validateTokenPayload($decodedToken);    
	            else
	               $this -> invalid[] = "Decoded Token is not a JSON object";
	        }else
	            $this -> invalid[] = "Invalid Token";
	    }else
	        $this -> invalid[] = "No Token String Found";
	    
	    $accessDetails = [
	        "IP_Address" => $this -> getColumnData($_SERVER, "REMOTE_ADDR"),
	        "Method" => $this -> getColumnData($_SERVER, "REQUEST_METHOD"),
	        "headers" => apache_request_headers()
	    ];
	    
	    if($this -> getUserData(PARAM_USER_ACCOUNT) != NO_STRING)
	        $accessDetails[PARAM_USER_ACCOUNT] = $this -> getUserData(PARAM_USER_ACCOUNT);
	    
	    if(isset($decodedToken))
	        $accessDetails["Decoded_Token"] = $decodedToken;
	        
	    $this -> logDetails(print_r($accessDetails, true));
	        
	    if($this -> isLoopData($this -> invalid)){
	        $this -> logDetails(print_r($this -> invalid, true));
	        header("HTTP/1.1 401 Unauthenticated");
	        die("NG");
	    }
	}
	
	public function runPurposeAction(){
	    if(isset($this -> purposecls))
	       $this ->purposecls::action();
	}
	
	private function logDetails($content){
	    $timestamp = date("Y-m-d H:i:s");
	    
	    $logDir = SITE_ROOT."api/Logs/Middleware";
	    
	    if(!@file_exists($logDir))
	        mkdir($logDir, 0777, true);
	        
	    $logFile = "{$logDir}/Log_".date("Y-m-d").".log";
	    error_log("[{$timestamp}]\n\n{$content}\n\n", VAL_INT_3, $logFile);
	}
	
	private function validateTokenPayload($decodedToken){
	    $bools = [
	        [
	            "focus" => "purpose",
	            "expression" => !isset($decodedToken -> purpose),
	            "description" => "'purpose' is undefined"
	        ],
	        [
	            "focus" => "data",
	            "expression" => !isset($decodedToken -> data),
	            "description" => "'data' is undefined"
	        ],
	        [
	            "focus" => "expiration",
	            "expression" => isset($decodedToken -> expiration)
	        ]
	    ];
	    
	    foreach($bools as $bool){
	        if($bool["expression"]){
	            if($bool["focus"] == "expiration"){
	                $now = strtotime(date("Y-m-d H:i:s"));
	                $exp = strtotime($decodedToken -> expiration);
	              
	                if($now > $exp)
	                    $this -> invalid[] = "Token Expired";
	            }else
	               $this -> invalid[] = $bool["description"];
	        }
	    }
	    
	    $purposes = explode(",", $this -> midconf -> purposes);
	    
	    if(in_array($decodedToken -> purpose, $purposes)){
	        if(class_exists($decodedToken -> purpose)){
	            $data = base64_decode($decodedToken -> data);
	            
	            if($this -> checkJSONString($data)){
	               $verdict = []; 
	                
	               $decodedToken -> purpose::validation($data, $verdict);
	               
	               if($this -> isLoopData($verdict)){
	                   if(!$verdict["validation"])
	                       $this -> invalid[] = $verdict["description"];
	                   else
	                       $this -> purposecls = $decodedToken -> purpose;
	               }
	            }
	        }else
	            $this -> invalid[] = "Purpose class doesn't exist [{$decodedToken -> purpose}]";
	    }else
	        $this -> invalid[] = "Invalid Purpose [{$decodedToken -> purpose}]";
	}
}

