<?php declare(strict_types=1);
/**
 * @package messenger
 * @author kputyr
 * @date 23.03.2026
 */

namespace IMATHUZH\Messenger;

require_once dirname(__DIR__) . '/Classes/Constants.php';
require_once dirname(__DIR__) . '/Classes/TokenInterface.php';
require_once dirname(__DIR__) . '/Classes/AuthorizationToken.php';
require_once dirname(__DIR__) . '/Classes/TokenManagerInterface.php';
require_once dirname(__DIR__) . '/Classes/SessionTokenManager.php';

const REQUEST_PATH = '/messenger/rest/';

abstract class ActionHandler
{
    protected array $args = [];

    public function apply($args): mixed {
        if (count($args) < count($this->args)) {
            $this->reject('not enough arguments');
        }
        foreach ($this->args as $id => $argPattern) {
            if (preg_match($argPattern, $args[$id], $match) === false) {
                $this->reject("invalid value of #$id argument");
            }
        }
        return $this->handle(...$args);
    }

    public static function reject(string $message): void
    {
        throw new \RuntimeException($message);
    }

    protected static function getTokenManager(): SessionTokenManager
    {
        session_start();
        return new SessionTokenManager($_SESSION);
    }

    protected static function getRequestBody(): ?object
    {
        return json_decode(file_get_contents('php://input'));
    }

    abstract protected function handle(...$args): mixed;
}


class MergeTokensHandler extends ActionHandler
{
    protected function handle(...$args): mixed {
        $tokenMgr = $this->getTokenManager();
        $body = $this->getRequestBody();
        $tokens = array_filter(
            array_map(
                function($tokenId) use($tokenMgr) {
                    $trimmedId = trim($tokenId);
                    return $trimmedId ? $tokenMgr->loadToken($trimmedId) : null;
                },
                $body->tokens ?? []
            ), fn ($token) => !!$token
        );
        logmsg('rest - INFO - merging tokens');
        $merged = AuthorizationToken::mergeTokens(...$tokens);
        $mergedId = $tokenMgr->saveToken($merged, $body->merged ?? '');
        logmsg("rest - INFO - merged tokens into $mergedId");
        return $mergedId;
    }
}

class ListTokenChannelsHandler extends ActionHandler
{
    protected array $args = [ '/^\w+$/', '/^[\w\-]+$/' ];

    protected function handle(...$args): mixed {
        list($tokenId, $group) = $args;
        logmsg("rest - DEBUG - loading $group from $tokenId");
        return $this->getTokenManager()->loadToken($tokenId)->toArray()[$group] ?? [];
    }
}

class ListTokenGroupsHandler extends ActionHandler
{
    protected array $args = [ '/^\w+$/' ];

    protected function handle(...$args): mixed {
        $tokenId = $args[0];
        logmsg("rest - DEBUG - loading groups of $tokenId");
        return array_keys($this->getTokenManager()->loadToken($tokenId)->toArray());
    }
}

class DecodeTokenHandler extends ActionHandler
{
    protected array $args = [ '/^\w+$/' ];

    protected function handle(...$args): mixed {
        $tokenId = $args[0];
        logmsg("rest - DEBUG - loading channel data of $tokenId");
        return $this->getTokenManager()->loadToken($tokenId)->toArray();
    }
}

function applyAction(array $path, array $actions) {
    if ($path && is_array($actions[$path[0]] ?? null)) {
        $action = array_shift($path);
        return applyAction($path, $actions[$action]);
    }
    $handler = $actions[$_SERVER['REQUEST_METHOD']] ?? null;
    if (is_string($handler)) {
        logmsg("rest - INFO - found handler $handler");
        return (new $handler())->apply($path);
    } else {
        ActionHandler::reject('unsupported method');
    }
}

$actions = [
    'token' => [
        'merge'    => [ 'POST' => MergeTokensHandler::class ],
        'channels' => [ 'GET' => ListTokenChannelsHandler::class ],
        'groups'   => [ 'GET' => ListTokenGroupsHandler::class ],
        'GET' => DecodeTokenHandler::class
    ]
];


try {
    logHeaders('rest');
    logTokens('rest');

    $uri = $_SERVER['HTTP_X_ORIGINAL_URI'] ?? '';
    if (!str_starts_with($uri, REQUEST_PATH)) return [];
    $path = array_filter(explode('/', substr($uri, strlen(REQUEST_PATH))), fn ($part) => !!$part);

    $result = applyAction($path, $actions);
    http_response_code(200);
    header('Content-Type: application/json');
    echo json_encode([
        'success' => true,
        'data' => $result
    ]);
} catch(\Throwable $e) {
    logmsg("rest - ERROR - {$e->getMessage()}\n{$e->getTraceAsString()}");
    http_response_code(403);
    header('Content-Type: application/json');
    echo json_encode([
        'success' => false,
        'reason' => $e->getMessage()
    ]);
}
logmsg('rest - INFO - done');