<?php

declare(strict_types=1);

namespace App\Core;

/**
 * Classe para gerenciar requisições HTTP
 */
class Request
{
    private array $get;
    private array $post;
    private array $server;
    private array $cookies;
    private array $files;
    private ?string $body = null;

    public function __construct()
    {
        $this->get = $_GET;
        $this->post = $_POST;
        $this->server = $_SERVER;
        $this->cookies = $_COOKIE;
        $this->files = $_FILES;
    }

    /**
     * Retorna o método HTTP
     */
    public function method(): string
    {
        return strtoupper($this->server['REQUEST_METHOD'] ?? 'GET');
    }

    /**
     * Retorna o URI da requisição
     */
    public function uri(): string
    {
        $uri = $this->server['REQUEST_URI'] ?? '/';

        // Remove query string
        if (($pos = strpos($uri, '?')) !== false) {
            $uri = substr($uri, 0, $pos);
        }

        return $uri;
    }

    /**
     * Retorna o path da requisição
     * IMPORTANTE: Detecta o diretório dinamicamente, NUNCA força um diretório específico
     * Cada sistema (/printjetdigital, /Systhema, etc) funciona isoladamente
     */
    public function path(): string
    {
        // Primeiro verifica se tem rota no query string (compatibilidade com .htaccess)
        if (isset($this->get['_route'])) {
            $route = $this->get['_route'];
            // Remove qualquer base path que possa ter vindo
            $route = ltrim($route, '/');
            // Detecta e remove o base path dinamicamente (Systhema, grupotupan, etc)
            $scriptName = $this->server['SCRIPT_NAME'] ?? '';
            $basePathSegment = basename(dirname($scriptName));
            if ($basePathSegment && $basePathSegment !== '.' && strpos($route, $basePathSegment . '/') === 0) {
                $route = substr($route, strlen($basePathSegment) + 1); // Remove "basepath/"
            }
            // Remover prefixos comuns também do _route
            $commonPrefixes = ['sanctus', 'grupotupan', 'systhema', 'tupanfarma'];
            foreach ($commonPrefixes as $prefix) {
                if (strpos($route, $prefix . '/') === 0) {
                    $route = substr($route, strlen($prefix) + 1);
                    break;
                }
            }
            $path = '/' . ltrim($route, '/');
            // Se vier do _route, já está processado, retorna direto
            return $path ?: '/';
        }

        // Caso contrário, processa do URI
        $uri = $this->uri();
        $path = parse_url($uri, PHP_URL_PATH) ?? '/';

        // Remove index.php se presente
        $path = str_replace('/index.php', '', $path);

        // PRIMEIRO: Remover prefixos comuns como /sanctus, /grupotupan, /tupanfarma, etc
        // Isso deve ser feito ANTES de processar o basePath do SCRIPT_NAME
        $commonPrefixes = ['/sanctus', '/grupotupan', '/systhema', '/tupanfarma'];
        foreach ($commonPrefixes as $prefix) {
            // Verifica se o path começa com o prefixo seguido de /
            if (strpos($path, $prefix . '/') === 0) {
                $path = substr($path, strlen($prefix));
                break;
            } elseif ($path === $prefix) {
                $path = '/';
                break;
            }
        }

        // SEGUNDO: Detecta o base path dinamicamente baseado no SCRIPT_NAME
        // Isso garante que cada sistema detecte seu próprio diretório
        $scriptName = $this->server['SCRIPT_NAME'] ?? '';
        $basePath = str_replace('\\', '/', dirname($scriptName));
        $basePath = rtrim($basePath, '/');

        // Remove o base path detectado do path atual
        // Ex: se SCRIPT_NAME = /Systhema/index.php, basePath = /Systhema
        // Se path = /Systhema/ordem-servico-pmoc, remove /Systhema -> /ordem-servico-pmoc
        if ($basePath !== '/' && $basePath !== '' && strpos($path, $basePath) === 0) {
            $path = substr($path, strlen($basePath));
        }

        // Garantir que o path comece com / e normalize
        $path = '/' . ltrim($path, '/');

        return $path ?: '/';
    }

    /**
     * Verifica se é uma requisição GET
     */
    public function isGet(): bool
    {
        return $this->method() === 'GET';
    }

    /**
     * Verifica se é uma requisição POST
     */
    public function isPost(): bool
    {
        return $this->method() === 'POST';
    }

    /**
     * Verifica se é uma requisição AJAX
     */
    public function isAjax(): bool
    {
        // Verificar header X-Requested-With
        if (isset($this->server['HTTP_X_REQUESTED_WITH'])
            && strtolower($this->server['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
            return true;
        }

        // Verificar se é requisição fetch (Content-Type: application/json ou multipart/form-data)
        $contentType = $this->server['HTTP_CONTENT_TYPE'] ?? '';
        if (strpos($contentType, 'application/json') !== false
            || strpos($contentType, 'multipart/form-data') !== false) {
            // Se for POST/PUT/PATCH com Content-Type específico, provavelmente é AJAX
            $method = strtoupper($this->server['REQUEST_METHOD'] ?? 'GET');
            if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Verifica se é uma requisição JSON
     */
    public function isJson(): bool
    {
        return isset($this->server['CONTENT_TYPE'])
            && strpos($this->server['CONTENT_TYPE'], 'application/json') !== false;
    }

    /**
     * Retorna um parâmetro GET
     */
    public function get(string $key, $default = null)
    {
        return $this->get[$key] ?? $default;
    }

    /**
     * Retorna um parâmetro POST
     * Para multipart/form-data, verifica $_POST diretamente para garantir que está atualizado
     * Também verifica php://input para requisições AJAX que podem não popular $_POST
     */
    public function post(string $key, $default = null)
    {
        // CRÍTICO: Sempre verificar $_POST diretamente primeiro para multipart/form-data
        // O $_POST pode ter sido populado depois da inicialização do Request
        $contentType = $this->server['HTTP_CONTENT_TYPE'] ?? '';
        $isMultipart = strpos($contentType, 'multipart/form-data') !== false;

        // Se for multipart/form-data, SEMPRE verificar $_POST diretamente
        if ($isMultipart && isset($_POST[$key])) {
            // Atualizar cache e retornar
            $this->post[$key] = $_POST[$key];
            return $_POST[$key];
        }

        // Se for multipart e $_POST não estiver vazio, atualizar todo o cache
        if ($isMultipart && !empty($_POST)) {
            $this->post = array_merge($this->post, $_POST);
            if (isset($_POST[$key])) {
                return $_POST[$key];
            }
        }

        // Verificar cache primeiro (pode ter sido atualizado acima)
        if (isset($this->post[$key])) {
            return $this->post[$key];
        }

        // Se não encontrou e é uma requisição POST, verificar php://input
        // Isso é necessário para requisições AJAX que podem não popular $_POST
        if ($this->isPost() && !$isMultipart) {
            // Não é multipart, pode ser application/x-www-form-urlencoded ou JSON
            $input = file_get_contents('php://input');
            if (!empty($input)) {
                parse_str($input, $parsed);
                if (isset($parsed[$key])) {
                    $this->post[$key] = $parsed[$key];
                    return $parsed[$key];
                }
            }
        }

        return $default;
    }

    /**
     * Retorna um parâmetro (GET ou POST)
     * Para multipart/form-data, verifica $_POST diretamente
     */
    public function input(string $key, $default = null)
    {
        // Verifica POST primeiro (incluindo verificação dinâmica de $_POST)
        $postValue = $this->post($key);
        if ($postValue !== null) {
            return $postValue;
        }

        // Depois verifica GET
        return $this->get[$key] ?? $default;
    }

    /**
     * Retorna todos os parâmetros
     */
    public function all(): array
    {
        // Se $_POST está vazio mas é uma requisição POST, tentar ler de php://input
        if (empty($this->post) && $this->isPost()) {
            $input = file_get_contents('php://input');
            if (!empty($input)) {
                parse_str($input, $parsed);
                if (!empty($parsed)) {
                    $this->post = array_merge($this->post, $parsed);
                }
            }
        }

        return array_merge($this->get, $this->post);
    }

    /**
     * Retorna o corpo da requisição
     */
    public function body(): string
    {
        if ($this->body === null) {
            $this->body = file_get_contents('php://input') ?: '';
        }

        return $this->body;
    }

    /**
     * Retorna o corpo da requisição como JSON
     */
    public function json(): ?array
    {
        $body = $this->body();

        if (empty($body)) {
            return null;
        }

        return json_decode($body, true);
    }

    /**
     * Retorna um cookie
     */
    public function cookie(string $key, $default = null)
    {
        return $this->cookies[$key] ?? $default;
    }

    /**
     * Retorna um arquivo enviado
     */
    public function file(string $key): ?array
    {
        return $this->files[$key] ?? null;
    }

    /**
     * Retorna o host da requisição
     */
    public function host(): string
    {
        return $this->server['HTTP_HOST'] ?? 'localhost';
    }

    /**
     * Retorna o domínio completo
     */
    public function fullUrl(): string
    {
        $protocol = $this->isSecure() ? 'https' : 'http';
        return $protocol . '://' . $this->host() . $this->uri();
    }

    /**
     * Verifica se é HTTPS
     */
    public function isSecure(): bool
    {
        return isset($this->server['HTTPS']) && $this->server['HTTPS'] !== 'off';
    }

    /**
     * Retorna o IP do cliente
     */
    public function ip(): string
    {
        if (!empty($this->server['HTTP_CLIENT_IP'])) {
            return $this->server['HTTP_CLIENT_IP'];
        }

        if (!empty($this->server['HTTP_X_FORWARDED_FOR'])) {
            return explode(',', $this->server['HTTP_X_FORWARDED_FOR'])[0];
        }

        return $this->server['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    /**
     * Retorna o User Agent
     */
    public function userAgent(): string
    {
        return $this->server['HTTP_USER_AGENT'] ?? '';
    }

    /**
     * Valida dados da requisição
     */
    public function validate(array $rules): array
    {
        $errors = [];
        $data = $this->all();

        foreach ($rules as $field => $rule) {
            $ruleList = is_string($rule) ? explode('|', $rule) : $rule;

            // Verificar se o campo é numérico (para aplicar validações numéricas)
            $isNumeric = false;
            foreach ($ruleList as $r) {
                if ($r === 'numeric') {
                    $isNumeric = true;
                    break;
                }
            }

            foreach ($ruleList as $r) {
                if ($r === 'required' && empty($data[$field])) {
                    $errors[$field][] = "O campo {$field} é obrigatório.";
                }

                if ($r === 'numeric' && isset($data[$field]) && !is_numeric($data[$field])) {
                    $errors[$field][] = "O campo {$field} deve ser um número.";
                }

                if (str_starts_with($r, 'in:')) {
                    $allowedValues = explode(',', substr($r, 3));
                    $allowedValues = array_map('trim', $allowedValues);
                    if (isset($data[$field]) && !in_array($data[$field], $allowedValues)) {
                        $errors[$field][] = "O campo {$field} deve ser um dos valores: " . implode(', ', $allowedValues);
                    }
                }

                // Validação min: - numérica se o campo for numérico, senão string
                if (str_starts_with($r, 'min:')) {
                    $min = substr($r, 4);
                    if ($isNumeric && isset($data[$field]) && is_numeric($data[$field])) {
                        $minValue = (float) $min;
                        if ((float) $data[$field] < $minValue) {
                            $errors[$field][] = "O campo {$field} deve ser no mínimo {$minValue}.";
                        }
                    } elseif (!$isNumeric && isset($data[$field])) {
                        $minLength = (int) $min;
                        if (strlen((string) $data[$field]) < $minLength) {
                            $errors[$field][] = "O campo {$field} deve ter no mínimo {$minLength} caracteres.";
                        }
                    }
                }

                if (str_starts_with($r, 'max:')) {
                    $max = (int) substr($r, 4);
                    if (isset($data[$field]) && strlen((string) $data[$field]) > $max) {
                        $errors[$field][] = "O campo {$field} deve ter no máximo {$max} caracteres.";
                    }
                }

                if ($r === 'email' && isset($data[$field]) && !filter_var($data[$field], FILTER_VALIDATE_EMAIL)) {
                    $errors[$field][] = "O campo {$field} deve ser um email válido.";
                }
            }
        }

        return $errors;
    }
}
