<?php

declare(strict_types=1);

namespace App\Framework\Waf\MachineLearning;

use App\Framework\Waf\MachineLearning\ValueObjects\BehaviorFeatures;
use App\Framework\Waf\MachineLearning\ValueObjects\RequestSequence;
use App\Framework\Http\Request;
use App\Infrastructure\GeoIp\GeoIp;

/**
 * Behavior Pattern Feature Extractor for Advanced WAF
 *
 * Extracts 8 behavioral features from request sequences:
 * 1. request_frequency: Requests per time window
 * 2. endpoint_diversity: Shannon entropy of endpoint distribution
 * 3. parameter_entropy: Randomness in parameter values
 * 4. user_agent_consistency: Consistency score across requests
 * 5. geographic_anomaly: IP geolocation change detection (country-based)
 * 6. time_pattern_regularity: Request timing pattern analysis
 * 7. payload_similarity: Similarity between consecutive payloads
 * 8. http_method_distribution: Distribution of HTTP methods used
 *
 * Based on N+1 Detection ML Framework patterns with FeatureExtractorInterface
 */
final readonly class BehaviorPatternExtractor
{
    public function __construct(
        private GeoIp $geoIp,
        private float $minConfidence = 0.6
    ) {}

    /**
     * Extract behavioral features from request sequence
     *
     * @param RequestSequence $sequence Recent request history for analysis
     * @return BehaviorFeatures 8-feature vector for ML analysis
     */
    public function extract(RequestSequence $sequence): BehaviorFeatures
    {
        if ($sequence->isEmpty()) {
            return BehaviorFeatures::empty();
        }

        return new BehaviorFeatures(
            requestFrequency: $this->calculateRequestFrequency($sequence),
            endpointDiversity: $this->calculateEndpointDiversity($sequence),
            parameterEntropy: $this->calculateParameterEntropy($sequence),
            userAgentConsistency: $this->calculateUserAgentConsistency($sequence),
            geographicAnomaly: $this->detectGeographicAnomaly($sequence),
            timePatternRegularity: $this->analyzeTimePatternRegularity($sequence),
            payloadSimilarity: $this->calculatePayloadSimilarity($sequence),
            httpMethodDistribution: $this->calculateHttpMethodDistribution($sequence)
        );
    }

    /**
     * Feature 1: Request Frequency
     * Calculates requests per second over the sequence window
     */
    private function calculateRequestFrequency(RequestSequence $sequence): float
    {
        $requests = $sequence->getRequests();
        $timeWindowSeconds = $sequence->getTimeWindowSeconds();

        if ($timeWindowSeconds === 0.0) {
            return 0.0;
        }

        return count($requests) / $timeWindowSeconds;
    }

    /**
     * Feature 2: Endpoint Diversity
     * Shannon entropy of endpoint distribution - higher = more diverse
     */
    private function calculateEndpointDiversity(RequestSequence $sequence): float
    {
        $endpointCounts = [];

        foreach ($sequence->getRequests() as $request) {
            $path = $request->path;
            $endpointCounts[$path] = ($endpointCounts[$path] ?? 0) + 1;
        }

        $total = array_sum($endpointCounts);
        if ($total === 0) {
            return 0.0;
        }

        $entropy = 0.0;
        foreach ($endpointCounts as $count) {
            $probability = $count / $total;
            if ($probability > 0) {
                $entropy -= $probability * log($probability, 2);
            }
        }

        return $entropy;
    }

    /**
     * Feature 3: Parameter Entropy
     * Average entropy of query parameters across requests
     */
    private function calculateParameterEntropy(RequestSequence $sequence): float
    {
        $entropies = [];

        foreach ($sequence->getRequests() as $request) {
            $params = array_merge(
                $request->queryParams ?? [],
                $request->parsedBody->data ?? []
            );

            if (empty($params)) {
                continue;
            }

            $entropy = $this->calculateStringEntropy(json_encode($params));
            $entropies[] = $entropy;
        }

        return empty($entropies) ? 0.0 : array_sum($entropies) / count($entropies);
    }

    /**
     * Feature 4: User Agent Consistency
     * Score from 0 (completely inconsistent) to 1 (perfectly consistent)
     */
    private function calculateUserAgentConsistency(RequestSequence $sequence): float
    {
        $userAgents = [];

        foreach ($sequence->getRequests() as $request) {
            $userAgent = $request->headers->getFirst('User-Agent') ?? 'unknown';
            $userAgents[] = $userAgent;
        }

        if (empty($userAgents)) {
            return 1.0;
        }

        $uniqueCount = count(array_unique($userAgents));
        $totalCount = count($userAgents);

        // Consistency = 1 - (variation ratio)
        return 1.0 - (($uniqueCount - 1) / max($totalCount - 1, 1));
    }

    /**
     * Feature 5: Geographic Anomaly
     * Detects unusual country changes - score 0 (normal) to 1 (anomalous)
     */
    private function detectGeographicAnomaly(RequestSequence $sequence): float
    {
        $countries = [];

        foreach ($sequence->getRequests() as $request) {
            $ip = $request->server->getRemoteAddr();

            // Skip local/private IPs
            if ($ip->isLocal()) {
                continue;
            }

            $countryCode = $this->geoIp->getCountryCode($ip);
            $countries[] = $countryCode->toString();
        }

        if (count($countries) < 2) {
            return 0.0; // Not enough data
        }

        // Calculate country change frequency
        $uniqueCountries = count(array_unique($countries));
        $totalRequests = count($countries);

        // High country diversity = anomalous (potential proxy/VPN hopping)
        // Normalize: 1 country = 0.0, all different = 1.0
        return ($uniqueCountries - 1) / max($totalRequests - 1, 1);
    }

    /**
     * Feature 6: Time Pattern Regularity
     * Analyzes request timing patterns - score 0 (irregular) to 1 (regular/automated)
     */
    private function analyzeTimePatternRegularity(RequestSequence $sequence): float
    {
        $timestamps = array_map(
            fn($request) => $request->timestamp ?? time(),
            $sequence->getRequests()
        );

        if (count($timestamps) < 3) {
            return 0.0;
        }

        // Calculate inter-arrival times
        $intervals = [];
        for ($i = 1; $i < count($timestamps); $i++) {
            $intervals[] = $timestamps[$i] - $timestamps[$i - 1];
        }

        // Calculate coefficient of variation (CV)
        $mean = array_sum($intervals) / count($intervals);
        $stdDev = sqrt($this->calculateVariance($intervals, $mean));

        if ($mean === 0.0) {
            return 0.0;
        }

        $cv = $stdDev / $mean;

        // Low CV = regular pattern (potential bot)
        // Invert and normalize: regular patterns get higher scores
        return max(0.0, 1.0 - min(1.0, $cv));
    }

    /**
     * Feature 7: Payload Similarity
     * Average similarity between consecutive request payloads
     */
    private function calculatePayloadSimilarity(RequestSequence $sequence): float
    {
        $payloads = array_map(
            fn($request) => $request->body ?? '',
            $sequence->getRequests()
        );

        if (count($payloads) < 2) {
            return 0.0;
        }

        $similarities = [];
        for ($i = 1; $i < count($payloads); $i++) {
            $similarity = $this->calculateStringSimilarity($payloads[$i - 1], $payloads[$i]);
            $similarities[] = $similarity;
        }

        return array_sum($similarities) / count($similarities);
    }

    /**
     * Feature 8: HTTP Method Distribution
     * Entropy of HTTP method usage - higher = more diverse
     */
    private function calculateHttpMethodDistribution(RequestSequence $sequence): float
    {
        $methodCounts = [];

        foreach ($sequence->getRequests() as $request) {
            $method = $request->method->value;
            $methodCounts[$method] = ($methodCounts[$method] ?? 0) + 1;
        }

        $total = array_sum($methodCounts);
        if ($total === 0) {
            return 0.0;
        }

        $entropy = 0.0;
        foreach ($methodCounts as $count) {
            $probability = $count / $total;
            if ($probability > 0) {
                $entropy -= $probability * log($probability, 2);
            }
        }

        // Normalize by max possible entropy (log2(9) for 9 HTTP methods)
        $maxEntropy = log(9, 2);
        return $entropy / $maxEntropy;
    }

    // ===== Helper Methods =====

    /**
     * Calculate Shannon entropy of a string
     */
    private function calculateStringEntropy(string $data): float
    {
        if (empty($data)) {
            return 0.0;
        }

        $length = strlen($data);
        $frequencies = array_count_values(str_split($data));

        $entropy = 0.0;
        foreach ($frequencies as $count) {
            $probability = $count / $length;
            $entropy -= $probability * log($probability, 2);
        }

        return $entropy;
    }

    /**
     * Calculate string similarity using Levenshtein distance
     */
    private function calculateStringSimilarity(string $str1, string $str2): float
    {
        $maxLen = max(strlen($str1), strlen($str2));
        if ($maxLen === 0) {
            return 1.0;
        }

        $distance = levenshtein($str1, $str2);
        return 1.0 - ($distance / $maxLen);
    }

    /**
     * Calculate variance of numeric array
     */
    private function calculateVariance(array $values, float $mean): float
    {
        if (empty($values)) {
            return 0.0;
        }

        $squaredDiffs = array_map(fn($val) => ($val - $mean) ** 2, $values);
        return array_sum($squaredDiffs) / count($values);
    }
}
