<?php

namespace WordpressModels\Rest;

use JetBrains\PhpStorm\NoReturn;
use Psr\Cache\InvalidArgumentException;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
use WordpressModels\Rest\Attribute\RestRoute;
use WordpressModels\Rest\Permissions\RequestPermissionEvaluator;
use WordpressModels\Traits\AbstractSingletonTrait;
use WP_REST_Response;

/**
 * @template T
 * @deprecated 0.5.0 -- Use Dependency Injection instead by RestRoute attribute. This class has become obsolete.
 */
abstract class AbstractRestApi
{

    use AbstractSingletonTrait;

    protected array $routeDefinitions;

    protected function __construct(protected string $namespace = '', private readonly ?\Psr\Cache\CacheItemPoolInterface $cachePool = null)
    {
        add_action('rest_api_init', $this->registerRoutes(...));

        $this->registerRoutesAttributes();
    }

    /**
     * Autoregister REST routes attributed with the `WordpressModels\Route` attribute.
     * @return void
     */
    public function registerRoutesAttributes(): void
    {
        // try to get from cache
        $cacheMethodKey = md5(static::class . '::routes');
        try {
            $cached = $this->cachePool?->getItem($cacheMethodKey);
            if ($cached?->isHit()) {
                $this->routeDefinitions = $cached->get();
                return;
            }
        } catch (InvalidArgumentException) {
            // to nothing
        }

        $reflectionClass = new ReflectionClass($this);

        if ($classRouteAttribute = $reflectionClass->getAttributes(RestRoute::class)) {
            /** @var RestRoute $baseSetup */
            $baseSetup = current($classRouteAttribute)->newInstance();

            $classRoute = $baseSetup->route;
            $classMethods = $baseSetup->methods;
            $classAdditionalProps = $baseSetup->getAdditionalProperties();
            $classPermission = $baseSetup->permission;
        }

        $reflectionMethods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
        foreach ($reflectionMethods as $method) {

            // get all RestRoute and extending attributes on the current method.
            $routeAttributes = $method->getAttributes(RestRoute::class, ReflectionAttribute::IS_INSTANCEOF);

            foreach ($routeAttributes as $attribute) {
                /** @var RestRoute $routeInstance */
                $routeInstance = $attribute->newInstance();

                $permissionCallback = null;

                // check permission exists
                $permission = is_bool($routeInstance->permission) || is_null($routeInstance->permission)
                    ? ($classPermission ?? false)
                    : $routeInstance->permission;

                if ($permission) {
                    // if expression is directly callable, use as callback,
                    // else wrap expression in RequestPermissionEvaluator
                    $permissionCallback = is_callable($permission)
                        ? $permission
                        : new RequestPermissionEvaluator($permission);
                }

                // prepend the class baseRoute if exists
                $route = (isset($classRoute) ? "$classRoute/" : '') . $routeInstance->route;

                // override the class `methods` if exists
                $methods = $routeInstance->methods ?: ($classMethods ?? []);

                // to support DI, namespace is added later at actual registration in `registerRoutes()`
                $routeDef = [
                    'route' => $route,
                    'args' => [
                        'methods' => $methods,
                        'callback' => [$this, $method->getName()],
                        'permission_callback' => $permissionCallback,
                        'args' => $routeInstance->arguments ?? [],
                        'show_in_index' => $routeInstance->public
                    ],
                    'override' => $routeInstance->override
                ];

                foreach ($classAdditionalProps ?? [] as $key => $value) {
                    $routeInstance->addAdditionalProperty($key, $value);
                }

                $this->routeDefinitions[] = apply_filters('rest_process_route_attribute', $routeDef, $routeInstance);
            }
        }

        if (isset($cached)) {
            $this->cachePool?->save(
                $cached->set($this->routeDefinitions)
            );
        }
    }

    public function getNamespace(): string
    {
        return $this->namespace;
    }


    /**
     * Call the `register_rest_route` function for the given.
     *
     * @return void
     */
    public function registerRoutes(): void
    {
        foreach ($this->routeDefinitions as $definition) {
            // namespace is added here to support DI on prop
            register_rest_route($this->namespace, ...$definition);
        }
    }

    /**
     * Register a route to be registered during `rest_api_init` later.
     *
     * @param $route
     * @param array{methods: string, callback: callable, permission_callback: callable } $setup
     * @param array{validate_callback: callable, sanitize_callback: callable, required: bool, default: mixed}[] $arguments
     * @return void
     */
    public function registerRoute($route, array $setup, array $arguments = [], bool $override = false): void
    {
        $this->routeDefinitions[] = [
            'namespace' => $this->getNamespace(),
            'route' => $route,
            'args' => [...$setup, 'args' => $arguments],
            'override' => $override
        ];
    }

    /**
     * Prevent cloning.
     */
    private function __clone()
    {
    }

    /**
     * Prevent unserializing.
     */
    #[NoReturn] final public function __wakeup()
    {
        _doing_it_wrong(__FUNCTION__, __('Unserializing instances of this class is forbidden.', 'wrce'), '0.4.0');
        die();
    }

    /**
     * @param $code
     * @param $message
     * @param $context
     * @return WP_REST_Response
     */
    protected function error($code, $message, $context = null): WP_REST_Response
    {
        return new WP_REST_Response(
            array_filter([
                'message' => $message,
                'data' => $context
            ]),
            $code
        );
    }

}
