<?php

namespace onespace\tools\activeApi\components;

use bizley\jwt\Jwt;
use Yii;
use yii\web\IdentityInterface;

/**
 * This component allows the capturing and logging of a USER OSID GUID on downstream microservices.
 *
 * For this to function you will need to add this to your base controller in `beforeAction`:
 *
 * ```php
 * #[\Override]
 * public function beforeAction($action) {
 *     ...
 *     Yii::$app->user->setIdentity(new \onespace\tools\activeApi\components\UserComponent());
 *     ...
 * }
 * ```
 *
 * Add the following to the components in your web config:
 *
 * ```php
 * ...
 * 'components' => [
 *     ...
 *     'user' => [
 *         'identityClass' => \onespace\tools\activeApi\components\UserComponent::class,
 *         'enableSession' => false,
 *         'loginUrl' => null,
 *     ],
 * ],
 * ...
 * ```
 *
 * And the following to the components in your console config:
 *
 * ```php
 * ...
 * 'components' => [
 *     ...
 *     'user' => [
 *          'class' => \yii\web\User::class,
 *          'identityClass' => \onespace\tools\activeApi\components\UserComponent::class,
 *          'enableAutoLogin' => false,
 *          'enableSession' => false,
 *     ],
 * ],
 * ...
 * ```
 *
 * @author  Gareth Palmer <gareth@one-space.co.za>
 */
class UserComponent implements IdentityInterface {
    /** @var string|null $id The USER GUID of from the parent service */
    public $id;
    /** @var string|null $guid The USER GUID of from the parent service */
    public $guid;
    /** @var string|null $authToken The raw JWT parsed user authToken from the parent service */
    public $authToken;
    /** @var bool $isGuest */
    public $isGuest;

    public $identity;

    public function __construct(?string $token = null) {
        if ($token === null) {
            // The data is pulled from the HTTP Header
            try {
                $this->authToken = Yii::$app->request->headers->get('Authorization');
            } catch (\Throwable $th) {
                $this->authToken = null;
            }
        } else {
            // The guid is set directly, usually via rabbit
            $this->authToken = $token;
        }
        $this->id = $this->decodeHeaderToken();
        $this->guid = $this->id;
        $this->isGuest = empty($this->id);
    }

    /**
     * Decode the raw JWT header into the USER GUID.
     */
    private function decodeHeaderToken(): ?string {
        /** @var Jwt $jwt */
        $jwt = Yii::$app->jwt;

        Yii::debug("Raw Token: {$this->authToken}", __METHOD__);

        if (empty($this->authToken)) {
            return null;
        }

        // Strip out the bearer
        $authToken = $this->authToken;
        if (preg_match('/^' . 'Bearer' . '\s+(.*?)$/', $this->authToken, $matches)) {
            $authToken = $matches[1];
        }

        $guid = null;

        /** @var UnencryptedToken $token */
        $token = $jwt->parse((string) $authToken);
        if ($jwt->validate($token)) {
            $guid = $token->claims()->get('uid');
        }
        Yii::debug("User GUID: {$guid}", __METHOD__);

        return $guid;
    }

    #[\Override]
    public static function findIdentity($id) {
        return new self($id); // allow login by id (guid)
    }

    #[\Override]
    public static function findIdentityByAccessToken($token, $type = null) {
        return null; // not needed unless using access tokens
    }

    #[\Override]
    public function getId() {
        return $this->id;
    }

    #[\Override]
    public function getAuthKey() {
        return null;
    }

    #[\Override]
    public function validateAuthKey($authKey) {
        return true;
    }

    /**
     * Returns the `authToken`.
     */
    public function getAuthToken(): string {
        return $this->authToken;
    }

    public function getIsGuest(): bool {
        return $this->isGuest;
    }

    public static function userIdentifierCallback() {
        try {
            return Yii::$app->user->id;
        } catch (\Throwable $th) {
            return null;
        }
    }

    public static function returnId(): ?string {
        try {
            return Yii::$app->user->id;
        } catch (\Throwable $th) {
            return null;
        }
    }
}
