<?php

declare(strict_types=1);

namespace App\Helpers;

class Request
{
    private array $body;
    private array $query;
    private array $headers;
    private string $method;
    private string $uri;
    private array $routeParams = [];

    public function __construct()
    {
        $this->method = strtoupper($_SERVER['REQUEST_METHOD']);

        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $this->uri = '/' . trim($uri ?? '', '/');

        $this->headers = $this->parseHeaders();

        if ($this->method !== 'GET') {
            $contentType = $this->header('CONTENT-TYPE', '');
            $isMultipart = str_contains($contentType, 'multipart/form-data');
            if (!$isMultipart && ($this->server('CONTENT_LENGTH') ?: 0) > 5242880) {
                throw new \RuntimeException('Solicitud demasiado grande', 413);
            }
            if ($isMultipart) {
                $this->body = $_POST;
                // Method override: permite usar POST con _method=PUT para multipart (PHP no parsea $_FILES ni $_POST en PUT)
                if (!empty($_POST['_method'])) {
                    $this->method = strtoupper($_POST['_method']);
                }
            } else {
                $input = file_get_contents('php://input');
                $parsed = json_decode($input, true);
                $this->body = $parsed ?? $_POST;
            }
        } else {
            $this->body = [];
        }

        $this->query = $_GET;
    }

    private function parseHeaders(): array
    {
        $headers = [];
        foreach ($_SERVER as $key => $value) {
            if (str_starts_with($key, 'HTTP_')) {
                $name = str_replace('_', '-', substr($key, 5));
                $headers[$name] = $value;
            }
        }
        if (isset($_SERVER['CONTENT_TYPE'])) {
            $headers['CONTENT-TYPE'] = $_SERVER['CONTENT_TYPE'];
        }
        return $headers;
    }

    public function body(): array
    {
        return $this->body;
    }

    public function input(string $key, mixed $default = null): mixed
    {
        $data = $this->body();
        return $data[$key] ?? $default;
    }

    public function query(string $key, mixed $default = null): mixed
    {
        return $this->query[$key] ?? $default;
    }

    public function all(): array
    {
        return array_merge($this->query, $this->body());
    }

    public function only(array $keys): array
    {
        $result = [];
        $data = $this->all();
        foreach ($keys as $key) {
            if (array_key_exists($key, $data)) {
                $result[$key] = $data[$key];
            }
        }
        return $result;
    }

    public function has(string $key): bool
    {
        $data = $this->all();
        return array_key_exists($key, $data) && !empty($data[$key]);
    }

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

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

    public function header(string $name, mixed $default = null): mixed
    {
        $name = strtoupper(str_replace('-', '-', $name));
        return $this->headers[$name] ?? $default;
    }

    public function bearerToken(): ?string
    {
        $auth = $this->header('Authorization');
        if ($auth && str_starts_with($auth, 'Bearer ')) {
            return substr($auth, 7);
        }
        return null;
    }

    public function isMethod(string $method): bool
    {
        return strtoupper($method) === $this->method;
    }

    public function isJson(): bool
    {
        $contentType = $this->header('CONTENT-TYPE', '');
        return str_contains($contentType, 'application/json');
    }

    public function setRouteParams(array $params): void
    {
        $this->routeParams = array_merge($this->routeParams, $params);
    }

    public function param(string $key, mixed $default = null): mixed
    {
        return $this->routeParams[$key] ?? $default;
    }

    public function uploadedFile(string $key): ?array
    {
        return $_FILES[$key] ?? null;
    }

    public function getClientIp(): string
    {
        return $_SERVER['HTTP_X_FORWARDED_FOR']
            ?? $_SERVER['HTTP_CLIENT_IP']
            ?? $_SERVER['REMOTE_ADDR']
            ?? '0.0.0.0';
    }

    public function server(string $key, mixed $default = null): mixed
    {
        return $_SERVER[$key] ?? $default;
    }
}
