<?php

namespace App\Services\DiscoveryTools\Jamf;

use GuzzleHttp\Client;
use App\Models\ApiCredential;
use Exception;

class JamfClient
{
	const DEFAULT_EXPIRY_SECONDS = 1800; // 30 Minutes
	const TOKEN_END_POINT = 'api/oauth/token';
	// const TOKEN_END_POINT = 'uapi/auth/tokens';
	// const REFRESH_TOKEN_END_POINT = 'uapi/auth/keepAlive';
	const REFRESH_TOKEN_END_POINT = 'api/v1/auth/keep-alive';
	const MOBILE_DEVICES_END_POINT = 'api/v2/mobile-devices';
	const COMPUTERS_END_POINT = 'uapi/preview/computers';
	const COMPUTER_HARDWARE_END_POINT = 'uapi/v1/computers-inventory-detail';
	const CLASSIC_COMPUTER_END_POINT = 'JSSResource/computers';
	const COMPUTER_PRE_STAGES_END_POINT = 'api/v2/computer-prestages';
	const RECOVERY_LOCK_PASSWORD_END_POINT = 'api/v1/computers-inventory/{id}/view-recovery-lock-password';
	const FILE_VAULT_INFO_END_POINT = 'api/v1/computers-inventory/{id}/filevault';
	const COMPUTER_COMMAND_END_POINT = 'JSSResource/computercommands';
	const PRO_COMPUTER_END_POINT = 'api/v1/computers-inventory';
	const PRO_COMPUTER_DETAIL_END_POINT = 'api/v1/computers-inventory-detail';

	/**
	 * The resource owner's client_id.
	 *
	 * @var string
	 */
	private $clientId;

	/**
	 * The resource owner's client_secret.
	 *
	 * @var string
	 */
	private $clientSecret;

	/**
	 * The number of seconds token is valid for.
	 *
	 * @var int
	 */
	private $expiry;

	/**
	 * The current access token.
	 *
	 * @var string
	 */
	private $token;

	/**
	 * The issue time of the token as a number of seconds
	 *
	 * @var int
	 */
	private $issuedAt;

	/**
	 * @var GuzzleHttp\Client
	 */
	private $client;

	/**
	 * @var array $config
	 */
	private $config;

	/**
	 * @var ApiCredential $credential
	 */
	private $credential;

	public function __construct(Client $client, array $config)
	{
		$this->client = $client;
		$this->clientId = $config['client_id'];
		$this->clientSecret = $config['client_secret'];
		$this->token = $config['token'];
		$this->expiry = $config['expiry'];
		$this->issuedAt = $config['issuedAt'];
	}

	/**
	 * Authorizes the API credential.
	 *
	 * @param ApiCredential $credential The API credential to be authorized
	 * @return bool
	 */
	public function authorize(ApiCredential $credential)
	{
		$this->setCredential($credential);
		$token = $this->fetchToken();
		if ($token) {
			return true;
		}

		return false;
	}

	/**
	 * Fetches the token, either by returning the existing token if it is valid, or by fetching a new token from the credentials.
	 *
	 * @return string The fetched token
	 */
	public function fetchToken()
	{
		if ($this->token) {
			if ($this->isTokenValid()) {
				return $this->getToken();
			}
		}

		return $this->fetchTokenFromCredentials();
	}

	/**
	 * Check if the token is valid
	 *
	 * @return bool
	 */
	private function isTokenValid()
	{
		return ($this->expiry - 30) > time();
	}

	/**
	 * A description of the entire PHP function.
	 *
	 * @return datatype
	 */
	private function fetchTokenFromKeepAlive()
	{

		$response = $this->client->request('POST', self::REFRESH_TOKEN_END_POINT, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);


		$this->setTokenCredentials(json_decode($response->getBody()->getContents()));

		return $this->token;
	}

	/**
	 * Fetches the token from the credentials.
	 *
	 * @return string The token.
	 */
	private function fetchTokenFromCredentials()
	{
		$response = $this->client->request('POST', self::TOKEN_END_POINT, [
			'headers' => [
				'Content-Type' => 'application/x-www-form-urlencoded',
			],
			'form_params' => [
				'client_id' => $this->clientId,
				'grant_type' => 'client_credentials',
				'client_secret' => $this->clientSecret,
			],
		]);

		$this->setTokenCredentials(json_decode($response->getBody()->getContents()));

		return $this->token;
	}

	/**
	 * Set token credentials based on the provided token details.
	 *
	 * @param mixed $tokenDetails 
	 * @throws 
	 * @return 
	 */
	private function setTokenCredentials($tokenDetails)
	{
		$expires = time() + $tokenDetails->expires_in;
		$this->credential->update([
			'data' => json_encode([
				'token' => $tokenDetails->access_token,
				'expiry' => $expires,
				'issuedAt' => time()
			])
		]);

		$this->setToken($tokenDetails->access_token);
		$this->setExpiry($expires);
		$this->setIssuedAt(time());
	}

	/**
	 * Get the list of mobile devices.
	 *
	 * @return mixed
	 */
	public function getMobileDevices()
	{
		return $this->client->request('GET', self::MOBILE_DEVICES_END_POINT, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Retrieves the mobile details by ID.
	 *
	 * @param int $id The ID of the mobile device.
	 * @throws Some_Exception_Class A description of the exception that can be thrown.
	 * @return Some_Return_Value The mobile details.
	 */
	public function getMobileDetailsById($id)
	{
		return $this->client->request('GET', self::MOBILE_DEVICES_END_POINT . "/" . $id . "/detail", [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Get a list of computers with optional pagination.
	 *
	 * @param int $page The page number for pagination
	 * @throws \Some_Exception_Class Description of exception
	 * @return Some_Return_Value
	 */
	public function getComputers($page = 0)
	{
		return $this->client->request('GET', self::COMPUTERS_END_POINT . "?page=" . $page, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * A description of the entire PHP function.
	 *
	 * @param datatype $id description
	 * @throws Some_Exception_Class description of exception
	 * @return Some_Return_Value
	 */
	public function getHardwareDetails($id = 0)
	{
		return $this->client->request('GET', self::COMPUTER_HARDWARE_END_POINT . "/" . $id, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * A description of the entire PHP function.
	 *
	 * @param datatype $name description
	 * @throws Some_Exception_Class description of exception
	 * @return Some_Return_Value
	 */
	public function getComputerWithName($name)
	{
		return $this->client->request('GET', self::CLASSIC_COMPUTER_END_POINT . "/match/" . $name, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Set the token.
	 *
	 * @param datatype $token description
	 */
	public function updateUserDetailsForComputer($id, $updateData)
	{
		return $this->client->request('PUT', self::CLASSIC_COMPUTER_END_POINT . "/id/" . $id, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Content-Type' => 'text/xml; charset=UTF8',
			],
			'body' => $updateData,
		]);
	}

	public function setToken($token)
	{
		$this->token = $token;
	}

	/**
	 * Set the expiry date for the given value.
	 *
	 * @param datatype $expiry The expiry date to be set
	 */
	public function setExpiry($expiry)
	{
		$this->expiry = $expiry;
	}

	/**
	 * Set the issuedAt property.
	 *
	 * @param datatype $issuedAt The value to set as issuedAt
	 */
	public function setIssuedAt($issuedAt)
	{
		$this->issuedAt = $issuedAt;
	}

	/**
	 * Get the token.
	 *
	 * @return mixed
	 */
	public function getToken()
	{
		return $this->token;
	}

	/**
	 * Set the credential for the PHP function.
	 *
	 * @param datatype $credential description
	 */
	public function setCredential($credential)
	{
		$this->credential = $credential;
	}

	/**
	 * Get device details by ID.
	 *
	 * @param datatype $id description
	 * @throws Some_Exception_Class description of exception
	 * @return Some_Return_Value
	 */
	public function getDeviceDetailsById($id)
	{
		return $this->client->request('GET', self::CLASSIC_COMPUTER_END_POINT . "/id/" . $id, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Get computer details by serial no
	 */
	public function getComputerDetailsBySerialNo($serialNo)
	{
		return $this->client->request('GET', self::CLASSIC_COMPUTER_END_POINT . "/serialnumber/" . $serialNo, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Get all the computer prestages
	 */
	public function getComputerPreStages($page)
	{
		return $this->client->request('GET', self::COMPUTER_PRE_STAGES_END_POINT . "?page=$page", [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Get the devices from the computer prestages
	 */
	public function getComputerPreStagesDevices($preStage)
	{
		return $this->client->request('GET', self::COMPUTER_PRE_STAGES_END_POINT . "/$preStage/scope", [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}


	/**
	 * Get the recovery lock password details
	 */
	public function getRecoveryLockPasswordById($id)
	{
		$url = str_replace('{id}', $id, self::RECOVERY_LOCK_PASSWORD_END_POINT);

		return $this->client->request('GET', $url, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * Get the recovery lock password details
	 */
	public function getFileVaultInfoById($id)
	{
		$url = str_replace('{id}', $id, self::FILE_VAULT_INFO_END_POINT);

		return $this->client->request('GET', $url, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * HTTP resquets to JAMF PRO API for fetching computer inventory
	 * @param int $page
	 * 
	 * @return [type]
	 */
	public function getJamfProComputers($page = 0, $pageSize = 100)
	{
		return $this->client->request('GET', self::PRO_COMPUTER_END_POINT . "?page=" . $page . "&page-size=" . $pageSize . "&sort=id:asc", [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}

	/**
	 * HTTP request to get jamf pro inventory details
	 * @param mixed $id
	 * 
	 * @return [type]
	 */
	public function getJamfProDeviceDetailsById($id)
	{
		return $this->client->request('GET', self::PRO_COMPUTER_DETAIL_END_POINT . "/" . $id, [
			'headers' => [
				'Authorization' => 'Bearer ' . $this->token,
				'Accept' => 'application/json',
				'User-Agent' =>  'teqtivity/1.0'
			]
		]);
	}
}
