<?php

namespace App\Services;

use App\Auth\XenForoUser;
use App\XenForoDataTypes\XenForoUserGroup;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Str;

class XenforoService {

    private const array PERMISSIONS_KEPT = [ 'general', 'romhackplaza' ];
    private const int TTL_PERMISSIONS = 300;
    private const int TTL_ROUTES = 86400;

    /**
     * Get specific XenForo user.
     *
     * @param int $xfUserId
     *
     * @return XenForoUser|null
     */
    public function getXfUser( int $xfUserId ): ?XenForoUser {

        $xfUser = \DB::connection('xenforo')
            ->table('user')
            ->where('user_id', $xfUserId)
            ->first();

        if(!$xfUser)
            return null;

        return new XenForoUser($xfUser);
    }

    /**
     * Get specific XenForo user group.
     *
     * @param int $xfUserGroupId
     *
     * @return XenForoUserGroup|null
     */
    public function getXfUserGroup( int $xfUserGroupId ): ?XenForoUserGroup {
        $xfUserGroup = \DB::connection('xenforo')
            ->table('user_group')
            ->where('user_group_id', $xfUserGroupId)
            ->first();

        if(!$xfUserGroup)
            return null;

        return new XenForoUserGroup($xfUserGroup);
    }

    /**
     * Get permissions for a specific user ID.
     *
     * @param int $userId
     * @param int $permissionCombinationId
     *
     * @return array
     */
    public function getPermissions(int $userId, int $permissionCombinationId): array {

        return Cache::remember("xf_permissions_{$userId}", self::TTL_PERMISSIONS, function() use($permissionCombinationId) {
           $row = \DB::connection('xenforo')
                    ->table('permission_combination')
                    ->where('permission_combination_id', $permissionCombinationId)
                    ->value('cache_value');

           if( !$row )
               return [];

           $data = json_decode($row, true);
           $data = array_intersect_key($data, array_flip(self::PERMISSIONS_KEPT));

           return $data ?: [];

        });

    }

    /**
     * Clear user data.
     *
     * @param int $userId
     *
     * @return void
     */
    public function clearUserData(int $userId): void
    {
        Cache::forget("xf_permissions_{$userId}");
    }

    /**
     *
     *
     * @param string $routeName <prefix>.<secname>
     *
     * @return string
     */
    public function getRoute( string $routeName, array $arguments ): string {

        $routes = Cache::remember("xf_routes", self::TTL_ROUTES, function(){
           return \DB::connection('xenforo')
                ->table('route')
                ->where('route_type', 'public' )
                ->get(['route_prefix', 'sub_name', 'format'])
                ->map(fn($r) => (array) $r )
                ->toArray();
        });

        $baseUrl = config('app.forum_url');

        try {
            [$prefix, $subName] = explode('.', $routeName, 2);

            $route = collect($routes)->first(function ($r) use ($prefix, $subName) {
                return $r['route_prefix'] === $prefix && $r['sub_name'] === $subName;
            });

            if( !$route )
                return $baseUrl . '/' . $prefix;

            $path = $this->buildRoutePath((array)$route, $arguments);
            return rtrim($baseUrl, '/') . '/' . $path;

        } catch (\Throwable $th) {
            $prefix = $routeName;

            $route = collect($routes)->first(function ($r) use ($prefix) {
                return $r['route_prefix'] === $prefix;
            });

            if( !$route )
                return $baseUrl . '/' . $prefix;

            $path = $this->buildRoutePath((array)$route, $arguments);
            return rtrim($baseUrl, '/') . '/' . $path;
        }

    }

    private function buildRoutePath(array $route, array $arguments): string {

        $prefix = $route['route_prefix'];
        $format = $route['format'];
        $subName = $route['sub_name'];

        if (!$format) {
            return $subName
                ? $prefix . '-/' . $subName
                : $prefix;
        }

        if (str_starts_with($format, '-/')) {
            return $prefix . $format;
        }

        $built = preg_replace_callback(
            '/:\+?(\w+)(?:_\w+)?(?:<([^>]+)>)?/',
            function(array $m) use ($arguments): string {
                $type = $m[1];
                $keys = isset($m[2])
                    ? explode(',', $m[2])
                    : [];

                return match(true) {

                    $type === 'page' => isset($params['page']) && $params['page'] > 1
                        ? 'page-' . $params['page']
                        : '',

                    $type === 'str_int' && count($keys) >= 2 => implode('.', array_filter([
                        $params[$keys[0]] ?? null,
                        $params[$keys[1]] ?? null,
                    ])),

                    $type === 'int' && count($keys) >= 1 => (string) ($params[$keys[0]] ?? ''),

                    in_array($type, ['str', 'any']) && count($keys) >= 1
                    => (string) ($params[$keys[0]] ?? ''),

                    default => isset($params[$type]) ? (string) $params[$type] : '',
                };
            },
            $format
        );

        $built = preg_replace('/\/+/', '/', $prefix . '/' . $built);
        $built = rtrim($built, '/');

        return $built;

    }

    private function hashCSRFToken( string $token, int $timestamp ): string
    {
        return hash_hmac('md5', $token . $timestamp, config('app.xf_salt') );
    }
    public function getCSRFToken(): string
    {
        $token = Cookie::get('xf_csrf');
        if( !$token ){
            $token = Str::random(16);
            Cookie::queue('xf_csrf', $token, 0, '/', config('session.domain'), 0, false, false );
        }

        $timestamp = time();
        return $timestamp . ',' . $this->hashCSRFToken($token, $timestamp);
    }
    public function verifyCSRFToken( string $requestToken ): bool
    {
        $token = Cookie::get('xf_csrf');
        if( !$token ){
            return false;
        }

        try {
            [$timestamp, $hash] = explode(',', $requestToken);
        } catch (\Throwable $th) {
            return false;
        }

        $timestamp = intval($timestamp);
        $currentTimestamp = time();

        if( abs( $currentTimestamp - $timestamp ) > 3600 )
            return false;

        return $hash === $this->hashCSRFToken($token, $timestamp);
    }
}
