<?php
namespace Misuzu\SharpChat;

use Index\Http\HttpFx;
use Misuzu\Config\IConfig;
use Misuzu\Config\CfgType;

// Replace
use Misuzu\AuthToken;
use Misuzu\Emoticon;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
use Misuzu\Users\UserWarning;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserWarningCreationFailedException;

final class SharpChatRoutes {
    private IConfig $config;
    private string $hashKey = 'woomy';

    public function __construct(HttpFx $router, IConfig $config) {
        $this->config = $config;

        $hashKey = $this->config->getValue('hashKey', CfgType::T_STR, '');

        if(empty($hashKey)) {
            $hashKeyPath = $this->config->getValue('hashKeyPath', CfgType::T_STR, '');
            if(is_file($hashKeyPath))
                $this->hashKey = file_get_contents($hashKeyPath);
        } else {
            $this->hashKey = $hashKey;
        }

        // Public endpoints
        $router->get('/_sockchat/emotes', [$this, 'emotes']);
        $router->get('/_sockchat/login', [$this, 'login']);
        $router->options('/_sockchat/token', [$this, 'token']);
        $router->get('/_sockchat/token', [$this, 'token']);

        // Private endpoints
        $router->get('/_sockchat/resolve', [$this, 'resolve']);
        $router->post('/_sockchat/bump', [$this, 'bump']);
        $router->post('/_sockchat/verify', [$this, 'verify']);
        $router->get('/_sockchat/bans', [$this, 'bans']);
        $router->get('/_sockchat/bans/check', [$this, 'checkBan']);
        $router->post('/_sockchat/bans/create', [$this, 'createBan']);
        $router->delete('/_sockchat/bans/remove', [$this, 'removeBan']);
    }

    public static function emotes($response, $request): array {
        $response->setHeader('Access-Control-Allow-Origin', '*');
        $response->setHeader('Access-Control-Allow-Methods', 'GET');

        $raw = Emoticon::all();
        $out = [];

        foreach($raw as $emote) {
            $strings = [];

            foreach($emote->getStrings() as $string)
                $strings[] = sprintf(':%s:', $string->emote_string);

            $out[] = [
                'Text' => $strings,
                'Image' => $emote->getUrl(),
                'Hierarchy' => $emote->getRank(),
            ];
        }

        return $out;
    }

    public static function login($response, $request): void {
        $currentUser = User::getCurrent();
        $configKey = $request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal';
        $chatPath = $this->config->getValue($configKey, CfgType::T_STR, '/');

        $response->redirect(
            $currentUser === null
                ? url('auth-login')
                : $chatPath
        );
    }

    public function token($response, $request) {
        $host = $request->hasHeader('Host') ? $request->getHeaderFirstLine('Host') : '';
        $origin = $request->hasHeader('Origin') ? $request->getHeaderFirstLine('Origin') : '';
        $originHost = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');

        if(!empty($originHost) && $originHost !== $host) {
            $whitelist = $this->config->getValue('origins', CfgType::T_ARR, []);

            if(!in_array($originHost, $whitelist))
                return 403;

            $originProto = strtolower(parse_url($origin, PHP_URL_SCHEME));
            $origin = $originProto . '://' . $originHost;

            $response->setHeader('Access-Control-Allow-Origin', $origin);
            $response->setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
            $response->setHeader('Access-Control-Allow-Credentials', 'true');
            $response->setHeader('Vary', 'Origin');
        }

        if($request->getMethod() === 'OPTIONS')
            return 204;

        if(!UserSession::hasCurrent())
            return ['ok' => false];

        $session = UserSession::getCurrent();
        $user = $session->getUser();
        $token = AuthToken::create($user, $session);

        return [
            'ok' => true,
            'usr' => $user->getId(),
            'tkn' => $token->pack(),
        ];
    }

    public function resolve($response, $request): array {
        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
        $method = (string)$request->getParam('m');
        $param = (string)$request->getParam('p');
        $realHash = hash_hmac('sha256', "resolve#{$method}#{$param}", $this->hashKey);

        if(!hash_equals($realHash, $userHash))
            return [];

        try {
            switch($method) {
                case 'id':
                    $userInfo = User::byId((int)$param);
                    break;

                case 'name':
                    $userInfo = User::byUsername($param);
                    break;
            }
        } catch(UserNotFoundException $ex) {}

        if(!isset($userInfo))
            return [];

        return [
            'user_id' => $userInfo->getId(),
            'username' => $userInfo->getUsername(),
            'colour_raw' => $userInfo->getColour()->getRaw(),
            'rank' => $rank = $userInfo->getRank(),
            'perms' => SharpChatPerms::convert($userInfo),
        ];
    }

    public function bump($response, $request) {
        if(!$request->isStringContent())
            return 400;

        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
        $bumpString = (string)$request->getContent();
        $realHash = hash_hmac('sha256', $bumpString, $this->hashKey);

        if(!hash_equals($realHash, $userHash))
            return;

        $bumpInfo = json_decode($bumpString);

        if(empty($bumpInfo))
            return;

        foreach($bumpInfo as $bumpUser)
            try {
                User::byId($bumpUser->id)->bumpActivity($bumpUser->ip);
            } catch(UserNotFoundException $ex) {}
    }

    public function verify($response, $request): array {
        if($request->isStreamContent())
            $authInfo = json_decode((string)$request->getContent());
        elseif($request->isJsonContent())
            $authInfo = $request->getContent()->getContent(); // maybe change this api lol, this looks silly
        else
            return ['success' => false, 'reason' => 'request'];

        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';

        if(strlen($userHash) !== 64)
            return ['success' => false, 'reason' => 'length'];

        if(!isset($authInfo->user_id, $authInfo->token, $authInfo->ip))
            return ['success' => false, 'reason' => 'data'];

        $realHash = hash_hmac('sha256', implode('#', [$authInfo->user_id, $authInfo->token, $authInfo->ip]), $this->hashKey);

        if(!hash_equals($realHash, $userHash))
            return ['success' => false, 'reason' => 'hash'];

        try {
            $userInfo = User::byId($authInfo->user_id);
        } catch(UserNotFoundException $ex) {
            return ['success' => false, 'reason' => 'user'];
        }

        $authMethod = mb_substr($authInfo->token, 0, 5);

        if($authMethod === 'SESS:') {
            $sessionToken = mb_substr($authInfo->token, 5);

            $authToken = AuthToken::unpack($sessionToken);
            if($authToken->isValid())
                $sessionToken = $authToken->getSessionToken();

            try {
                $sessionInfo = UserSession::byToken($sessionToken);
            } catch(UserSessionNotFoundException $ex) {
                return ['success' => false, 'reason' => 'token'];
            }

            if($sessionInfo->getUserId() !== $userInfo->getId())
                return ['success' => false, 'reason' => 'user'];

            if($sessionInfo->hasExpired()) {
                $sessionInfo->delete();
                return ['success' => false, 'reason' => 'expired'];
            }

            $sessionInfo->bump();
        } else {
            return ['success' => false, 'reason' => 'unsupported'];
        }

        $userInfo->bumpActivity($authInfo->ip);

        return [
            'success' => true,
            'user_id' => $userInfo->getId(),
            'username' => $userInfo->getUsername(),
            'colour_raw' => $userInfo->getColour()->getRaw(),
            'rank' => $rank = $userInfo->getRank(),
            'hierarchy' => $rank,
            'is_silenced' => date('c', $userInfo->isSilenced() || $userInfo->isBanned() ? ($userInfo->isActiveWarningPermanent() ? strtotime('10 years') : $userInfo->getActiveWarningExpiration()) : 0),
            'perms' => SharpChatPerms::convert($userInfo),
        ];
    }

    public function bans($response, $request): array {
        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
        $realHash = hash_hmac('sha256', 'givemethebeans', $this->hashKey);

        if(!hash_equals($realHash, $userHash))
            return [];

        $warnings = UserWarning::byActive();
        $bans = [];

        foreach($warnings as $warning) {
            if(!$warning->isBan() || $warning->hasExpired())
                continue;

            $isPermanent = $warning->isPermanent();
            $userInfo = $warning->getUser();
            $bans[] = [
                'user_id' => $userInfo->getId(),
                'id' => $userInfo->getId(),
                'username' => $userInfo->getUsername(),
                'colour_raw' => $userInfo->getColour()->getRaw(),
                'rank' => $rank = $userInfo->getRank(),
                'ip' => $warning->getUserRemoteAddress(),
                'is_permanent' => $isPermanent,
                'expires' => date('c', $isPermanent ? 0x7FFFFFFF : $warning->getExpirationTime()),
                'perms' => SharpChatPerms::convert($userInfo),
            ];
        }

        return $bans;
    }

    public function checkBan($response, $request): array {
        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
        $ipAddress = (string)$request->getParam('a');
        $userId = (int)$request->getParam('u', FILTER_SANITIZE_NUMBER_INT);

        $realHash = hash_hmac('sha256', "check#{$ipAddress}#{$userId}", $this->hashKey);
        if(!hash_equals($realHash, $userHash))
            return [];

        $response = [];
        $warning = UserWarning::byRemoteAddressActive($ipAddress)
            ?? UserWarning::byUserIdActive($userId);

        if($warning !== null) {
            $response['warning'] = $warning->getId();
            $response['id'] = $warning->getUserId();
            $response['user_id'] = $warning->getUserId();
            $response['ip'] = $warning->getUserRemoteAddress();
            $response['is_permanent'] = $warning->isPermanent();
            $response['expires'] = date('c', $response['is_permanent'] ? 0x7FFFFFFF : $warning->getExpirationTime());
        } else {
            $response['expires'] = date('c', 0);
            $response['is_permanent'] = false;
        }

        return $response;
    }

    public function createBan($response, $request): int {
        if(!$request->isFormContent())
            return 400;

        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
        $content = $request->getContent();
        $userId = (int)$content->getParam('u', FILTER_SANITIZE_NUMBER_INT);
        $modId = (int)$content->getParam('m', FILTER_SANITIZE_NUMBER_INT);
        $duration = (int)$content->getParam('d', FILTER_SANITIZE_NUMBER_INT);
        $isPermanent = (int)$content->getParam('p', FILTER_SANITIZE_NUMBER_INT);
        $reason = (string)$content->getParam('r');

        $realHash = hash_hmac('sha256', "create#{$userId}#{$modId}#{$duration}#{$isPermanent}#{$reason}", $this->hashKey);
        if(!hash_equals($realHash, $userHash))
            return 403;

        if(empty($reason))
            $reason = 'Banned through chat.';

        if($isPermanent)
            $duration = -1;
        elseif($duration < 1)
            return 400;

        try {
            $userInfo = User::byId($userId);
        } catch(UserNotFoundException $ex) {
            return 404;
        }

        try {
            $modInfo = User::byId($modId);
        } catch(UserNotFoundException $ex) {
            return 404;
        }

        try {
            UserWarning::create(
                $userInfo,
                $modInfo,
                UserWarning::TYPE_BAHN,
                $duration,
                $reason
            );
        } catch(UserWarningCreationFailedException $ex) {
            return 500;
        }

        return 201;
    }

    public function removeBan($response, $request): int {
        $userHash = $request->hasHeader('X-SharpChat-Signature')
            ? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
        $type = (string)$request->getParam('t');
        $subject = (string)$request->getParam('s');

        $realHash = hash_hmac('sha256', "remove#{$type}#{$subject}", $this->hashKey);
        if(!hash_equals($realHash, $userHash))
            return 403;

        $warning = null;
        switch($type) {
            case 'ip':
                $warning = UserWarning::byRemoteAddressActive($subject);
                break;

            case 'user':
                $warning = UserWarning::byUserIdActive((int)$subject);
                break;
        }

        if($warning === null)
            return 404;

        $warning->delete();

        return 204;
    }
}
