<?php
/**
 * Route Implementation
 *
 * @package Licenzo\Framework\Http\Router
 */

namespace Licenzo\Framework\Http\Router;

use Licenzo\Framework\Contracts\Router\Route as RouteContract;
use Licenzo\Framework\Foundation\Application;
use Licenzo\Framework\Http\Request;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Route implementation class
 */
class Route implements RouteContract
{
    protected $app;
    /**
     * HTTP method for this route
     *
     * @var string
     */
    protected string $method;

    /**
     * URI pattern for this route
     *
     * @var string
     */
    protected string $uri;

    /**
     * Action (controller/method) for this route
     *
     * @var mixed
     */
    protected $action;

    /**
     * Middleware assigned to this route
     *
     * @var array
     */
    protected array $middleware = [];

    /**
     * Policy assigned to this route
     *
     * @var string|null
     */
    protected $policy = null;

    /**
     * Name of this route
     *
     * @var string|null
     */
    protected $name = null;

    /**
     * Prefix for this route
     *
     * @var string
     */
    protected string $prefix = '';

    /**
     * Namespace for this route
     *
     * @var string|null
     */
    protected $namespace = null;

    /**
     * Route parameters extracted from URI
     *
     * @var array
     */
    protected array $parameters = [];

    /**
     * Router instance for named route registration
     *
     * @var \Licenzo\Framework\Contracts\Router\Router|null
     */
    protected $router = null;

    /**
     * Parameter constraints
     *
     * @var array
     */
    protected array $constraints = [];

    /**
     * WordPress REST API argument schemas for register_rest_route().
     *
     * @var array
     */
    protected array $restArgs = [];

    protected $permission = null;

    /**
     * Create a new route instance
     *
     * @param string $method
     * @param string $uri
     * @param mixed $action
     */
    public function __construct(Application $app, $method, $uri, $action, $options = [])
    {
        $this->app = $app;
        $this->method = strtoupper($method);
        $this->uri = $uri;
        $this->action = $action;

        $this->middleware = $options['middleware'] ?? [];
        $this->policy = $options['policy'] ?? null;
        $this->name = $options['name'] ?? null;
        $this->prefix = $options['prefix'] ?? '';
        $this->namespace = $options['namespace'] ?? null;
        $this->permission = $options['permissions'] ?? null;

    }

    /**
     * Set the router instance for named route registration
     *
     * @param \Licenzo\Framework\Contracts\Router\Router $router
     * @return self
     */
    public function setRouter($router)
    {
        $this->router = $router;
        return $this;
    }

    /**
     * Get the HTTP method for this route
     *
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * Get the URI pattern for this route
     *
     * @return string
     */
    public function getUri()
    {
        return $this->prefix . $this->uri;
    }

    /**
     * Get the raw URI without prefix
     *
     * @return string
     */
    public function getRawUri()
    {
        return $this->uri;
    }

    /**
     * Get the action for this route
     *
     * @return mixed
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * Get the middleware assigned to this route
     *
     * @return array
     */
    public function getMiddleware(): array
    {
        return $this->middleware;
    }

    /**
     * Get the policy assigned to this route
     *
     * @return string|null
     */
    public function getPolicy(): ?string
    {
        return $this->policy;
    }

    /**
     * Get the name of this route
     *
     * @return string|null
     */
    public function getName(): ?string
    {
        return $this->name;
    }

    /**
     * Get the prefix for this route
     *
     * @return string
     */
    public function getPrefix()
    {
        return $this->prefix;
    }

    /**
     * Get the namespace for this route
     *
     * @return string|null
     */
    public function getNamespace(): ?string
    {
        return $this->namespace;
    }

    /**
     * Get route parameters
     *
     * @return array
     */
    public function getParameters(): array
    {
        return $this->parameters;
    }

    /**
     * Add middleware to this route
     *
     * @param string|array $middleware
     * @return self
     */
    public function middleware($middleware)
    {
        $this->middleware = array_merge($this->middleware, (array)$middleware);
        return $this;
    }

    /**
     * Add a policy to this route
     *
     * @param string $policy
     * @return self
     */
    public function policy($policy)
    {
        $this->policy = $policy;
        return $this;
    }

    /**
     * Set the name for this route
     *
     * @param string $name
     * @return self
     */
    public function name($name)
    {
        $this->name = $name;

        // Register a named route in the router
        if ($this->router) {
            $this->router->addNamedRoute($name, $this);
        }

        return $this;
    }

    /**
     * Add parameter constraints
     *
     * @param string|array $parameter
     * @param string|null $pattern
     * @return self
     */
    public function where($parameter, $pattern = null)
    {
        if (is_array($parameter)) {
            $this->constraints = array_merge($this->constraints, $parameter);
        } else {
            $this->constraints[$parameter] = $pattern;
        }

        return $this;
    }

    /**
     * Define WordPress REST API argument schemas for this route.
     *
     * Each key is a parameter name (path or body). Values follow the
     * WP REST API schema format: type, required, sanitize_callback,
     * validate_callback, default, enum, description.
     *
     * @param array $args
     * @return self
     */
    public function args(array $args): self
    {
        $this->restArgs = array_merge($this->restArgs, $args);
        return $this;
    }

    /**
     * Set the prefix for this route
     *
     * @param string $prefix
     * @return self
     */
    public function setPrefix($prefix)
    {
        $this->prefix = rtrim($prefix, '/');
        return $this;
    }

    /**
     * Set the namespace for this route
     *
     * @param string|null $namespace
     * @return self
     */
    public function setNamespace($namespace)
    {
        $this->namespace = $namespace;
        return $this;
    }

    /**
     * Check if this route matches the given request
     *
     * @param Request $request
     * @return bool
     */
    public function matches(Request $request): bool
    {
        // Check HTTP method
        if ($this->method !== $request->getMethod()) {
            return false;
        }

        // Get request URI and clean it
        $requestUri = trim($request->getPathInfo(), '/');
        $routeUri = trim($this->getUri(), '/');

        // Handle empty URIs
        if (empty($routeUri) && empty($requestUri)) {
            return true;
        }

        // Convert route parameters {param} to the regex pattern
        $pattern = $this->convertToRegex($routeUri);

        // Match the pattern
        if (preg_match($pattern, $requestUri, $matches)) {
            // Extract parameters
            $this->extractParameters($routeUri, $matches);
            return true;
        }

        return false;
    }

    /**
     * Convert route URI to regex pattern
     *
     * @param string $uri
     * @return string
     */
    protected function convertToRegex(string $uri)
    {
        // Escape special regex characters except {}
        $pattern = preg_quote($uri, '#');

        // Convert {param} to named capture groups with constraints
        $pattern = preg_replace_callback('/\\\\{([^}]+)\\\\}/', function ($matches) {
            $param = $matches[1];
            $constraint = $this->constraints[$param] ?? '[^/]+';
            return "(?P<{$param}>{$constraint})";
        }, $pattern);

        return '#^' . $pattern . '$#';
    }

    /**
     * Calculate route specificity score for sorting
     *
     * Higher score = more specific route = higher priority
     *
     * Scoring algorithm:
     * - Static segment: +100 points
     * - Dynamic segment: +10 points
     * - Constraint on a dynamic segment: +5 points
     * - Penalty for total segments: -1 point per segment (prefer shorter routes)
     *
     * Examples:
     * - /customers/all → 200 points (2 static)
     * - /customers/{id} → 110 points (1 static + 1 dynamic)
     * - /customers/{id}/edit → 210 points (2 static + 1 dynamic)
     * - /{type}/{id} → 20 points (2 dynamic)
     * - /customers/{id:\d+} → 115 points (1 static + 1 dynamic + 1 constraint)
     *
     * @return int
     */
    public function getSpecificity(): int
    {
        $uri = trim($this->uri, '/');

        // Empty URI (root route)
        if (empty($uri)) {
            return 0;
        }

        $segments = explode('/', $uri);
        $score = 0;

        foreach ($segments as $segment) {
            // Check if segment is dynamic (contains {})
            if (preg_match('/^{.+}$/', $segment)) {
                // Dynamic segment: +10 points
                $score += 10;

                // Extract parameter name from {param} or {param:constraint}
                preg_match('/^{([^}:]+)/', $segment, $matches);
                if (isset($matches[1])) {
                    $paramName = $matches[1];

                    // Check if this parameter has constraints defined
                    if (isset($this->constraints[$paramName])) {
                        // Constraint bonus: +5 points
                        $score += 5;
                    }
                }
            } else {
                // Static segment: +100 points
                $score += 100;
            }
        }

        // Prefer shorter routes: -1 point per segment
        $score -= count($segments);

        return $score;
    }

    /**
     * Extract parameters from the matched URI
     *
     * @param string $routeUri
     * @param array $matches
     * @return void
     */
    protected function extractParameters($routeUri, $matches): void
    {
        $this->parameters = [];

        // Extract named parameters
        foreach ($matches as $key => $value) {
            if (is_string($key)) {
                $this->parameters[$key] = $value;
            }
        }
    }

    public static function create($app, $method, $uri, $action, $options = [])
    {
        return new static(
            $app,
            $method,
            $uri,
            $action,
            $options
        );
    }

    public function permission($permissions)
    {
        $this->permissions = $permissions;
        return $this;
    }

    protected function routeOptions()
    {
        return [
            'methods'             => $this->method,
            'callback'            => [$this, 'callback'],
            'permission_callback' => [$this, 'resolvePermissions'],
            'args'                => $this->restArgs,
        ];
    }

    public function callback($request)
    {
        $response = RouteResolver::create($this->app, $this)->dispatch();

        if ($response instanceof \Licenzo\Framework\Http\Response) {
            return $response->sendRest();
        }

        if ($response instanceof \WP_REST_Response || $response instanceof \WP_Error) {
            return $response;
        }

        if (is_array($response)) {
            if (isset($response['error']) && $response['error'] === true) {
                return new \WP_Error(
                    'licenzo_error',
                    $response['message'] ?? 'An error occurred',
                    ['status' => $response['code'] ?? 500]
                );
            }

            $statusCode = 200;
            if (isset($response['success']) && $response['success'] === false) {
                $statusCode = (int) ($response['status'] ?? 400);
            }

            return new \WP_REST_Response($response, $statusCode);
        }

        return $response;
    }

    public function register()
    {
        $namespace = $this->router->namespace;
        $wordpressUri = $this->compileRoute();
        $args = [
            'methods'             => $this->getMethod(),
            'callback'            => function($wpRequest){
                return $this->callback($wpRequest);
            },
            'permission_callback' => function($wpRequest){
                return $this->resolvePermissions($wpRequest);
            },
        ];

        $args = array_merge($this->routeOptions(), $args);
        return register_rest_route($namespace, $wordpressUri, $args);
    }

    public function resolvePermissions($wpRequest)
    {
        // Store WP request in app for RouteResolver
        $this->app->instance('current.wp.request', $wpRequest);

        // 1. Check Policy first (if set)
        if ($this->policy) {
            $policyInstance = $this->app->make($this->policy);
            $request = Request::createFromWordPressRequest($wpRequest);

            if (!$policyInstance->authorize($request)) {
                return new \WP_Error(
                    'rest_forbidden',
                    $policyInstance->message(),
                    ['status' => 403]
                );
            }
        }

        // 2. Check WordPress permission (if set)
        if ($this->permission) {
            return current_user_can($this->permission);
        }

        // 3. Default to public access
        return true;
    }

    public function compileRoute()
    {
        $uri = '/' . ltrim($this->getUri(), '/');
        // Convert {param} to WordPress REST API format
        return preg_replace('/\{([^}]+)\}/', '(?P<$1>[^/]+)', $uri);
    }
}
