<?php

declare(strict_types=1);

namespace App\Framework\Waf\MachineLearning\ValueObjects;

use App\Framework\Http\Request;

/**
 * Sequence of Recent Requests for Behavioral Analysis
 *
 * Immutable collection of requests with time window information
 * Used by BehaviorPatternExtractor for sequence-based feature extraction
 */
final readonly class RequestSequence
{
    /**
     * @param array<Request> $requests Recent requests in chronological order
     * @param float $timeWindowSeconds Time window covered by the sequence
     * @param string $clientIdentifier IP address or other identifier
     */
    public function __construct(
        private array $requests,
        private float $timeWindowSeconds,
        private string $clientIdentifier
    ) {}

    /**
     * Create empty sequence
     */
    public static function empty(string $clientIdentifier = ''): self
    {
        return new self([], 0.0, $clientIdentifier);
    }

    /**
     * Create from request array with automatic time window calculation
     *
     * @param array<Request> $requests
     */
    public static function fromRequests(array $requests, string $clientIdentifier): self
    {
        if (empty($requests)) {
            return self::empty($clientIdentifier);
        }

        // Calculate time window from first to last request
        $timestamps = array_map(fn($req) => $req->timestamp ?? time(), $requests);
        $minTime = min($timestamps);
        $maxTime = max($timestamps);
        $timeWindow = max(1.0, (float) ($maxTime - $minTime)); // Minimum 1 second

        return new self($requests, $timeWindow, $clientIdentifier);
    }

    /**
     * Get all requests in sequence
     *
     * @return array<Request>
     */
    public function getRequests(): array
    {
        return $this->requests;
    }

    /**
     * Get time window in seconds
     */
    public function getTimeWindowSeconds(): float
    {
        return $this->timeWindowSeconds;
    }

    /**
     * Get client identifier (IP address)
     */
    public function getClientIdentifier(): string
    {
        return $this->clientIdentifier;
    }

    /**
     * Check if sequence is empty
     */
    public function isEmpty(): bool
    {
        return empty($this->requests);
    }

    /**
     * Get number of requests in sequence
     */
    public function count(): int
    {
        return count($this->requests);
    }

    /**
     * Get most recent request
     */
    public function getLatestRequest(): ?Request
    {
        if (empty($this->requests)) {
            return null;
        }

        return $this->requests[array_key_last($this->requests)];
    }

    /**
     * Get oldest request
     */
    public function getOldestRequest(): ?Request
    {
        if (empty($this->requests)) {
            return null;
        }

        return $this->requests[array_key_first($this->requests)];
    }

    /**
     * Filter requests by path pattern
     */
    public function filterByPath(string $pattern): self
    {
        $filtered = array_filter(
            $this->requests,
            fn($req) => preg_match($pattern, $req->path) === 1
        );

        return new self($filtered, $this->timeWindowSeconds, $this->clientIdentifier);
    }

    /**
     * Filter requests by HTTP method
     */
    public function filterByMethod(string $method): self
    {
        $filtered = array_filter(
            $this->requests,
            fn($req) => $req->method->value === strtoupper($method)
        );

        return new self($filtered, $this->timeWindowSeconds, $this->clientIdentifier);
    }

    /**
     * Get requests in time window (last N seconds)
     */
    public function getRecentRequests(int $seconds): self
    {
        if (empty($this->requests)) {
            return $this;
        }

        $cutoffTime = time() - $seconds;
        $recent = array_filter(
            $this->requests,
            fn($req) => ($req->timestamp ?? time()) >= $cutoffTime
        );

        return self::fromRequests($recent, $this->clientIdentifier);
    }

    /**
     * Merge with another sequence
     */
    public function merge(self $other): self
    {
        if ($this->clientIdentifier !== $other->clientIdentifier) {
            throw new \InvalidArgumentException('Cannot merge sequences from different clients');
        }

        $allRequests = array_merge($this->requests, $other->requests);

        // Sort by timestamp
        usort($allRequests, function ($a, $b) {
            $timeA = $a->timestamp ?? 0;
            $timeB = $b->timestamp ?? 0;
            return $timeA <=> $timeB;
        });

        return self::fromRequests($allRequests, $this->clientIdentifier);
    }

    /**
     * Limit sequence to maximum number of requests (keep most recent)
     */
    public function limitToLast(int $maxRequests): self
    {
        if (count($this->requests) <= $maxRequests) {
            return $this;
        }

        $limited = array_slice($this->requests, -$maxRequests);
        return self::fromRequests($limited, $this->clientIdentifier);
    }

    /**
     * Get sequence statistics
     */
    public function getStatistics(): array
    {
        if (empty($this->requests)) {
            return [
                'count' => 0,
                'time_window_seconds' => 0.0,
                'requests_per_second' => 0.0,
                'unique_endpoints' => 0,
                'unique_methods' => 0,
            ];
        }

        $uniqueEndpoints = count(array_unique(array_map(fn($req) => $req->path, $this->requests)));
        $uniqueMethods = count(array_unique(array_map(fn($req) => $req->method->value, $this->requests)));

        return [
            'count' => count($this->requests),
            'time_window_seconds' => $this->timeWindowSeconds,
            'requests_per_second' => $this->timeWindowSeconds > 0
                ? count($this->requests) / $this->timeWindowSeconds
                : 0.0,
            'unique_endpoints' => $uniqueEndpoints,
            'unique_methods' => $uniqueMethods,
        ];
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'client_identifier' => $this->clientIdentifier,
            'request_count' => count($this->requests),
            'time_window_seconds' => $this->timeWindowSeconds,
            'statistics' => $this->getStatistics(),
        ];
    }
}
