<?php /** @noinspection PhpUnused */

use Stevenmaguire\OAuth2\Client\Provider\Keycloak;

class oidc {

	const ACTION_ON_RETURN='oidc_actionOnReturn';
	const CALLBACK_URL='oidc_callbackUrl';
	const TOKEN='oidc_token';

	/**
	 * Handles a POST. Doesn't actually create anything.
	 * @param array $request
	 * @return array
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws NotFoundException
	 */
	public static function create(array $request=[]): array {
		if(empty($request['callbackUrl'])) {
			throw new BadRequestException("callbackUrl is required");
		}
		if(empty($request['actionOnReturn'])) {
			throw new BadRequestException("actionOnReturn is required");
		}
		$callbackUrl = $request['callbackUrl'];
		session::set(oidc::CALLBACK_URL, $callbackUrl);
		session::set(oidc::ACTION_ON_RETURN, $request['actionOnReturn']);
		$iceBearObject=oidc::getIceBearObjectFromIceBearUrl($callbackUrl);
		$config=oidc::getRemoteServerOidcConfig($iceBearObject['objecttype'], $iceBearObject['id']);
		$provider=new Keycloak([
			'authServerUrl' => $config['authServer'],
			'realm'         => $config['realm'],
			'clientId'      => $config['clientID'],
			'clientSecret'  => $config['clientSecret'],
			'scopes'        => implode(' ',$config['scopes']),
			'redirectUri'   => oidc::getOidcUrlFromIceBearUrl($callbackUrl)
		]);
		$authUrl = $provider->getAuthorizationUrl();
		$_SESSION['oauth2state'] = $provider->getState();
		header('Location: ' . $authUrl, false, 302);
		ob_flush();
		exit();
	}


	/**
	 * Handles the OIDC callback to /api/oidc.
	 * @throws BadRequestException
	 * @throws ServerException
	 * @throws NotFoundException
	 */
	public static function getAll(array $request=[]): array {
		if(empty($_GET['code'])) {
			throw new ServerException('OIDC auth failed or cancelled');
		}
		$callbackUrl=session::get(oidc::CALLBACK_URL);
		if(!$callbackUrl){ throw new BadRequestException('OIDC return path called with no callback URL in session'); }
		$iceBearObject=oidc::getIceBearObjectFromIceBearUrl($callbackUrl);
		$config=oidc::getRemoteServerOidcConfig($iceBearObject['objecttype'], $iceBearObject['id']);
		$provider = new Keycloak([
			'authServerUrl' => $config['authServer'],
			'realm'         => $config['realm'],
			'clientId'      => $config['clientID'],
			'clientSecret'  => $config['clientSecret'],
			'scopes'        => implode(' ',$config['scopes']),
			'redirectUri'   => oidc::getOidcUrlFromIceBearUrl($callbackUrl),
		]);
		try {
			$token = $provider->getAccessToken('authorization_code', [
				'code' => $_GET['code'],
			])->getToken();
		} catch (Throwable $e) {
			throw new ServerException('Token exchange failed: ' . $e->getMessage());
		}

		// store token
		session::set(oidc::TOKEN.'_'.$iceBearObject['objecttype'].$iceBearObject['id'], $token);

		// resume action from session
		$targetUrl = session::get(oidc::CALLBACK_URL) ?? '/';
		$actionOnReturn=session::get(oidc::ACTION_ON_RETURN);
		if(!empty($actionOnReturn)){
			$targetUrl.=((!str_contains($targetUrl, '?')) ? '?' : '&').oidc::ACTION_ON_RETURN.'='.$actionOnReturn;
		}
		header('Location: ' . $targetUrl, false, 302);
		ob_flush();
		exit();
	}

	/**
	 * Removes the token and actionOnReturn values from the session.
	 * @throws ServerException
	 * @throws NotFoundException
	 * @throws BadRequestException
	 */
	public static function delete(int $id): array {
		$iceBearObject=baseobject::getById($id);
		if(!$iceBearObject){ throw new NotFoundException('No object with ID '.$id); }
		if(!$iceBearObject['objecttype']){ throw new ServerException('No objecttype for object with ID '.$id); }
		session::set(oidc::TOKEN.'_'.$iceBearObject['objecttype'].$iceBearObject['id'], null);
		session::set(oidc::ACTION_ON_RETURN, null);
		return ['deleted'=>0, 'message'=>'Token deleted'];
	}


	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws NotFoundException
	 */
	public static function writeJavascriptVariables($iceBearObject): void {
		$remoteServer=oidc::getRemoteServerObjectFromObjectTypeAndId($iceBearObject['objecttype'], $iceBearObject['id']);
		echo "\nwindow.remoteServiceOidcConfigFileExists=".(oidc::remoteServerOidcConfigFileExists($remoteServer['objecttype'], $remoteServer['id'])?'true':'false').";\n";
		$actionOnReturn=$_GET[oidc::ACTION_ON_RETURN];
		$token=session::get(oidc::TOKEN.'_'.$remoteServer['objecttype'].$remoteServer['id']);
		echo 'window.actionOnReturn="'.$actionOnReturn.'";'."\n";
		echo 'window.oidcToken="'.$token.'";'."\n";
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 */
	private static function getRemoteServerOidcConfigFilePath($remoteServerObjectType, $remoteServerId): string {
		return rtrim(config::get('core_filestore'),'/')."/oidc/$remoteServerObjectType$remoteServerId.json";
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 */
	public static function remoteServerOidcConfigFileExists($remoteServerObjectType, $remoteServerId): bool {
		$configFile=oidc::getRemoteServerOidcConfigFilePath($remoteServerObjectType, $remoteServerId);
		return file_exists($configFile);
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 */
	private static function getRemoteServerOidcConfig($remoteServerObjectType, $remoteServerId): array {
		if(!oidc::remoteServerOidcConfigFileExists($remoteServerObjectType, $remoteServerId)) {
			throw new ServerException("No OIDC config file for $remoteServerObjectType $remoteServerId");
		}
		$configFile=oidc::getRemoteServerOidcConfigFilePath($remoteServerObjectType, $remoteServerId);
		$config=json_decode(file_get_contents($configFile),true);
		if(!$config){ throw new ServerException("Invalid OIDC config file at $configFile"); }
		return $config;
	}

	/**
	 * For a URL containing, e.g., "/shipment/123", return [ "shipmentdestination","456" ]
	 * @param $url
	 * @return array
	 * @throws BadRequestException|ServerException
	 * @throws NotFoundException
	 */
	private static function getIceBearObjectFromIceBearUrl($url): array {
		$path=parse_url($url,PHP_URL_PATH);
		$parts=explode('/',$path);
		if(!isset($parts[1])) {
			throw new BadRequestException("redirectUrl could not be split on /");
		}
		for($i=1;$i<count($parts);$i++) {
			if (is_numeric($parts[$i])) {
				$id = $parts[$i];
				$objectType = $parts[$i - 1];
				return oidc::getRemoteServerObjectFromObjectTypeAndId($objectType, $id);
			}
		}
		throw new BadRequestException("Invalid redirect url - expected to contain something like /shipmentdestination/123");
	}

	private static function getOidcUrlFromIceBearUrl($url): string {
		$path=parse_url($url,PHP_URL_PATH);
		$parts=explode($path, $url);
		return rtrim($parts[0],"/").'/api/oidc';
	}

	/**
	 * @throws ServerException
	 * @throws BadRequestException
	 * @throws NotFoundException
	 */
	private static function getRemoteServerObjectFromObjectTypeAndId($objectType, $objectId): array {
		if("shipment"===$objectType) {
			$shipment=shipment::getById($objectId);
			if(!$shipment){ throw new NotFoundException("Shipment $objectId not found"); }
			$objectType="shipmentdestination";
			$objectId=$shipment['shipmentdestinationid'];
		}
		$object=forward_static_call_array([$objectType,'getById'], [$objectId]);
		if(!$object){
			throw new BadRequestException("No $objectType with ID $objectId");
		}
		$object['objecttype']=$objectType;
		return $object;
	}

}