<?php

declare(strict_types=1);

namespace App\Framework\Waf\Analysis\ValueObjects;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Http\IpAddress;
use App\Framework\UserAgent\UserAgent;

/**
 * Structured representation of request data for analysis
 */
final readonly class RequestAnalysisData
{
    public function __construct(
        public string $method,
        public string $url,
        public string $path,
        public string $queryString,
        public array $headers,
        public array $queryParameters,
        public array $postParameters,
        public array $cookies,
        public string $body,
        public array $files,
        public ?IpAddress $clientIp = null,
        public ?UserAgent $userAgent = null,
        public ?string $contentType = null,
        public ?Byte $contentLength = null,
        public ?string $protocol = null,
        public ?Timestamp $timestamp = null,
        public array $metadata = []
    ) {
    }

    /**
     * Create from HTTP request array
     */
    public static function fromArray(array $requestData): self
    {
        $headers = $requestData['headers'] ?? [];
        $clientIp = isset($headers['X-Forwarded-For']) || isset($headers['X-Real-IP'])
            ? IpAddress::fromString($headers['X-Forwarded-For'] ?? $headers['X-Real-IP'])
            : null;

        $userAgent = isset($headers['User-Agent'])
            ? UserAgent::fromString($headers['User-Agent'])
            : null;

        $contentLength = isset($headers['Content-Length'])
            ? Byte::fromBytes((int) $headers['Content-Length'])
            : null;

        // Parse URL components
        $url = $requestData['url'] ?? '/';
        $parsed = parse_url($url);
        $path = $parsed['path'] ?? '/';
        $queryString = $parsed['query'] ?? '';

        return new self(
            method: strtoupper($requestData['method'] ?? 'GET'),
            url: $url,
            path: $path,
            queryString: $queryString,
            headers: $headers,
            queryParameters: $requestData['query'] ?? [],
            postParameters: $requestData['post'] ?? [],
            cookies: $requestData['cookies'] ?? [],
            body: $requestData['body'] ?? '',
            files: $requestData['files'] ?? [],
            clientIp: $clientIp,
            userAgent: $userAgent,
            contentType: $headers['Content-Type'] ?? null,
            contentLength: $contentLength,
            protocol: $requestData['protocol'] ?? 'HTTP/1.1',
            timestamp: Timestamp::fromFloat(microtime(true)),
            metadata: $requestData['metadata'] ?? []
        );
    }

    /**
     * Create minimal data for testing
     */
    public static function minimal(
        string $method = 'GET',
        string $path = '/',
        array $headers = []
    ): self {
        return new self(
            method: $method,
            url: $path,
            path: $path,
            queryString: '',
            headers: $headers,
            queryParameters: [],
            postParameters: [],
            cookies: [],
            body: '',
            files: [],
            timestamp: Timestamp::fromFloat(microtime(true))
        );
    }

    /**
     * Check if request has body content
     */
    public function hasBody(): bool
    {
        return ! empty($this->body) || in_array($this->method, ['POST', 'PUT', 'PATCH', 'DELETE']);
    }

    /**
     * Check if request contains file uploads
     */
    public function hasFiles(): bool
    {
        return ! empty($this->files);
    }

    /**
     * Check if request is JSON
     */
    public function isJson(): bool
    {
        return $this->contentType !== null &&
               str_contains(strtolower($this->contentType), 'application/json');
    }

    /**
     * Check if request is XML
     */
    public function isXml(): bool
    {
        return $this->contentType !== null &&
               (str_contains(strtolower($this->contentType), 'application/xml') ||
                str_contains(strtolower($this->contentType), 'text/xml'));
    }

    /**
     * Check if request is form data
     */
    public function isFormData(): bool
    {
        return $this->contentType !== null &&
               str_contains(strtolower($this->contentType), 'application/x-www-form-urlencoded');
    }

    /**
     * Check if request is multipart
     */
    public function isMultipart(): bool
    {
        return $this->contentType !== null &&
               str_contains(strtolower($this->contentType), 'multipart/form-data');
    }

    /**
     * Get request size in bytes
     */
    public function getSize(): Byte
    {
        if ($this->contentLength !== null) {
            return $this->contentLength;
        }

        // Calculate approximate size
        $headerSize = array_sum(array_map(
            fn ($name, $value) => strlen($name) + strlen($value) + 4, // +4 for ": \r\n"
            array_keys($this->headers),
            array_values($this->headers)
        ));

        $bodySize = strlen($this->body);
        $urlSize = strlen($this->url);

        return Byte::fromBytes($headerSize + $bodySize + $urlSize + 50); // +50 for method, protocol, etc.
    }

    /**
     * Get header value (case-insensitive)
     */
    public function getHeader(string $name): ?string
    {
        foreach ($this->headers as $headerName => $value) {
            if (strcasecmp($headerName, $name) === 0) {
                return $value;
            }
        }

        return null;
    }

    /**
     * Check if header exists (case-insensitive)
     */
    public function hasHeader(string $name): bool
    {
        return $this->getHeader($name) !== null;
    }

    /**
     * Get all parameters (query + post)
     */
    public function getAllParameters(): array
    {
        return array_merge($this->queryParameters, $this->postParameters);
    }

    /**
     * Get parameter value from query or post
     */
    public function getParameter(string $name): ?string
    {
        return $this->queryParameters[$name] ?? $this->postParameters[$name] ?? null;
    }

    /**
     * Check if parameter exists
     */
    public function hasParameter(string $name): bool
    {
        return isset($this->queryParameters[$name]) || isset($this->postParameters[$name]);
    }

    /**
     * Get cookie value
     */
    public function getCookie(string $name): ?string
    {
        return $this->cookies[$name] ?? null;
    }

    /**
     * Check if cookie exists
     */
    public function hasCookie(string $name): bool
    {
        return isset($this->cookies[$name]);
    }

    /**
     * Get file upload info
     */
    public function getFile(string $name): ?array
    {
        return $this->files[$name] ?? null;
    }

    /**
     * Get all uploaded file names
     */
    public function getFileNames(): array
    {
        return array_keys($this->files);
    }

    /**
     * Get total number of uploaded files
     */
    public function getFileCount(): int
    {
        return count($this->files);
    }

    /**
     * Check if request is from localhost
     */
    public function isLocalhost(): bool
    {
        return $this->clientIp !== null && $this->clientIp->isLoopback();
    }

    /**
     * Check if request is from private network
     */
    public function isPrivateNetwork(): bool
    {
        return $this->clientIp !== null && $this->clientIp->isPrivate();
    }

    /**
     * Get request summary for logging
     */
    public function getSummary(): array
    {
        return [
            'method' => $this->method,
            'path' => $this->path,
            'client_ip' => $this->clientIp?->toString(),
            'user_agent' => $this->userAgent?->toString(),
            'content_type' => $this->contentType,
            'size_bytes' => $this->getSize()->toBytes(),
            'has_body' => $this->hasBody(),
            'has_files' => $this->hasFiles(),
            'parameter_count' => count($this->getAllParameters()),
            'cookie_count' => count($this->cookies),
            'header_count' => count($this->headers),
            'timestamp' => $this->timestamp?->format('c'),
        ];
    }

    /**
     * Convert to analysis-friendly array
     */
    public function toAnalysisArray(): array
    {
        return [
            'method' => $this->method,
            'url' => $this->url,
            'path' => $this->path,
            'query_string' => $this->queryString,
            'headers' => $this->headers,
            'query' => $this->queryParameters,
            'post' => $this->postParameters,
            'cookies' => $this->cookies,
            'body' => $this->body,
            'files' => $this->files,
            'client_ip' => $this->clientIp?->toString(),
            'user_agent' => $this->userAgent?->toString(),
            'content_type' => $this->contentType,
            'content_length' => $this->contentLength?->toBytes(),
            'protocol' => $this->protocol,
            'is_json' => $this->isJson(),
            'is_xml' => $this->isXml(),
            'is_form_data' => $this->isFormData(),
            'is_multipart' => $this->isMultipart(),
            'has_body' => $this->hasBody(),
            'has_files' => $this->hasFiles(),
            'size_bytes' => $this->getSize()->toBytes(),
            'timestamp' => $this->timestamp?->format('c'),
            'metadata' => $this->metadata,
        ];
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'method' => $this->method,
            'url' => $this->url,
            'path' => $this->path,
            'query_string' => $this->queryString,
            'headers' => $this->headers,
            'query_parameters' => $this->queryParameters,
            'post_parameters' => $this->postParameters,
            'cookies' => $this->cookies,
            'body' => strlen($this->body) > 1000 ? substr($this->body, 0, 1000) . '...' : $this->body,
            'files' => $this->files,
            'client_ip' => $this->clientIp?->toString(),
            'user_agent' => $this->userAgent?->toString(),
            'content_type' => $this->contentType,
            'content_length' => $this->contentLength?->toBytes(),
            'protocol' => $this->protocol,
            'timestamp' => $this->timestamp?->format('c'),
            'summary' => $this->getSummary(),
            'metadata' => $this->metadata,
        ];
    }
}
