<?php

namespace Licenzo\Framework\Http;

use Licenzo\Framework\Support\Arr;
use Licenzo\Framework\Validator\ValidationException;
use Licenzo\Framework\Validator\Validator;

class Request
{
    /**
     * Request query parameters
     *
     * @var array
     */
    protected $query = [];

    /**
     * Request post data
     *
     * @var array
     */
    protected $request = [];

    /**
     * Request files
     *
     * @var array
     */
    protected $files = [];

    /**
     * Request headers
     *
     * @var array
     */
    protected $headers = [];

    /**
     * Raw request body
     *
     * @var string
     */
    protected $rawBody = '';

    /**
     * Request method
     *
     * @var string
     */
    protected $method;

    /**
     * Request attributes (for storing additional data)
     *
     * @var \Licenzo\Framework\Support\ParameterBag
     */
    public $attributes;

    /**
     * Path info for routing
     *
     * @var string|null
     */
    protected $pathInfo = null;

    /**
     * Constructor
     *
     * @param \WP_REST_Request|null $wp_rest_request WordPress REST request object
     */
    public function __construct($wp_rest_request = null)
    {
        $this->query = $_GET;
        $this->request = $_POST;
        $this->files = $_FILES;
        $this->headers = $this->getRequestHeaders();
        $this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $this->attributes = new \Licenzo\Framework\Support\ParameterBag();
        $rawBody = file_get_contents('php://input');
        $this->rawBody = is_string($rawBody) ? $rawBody : '';

        if ($wp_rest_request instanceof \WP_REST_Request) {
            $this->bindRestRequest($wp_rest_request);
        }
    }

    /**
     * Capture the current request from globals
     *
     * @return static
     */
    public static function capture(): self
    {
        return new static();
    }

    /**
     * Create request from WordPress REST request
     *
     * @param \WP_REST_Request $wp_request
     * @return static
     */
    public static function createFromWordPressRequest($wp_request)
    {
        return new static($wp_request);
    }

    /**
     * Get request headers
     *
     * @return array
     */
    protected function getRequestHeaders()
    {
        $headers = [];
        foreach ($_SERVER as $key => $value) {
            if (strpos($key, 'HTTP_') === 0) {
                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value;
            }
        }
        return $headers;
    }

    /**
     * Get request method
     *
     * @return string
     */
    public function method()
    {
        return $this->method;
    }

    /**
     * Get request method (alias)
     *
     * @return string
     */
    public function getMethod()
    {
        return $this->method();
    }

    /**
     * Get request URI
     *
     * @return string
     */
    public function getUri()
    {
        return $_SERVER['REQUEST_URI'] ?? '/';
    }

    /**
     * Get path info for routing
     *
     * @return string
     */
    public function getPathInfo()
    {
        // Return stored path info if set, otherwise extract from REQUEST_URI
        if ($this->pathInfo !== null) {
            return $this->pathInfo;
        }

        $uri = $_SERVER['REQUEST_URI'] ?? '/';
        return strtok($uri, '?'); // Remove query string
    }

    /**
     * Set path info for routing
     *
     * @param string $pathInfo
     * @return $this
     */
    public function setPathInfo($pathInfo)
    {
        $this->pathInfo = $pathInfo;
        return $this;
    }

    /**
     * Check if request is ajax
     *
     * @return bool
     */
    public function ajax()
    {
        return isset($this->headers['X-Requested-With']) && $this->headers['X-Requested-With'] === 'XMLHttpRequest';
    }

    /**
     * Get input value
     *
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    public function input($key, $default = null)
    {
        return Arr::get($this->all(), $key, $default);
    }

    /**
     * Get all input data
     *
     * @return array
     */
    public function all()
    {
        return array_merge($this->query, $this->request);
    }

    /**
     * Get query parameter
     *
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    public function query($key, $default = null)
    {
        return $this->query[$key] ?? $default;
    }

    /**
     * Get file
     *
     * @param string $key
     * @return array|null
     */
    public function file($key)
    {
        return $this->files[$key] ?? null;
    }

    /**
     * Get header value
     *
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    public function header($key = null, $default = null)
    {
        if (!$key) {
            return $this->headers;
        }

        return Arr::get($this->headers, $key, $default);
    }

    /**
     * Check if input exists
     *
     * @param string $key
     * @return bool
     */
    public function has($key)
    {
        return isset($this->request[$key]) || isset($this->query[$key]);
    }

    /**
     * Check if request matches method
     *
     * @param string $method
     * @return bool
     */
    public function isMethod($method)
    {
        return strtoupper($method) === $this->method;
    }

    /**
     * Bind WordPress REST request parameters
     *
     * @param \WP_REST_Request $wp_rest_request
     * @return void
     */
    protected function bindRestRequest(\WP_REST_Request $wp_rest_request)
    {
        $this->rawBody = (string) $wp_rest_request->get_body();
        $this->query = array_merge($this->query, $wp_rest_request->get_query_params());
        $this->request = array_merge($this->request, $wp_rest_request->get_body_params());
        $this->files = array_merge($this->files, $wp_rest_request->get_file_params());
        $this->headers = array_merge($this->headers, $wp_rest_request->get_headers());
        $this->method = $wp_rest_request->get_method();

        // Also get all parameters (includes route parameters like {id})
        $allParams = $wp_rest_request->get_params();
        $this->request = array_merge($this->request, $allParams);

        // For JSON requests, try to get JSON body
        $contentType = $wp_rest_request->get_content_type();
        if ($contentType && isset($contentType['value']) && $contentType['value'] === 'application/json') {
            $jsonBody = json_decode($wp_rest_request->get_body(), true);
            if (is_array($jsonBody)) {
                $this->request = array_merge($this->request, $jsonBody);
            }
        }
    }

    public function get($key, $default = null)
    {

        if (is_array($key)) {
            $values = [];

            foreach ($key as $k) $values[] = $this->input($k, $default);

            return $values;
        }

        return $this->input($key, $default);
    }

    /**
     * Get sanitized input value(s)
     *
     * @param array|string $fields Array of ['key' => 'callback'] or single key
     * @param callable|null $callback Sanitization callback for single key (default: sanitize_text_field)
     * @param mixed $default Default value if key doesn't exist
     * @return array|mixed Array of sanitized values or single sanitized value
     */
    public function getSafe($fields, $callback = null, $default = null)
    {
        // Handle array input: ['key' => 'callback', 'key2' => 'callback2']
        if (is_array($fields)) {
            $result = [];
            foreach ($fields as $key => $sanitizer) {
                $value = $this->input($key, $default);

                // Determine the sanitizer callback
                if (is_callable($sanitizer)) {
                    $result[$key] = call_user_func($sanitizer, $value);
                } elseif (is_string($sanitizer) && function_exists($sanitizer)) {
                    $result[$key] = call_user_func($sanitizer, $value);
                } else {
                    // Default to sanitize_text_field
                    $result[$key] = sanitize_text_field($value);
                }
            }
            return $result;
        }

        // Handle string input: getSafe('key', 'callback')
        if (is_string($fields)) {
            $value = $this->input($fields, $default);

            // Determine the sanitizer callback
            if (is_callable($callback)) {
                return call_user_func($callback, $value);
            } elseif (is_string($callback) && function_exists($callback)) {
                return call_user_func($callback, $value);
            } else {
                // Default to sanitize_text_field
                return sanitize_text_field($value);
            }
        }

        throw new \InvalidArgumentException('First parameter must be array or string');
    }

    /**
     * Get current page number for pagination
     *
     * @param string $pageName
     * @return int
     */
    public function page(string $pageName = 'page'): int
    {
        $page = $this->input($pageName, 1);
        return max(1, (int)$page);
    }

    /**
     * Get per page value for pagination
     *
     * @param int $default
     * @param int $max
     * @return int
     */
    public function perPage(int $default = 15, int $max = 100): int
    {
        $perPage = $this->input('per_page', $default);
        return min($max, max(1, (int)$perPage));
    }

    /**
     * Get the current URL for pagination
     *
     * @return string
     */
    public function url(): string
    {
        $scheme = $this->isSecure() ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
        $uri = $_SERVER['REQUEST_URI'] ?? '/';

        return $scheme . '://' . $host . $uri;
    }

    /**
     * Get the current path for pagination
     *
     * @return string
     */
    public function path(): string
    {
        $uri = $_SERVER['REQUEST_URI'] ?? '/';
        return strtok($uri, '?');
    }

    /**
     * Check if request is secure (HTTPS)
     *
     * @return bool
     */
    public function isSecure(): bool
    {
        return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || $_SERVER['SERVER_PORT'] == 443
            || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
    }

    public function only($keys = [])
    {
        return Arr::only($this->all(), $keys);
    }

    /**
     * @throws ValidationException
     */
    public function validate(array $data = [], array $rules = [], array $messages = [])
    {
        $validator = Validator::make($data = $this->all(), $rules, $messages)->validate();

        if ($validator->validate()->fails()) {
            throw new ValidationException(
                'Unprocessable Entity!', 422, null, $validator->errors()
            );
        }

        return $validator->validated();
    }

    /**
     * @throws \HttpException
     */
    public function abort($status = 403, $message = null)
    {
        if (!$message && !is_numeric($status) && is_string($status)) {
            $message = $status;
            $status = 403;
        }

        $message = $message ?: 'Request has been aborted.';

        throw new \HttpException($message, $status);
    }

    /**
     * Resolve the client IP, honoring a configured trusted-proxy allowlist.
     *
     * Default configuration (`trusted_proxies` empty) returns REMOTE_ADDR
     * verbatim — the only hop-level IP that cannot be spoofed by the
     * client. X-Forwarded-For is consulted ONLY when REMOTE_ADDR is in
     * the configured trusted-proxy list.
     *
     * See \Licenzo\Support\ClientIpResolver for the full trust algorithm.
     */
    public function ip()
    {
        $trustedProxies = [];
        $trustedHeader  = 'HTTP_X_FORWARDED_FOR';

        try {
            $app = \Licenzo\Framework\Foundation\App::getInstance();
            $trustedProxies = $app->config('app.trusted_proxies', []);
            $trustedHeader  = $app->config('app.trusted_proxy_header', 'HTTP_X_FORWARDED_FOR');
            if (!is_array($trustedProxies)) {
                $trustedProxies = [];
            }
            if (!is_string($trustedHeader) || $trustedHeader === '') {
                $trustedHeader = 'HTTP_X_FORWARDED_FOR';
            }
        } catch (\Throwable $e) {
            // Config not yet bootstrapped (e.g. during unit test or early
            // plugin load). Fall through to safe defaults: empty proxy
            // list → returns REMOTE_ADDR only.
            $trustedProxies = [];
            $trustedHeader  = 'HTTP_X_FORWARDED_FOR';
        }

        return \Licenzo\Support\ClientIpResolver::resolve($_SERVER, $trustedProxies, $trustedHeader);
    }

    public function userAgent()
    {
        return $_SERVER['HTTP_USER_AGENT'] ?? '';
    }

    /**
     * Get the raw request body.
     *
     * @return string
     */
    public function rawBody(): string
    {
        return $this->rawBody;
    }

    /**
     * Get the raw request body (alias).
     *
     * @return string
     */
    public function getRawBody(): string
    {
        return $this->rawBody();
    }

}
