<?php

/**
 * A PHP  class  client library to interact with Supabase GoTrue Api.
 *
 * Any method under the supabase.auth.admin namespace requires a service_role key
 *
 * These methods are considered admin methods and should be called on a trusted server.
 * Never expose your service_role key in the browser.
 */

namespace Supabase\GoTrue;

use GuzzleHttp\Psr7;
use Supabase\Util\Constants;
use Supabase\Util\Request;

class GoTrueAdminApi
{
	/**
	 * Fully qualified URL to the Supabase instance REST endpoint(s).
	 *
	 * @var string
	 */
	protected string $url;

	/**
	 * A header Bearer Token generated by the server in response to a login request
	 * [service key, not anon key].
	 *
	 * @var array
	 */
	protected array $headers = [];

	/**
	 * Get the url.
	 */
	public function __getUrl(): string
	{
		return $this->url;
	}

	/**
	 * Get the headers.
	 */
	public function __getHeaders(): array
	{
		return $this->headers;
	}

	/**
	 * GoTrueAdminApi constructor.
	 *
	 * @param  string  $api_key  The anon or service role key
	 * @param  string  $reference_id  Reference ID
	 * @param  array  $options  Options
	 * @param  string  $domain  The domain pointing to api
	 * @param  string  $scheme  The api sheme
	 * @param  string  $path  The path to api
	 *
	 * @throws Exception
	 */
	public function __construct($reference_id, $api_key, $options = [], $domain = 'supabase.co', $scheme = 'https', $path = '/auth/v1')
	{
		$headers = ['Authorization' => "Bearer {$api_key}", 'apikey' => $api_key];
		$this->url = ! empty($reference_id) ? "{$scheme}://{$reference_id}.{$domain}{$path}" : "{$scheme}://{$domain}{$path}";
		$this->headers = $headers ?? null;
		$this->headers = array_merge(Constants::getDefaultHeaders(), $headers);
	}

	public function __request($method, $url, $headers, $body = null): array
	{
		$response = Request::request($method, $url, $headers, $body);
		if ($response->getStatusCode() === 204) {
			return ['code' => 204, 'msg' => 'OK'];
		}

		return json_decode($response->getBody(), true);
	}

	/**
	 * Removes a logged-in session.
	 *
	 * @param  string  $jwt  A valid, logged-in JWT.
	 */
	public function signOut($jwt)
	{
		try {
			$url = $this->url.'/logout';
			$this->headers['Authorization'] = "Bearer {$jwt}";
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json']);
			$response = $this->__request('POST', $url, $headers);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Sends an invite link to an email address.
	 *
	 * @param  string  $email  The email address of the user.
	 * @param  array  $options
	 * @param string options['redirectTo'] A URL or mobile deeplink to send the user to after they are confirmed.
	 * @param string options['data Optional'] Optional user metadata
	 * @return array
	 *
	 * @throws Exception
	 */
	public function inviteUserByEmail($email, $options = [])
	{
		$redirectTo = isset($options['redirectTo']) ? ($options['redirectTo'] ? '?redirect_to=true' : null) : null;
		$data = ['email' => $email, 'data' => $options['data'] ?? null];

		try {
			$url = $this->url.'/invite'.$redirectTo;
			$body = json_encode($data);
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$response = $this->__request('POST', $url, $headers, $body);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Generates email links and OTPs to be sent via a custom email provider.
	 *
	 * @param  array  $params
	 * @param  string  $params['type']
	 * @param  string  $params['email']
	 * @param  string  $params['password']
	 * @param  array  $options
	 * @param  string  $options['password']  User password. For signup only.
	 * @param  array  $options['data']  Optional user metadata. For signup only.
	 * @param string options['redirectTo'] The redirect url which should be appended to the generated link
	 */
	public function generateLink($params, $options = [])
	{
		try {
			$redirectTo = isset($options['redirectTo']) ? "?redirect_to={$options['redirectTo']}" : null;
			if (isset($params['newEmail'])) {
				$params['new_email'] = $params['newEmail'];
				unset($params['newEmail']);
			}
			$data = $params;
			$url = $this->url.'/admin/generate_link'.$redirectTo;
			$body = json_encode($data);
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$response = $this->__request('POST', $url, $headers, $body);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	// User Admin API
	/**
	 * Creates a new user.
	 * This function should only be called on a server. Never expose your `service_role` key in the browser.
	 *
	 * @param  array  $attrs  User Attributes
	 *                        phone_confirm Confirms the user's phone number if set to true. Only a service role can modify.
	 *                        phone The user's phone.
	 *                        password The user's password.
	 *                        email_confirm Confirms the user's email address if set to true. Only a service role can modify.
	 *                        email The user's email.
	 *                        ban_duration Determines how long a user is banned for. The format for the ban duration follows
	 *                        a strict sequence of decimal numbers with a unit suffix.
	 *                        Valid time units are "ns", "us" (or "µs"), "ms", "s",
	 *                        "m", "h". For example, some possible durations include:
	 *                        '300ms', '2h45m'. Setting the ban duration to 'none'
	 *                        lifts the ban on the user.
	 *                        app_metadata  A custom data object to store the user's application specific metadata. This maps
	 *                        to the `auth.users.app_metadata` column. Only a service role can modify.
	 *                        The `app_metadata` should be a JSON object that includes app-specific info,
	 *                        such as identity providers, roles, and other access control information.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function createUser($attrs)
	{
		try {
			$url = $this->url.'/admin/users';
			$body = json_encode($attrs);
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$response = $this->__request('POST', $url, $headers, $body);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Get a list of users.
	 *
	 * @param  array  $params  An array which supports `page` and `perPage` as numbers, to alter the paginated results.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function listUsers($params = [])
	{
		try {
			$path = isset($params['page'], $params['perPage']) ? "?page={$params['page']}&per_page={$params['perPage']}" : '';
			$url = $this->url.'/admin/users'.$path;
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$response = $this->__request('GET', $url, $headers);
			//$users = json_decode($response->getBody(), true);
			$pagination = [];
			//$total = $response->getHeader('x-total-count') ?? 0;
			//$pagination = $response->getHeader('link') ? Psr7\Header::parse($response->getHeader('link')) : [];

			return ['data' => array_merge($response, $pagination), 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Get user by id.
	 * Fetches the user object from the database based on the user's id.
	 *
	 * @param  string  $uid  The user's unique identifier
	 * @return array
	 *
	 * @throws Exception
	 */
	public function getUserById($uid)
	{
		try {
			$url = $this->url.'/admin/users/'.$uid;
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json']);
			$response = $this->__request('GET', $url, $headers);

			return $response;
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Updates the user data.
	 *
	 * @param  string  $uid  The user's unique identifier
	 * @param  array  $attrs  The data you want to update.
	 *                        user_metadata A custom data object to store the user's metadata.
	 *                        phone_confirm Confirms the user's phone number if set to true. Only a service role can modify.
	 *                        phone The user's phone.
	 *                        password The user's password.
	 *                        email_confirm Confirms the user's email address if set to true. Only a service role can modify.
	 *                        email The user's email.
	 *                        ban_duration Determines how long a user is banned for. The format for the ban duration follows
	 *                        a strict sequence of decimal numbers with a unit suffix.
	 *                        Valid time units are "ns", "us" (or "µs"), "ms", "s",
	 *                        "m", "h". For example, some possible durations include:
	 *                        '300ms', '2h45m'. Setting the ban duration to 'none'
	 *                        lifts the ban on the user.
	 *                        app_metadata  A custom data object to store the user's application specific metadata. This maps
	 *                        to the `auth.users.app_metadata` column. Only a service role can modify.
	 *                        The `app_metadata` should be a JSON object that includes app-specific info,
	 *                        such as identity providers, roles, and other access control information.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function updateUserById($uid, $attrs)
	{
		try {
			$url = $this->url.'/admin/users/'.$uid;
			$body = json_encode($attrs);
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$response = $this->__request('PUT', $url, $headers, $body);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Delete a user.
	 *
	 * @param  string  $uid  The user id you want to remove.
	 * @param  bool  $shouldSoftDelete  If true, then the user will be soft-deleted from the auth schema.
	 *                                  Defaults to false for backward compatibility.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function deleteUser($uid, $shouldSoftDelete = false)
	{
		try {
			$url = $this->url."/admin/users/{$uid}";
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$body = json_encode(['should_soft_delete' => $shouldSoftDelete]);
			$response = $this->__request('DELETE', $url, $headers, $body);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Sends a password reset request to an email address.
	 *
	 * @param  string  $email  The email address of the user.
	 * @param  array  $options
	 * @param  string  $options['redirectTo']  The URL to send the user to after they click the password reset link.
	 * @param  string  $options['captchaToken']  Verification token received when the user completes the captcha on the site.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function resetPasswordForEmail($email, $options = [])
	{
		try {
			$redirectTo = isset($options['redirectTo']) ? "?redirect_to={$options['redirectTo']}" : null;
			$captchaToken = isset($options['captchaToken']) ? $options['captchaToken'] : '';
			$data = [
				'email'                => $email,
				'gotrue_meta_security' => [
					'captcha_token' => $captchaToken,
				],
			];
			$url = $this->url.'/recover'.$redirectTo;
			$body = json_encode($data);
			$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
			$response = $this->__request('POST', $url, $headers, $body);

			return ['data' => $response, 'error' => null];
		} catch (\Exception $e) {
			throw $e;
		}
	}

	/**
	 * Lists all factors associated to a user.
	 *
	 * @param  string  $id  ID of the user.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function _listFactors($uid)
	{
		$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
		$response = $this->__request('GET', $this->url.'/admin/users/'.$uid.'/factors', $headers);

		return ['data' => $response, 'error' => null];
	}

	/**
	 * Deletes a factor on a user. This will log the user out of
	 * all active sessions if the deleted factor was verified.
	 *
	 * @param  string  $userId  ID of the user whose factor is being deleted.
	 * @param  string  $factorId  ID of the MFA factor to delete.
	 * @return array
	 *
	 * @throws Exception
	 */
	public function _deleteFactor($uid, $factorId)
	{
		$headers = array_merge($this->headers, ['Content-Type' => 'application/json', 'noResolveJson' => true]);
		$response = $this->__request('DELETE', $this->url.'/admin/users/'.$uid.'/factors/'.$factorId, $headers);

		return ['data' => $response, 'error' => null];
	}
}
