<?php

declare(strict_types=1);

namespace App\Framework\Waf\Rules;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Logging\Logger;

/**
 * WAF Rule Engine - evaluates rules against requests
 */
final class RuleEngine
{
    private array $rules = [];

    private array $rulesByCategory = [];

    private array $rulesByPriority = [];

    private array $evaluationStats = [];

    public function __construct(
        private readonly Logger $logger,
        private readonly Duration $maxEvaluationTime,
        private readonly bool $enableCaching = true,
        private readonly int $maxCacheSize = 10000
    ) {
    }

    /**
     * Add rule to engine
     */
    public function addRule(Rule $rule): void
    {
        $this->rules[$rule->id->value] = $rule;

        // Index by category
        $categoryKey = $rule->category->value;
        if (! isset($this->rulesByCategory[$categoryKey])) {
            $this->rulesByCategory[$categoryKey] = [];
        }
        $this->rulesByCategory[$categoryKey][] = $rule;

        // Index by priority (sorted)
        $this->rulesByPriority = [];
        foreach ($this->rules as $r) {
            $this->rulesByPriority[] = $r;
        }

        // Sort by priority (descending - higher priority first)
        usort($this->rulesByPriority, fn (Rule $a, Rule $b) => $b->priority <=> $a->priority);
    }

    /**
     * Add multiple rules
     */
    public function addRules(array $rules): void
    {
        foreach ($rules as $rule) {
            $this->addRule($rule);
        }
    }

    /**
     * Remove rule from engine
     */
    public function removeRule(string $ruleId): void
    {
        if (! isset($this->rules[$ruleId])) {
            return;
        }

        $rule = $this->rules[$ruleId];
        unset($this->rules[$ruleId]);

        // Remove from category index
        $categoryKey = $rule->category->value;
        if (isset($this->rulesByCategory[$categoryKey])) {
            $this->rulesByCategory[$categoryKey] = array_filter(
                $this->rulesByCategory[$categoryKey],
                fn (Rule $r) => $r->id->value !== $ruleId
            );
        }

        // Rebuild priority index
        $this->rulesByPriority = array_filter(
            $this->rulesByPriority,
            fn (Rule $r) => $r->id->value !== $ruleId
        );
    }

    /**
     * Enable rule
     */
    public function enableRule(string $ruleId): void
    {
        if (isset($this->rules[$ruleId])) {
            $this->rules[$ruleId] = $this->rules[$ruleId]->enable();
        }
    }

    /**
     * Disable rule
     */
    public function disableRule(string $ruleId): void
    {
        if (isset($this->rules[$ruleId])) {
            $this->rules[$ruleId] = $this->rules[$ruleId]->disable();
        }
    }

    /**
     * Get rule by ID
     */
    public function getRule(string $ruleId): ?Rule
    {
        return $this->rules[$ruleId] ?? null;
    }

    /**
     * Get all rules
     */
    public function getRules(): array
    {
        return array_values($this->rules);
    }

    /**
     * Get enabled rules only
     */
    public function getEnabledRules(): array
    {
        return array_filter($this->rules, fn (Rule $rule) => $rule->enabled);
    }

    /**
     * Get rules by category
     */
    public function getRulesByCategory(string $category): array
    {
        return $this->rulesByCategory[$category] ?? [];
    }

    /**
     * Get rules by tag
     */
    public function getRulesByTag(string $tag): array
    {
        return array_filter($this->rules, fn (Rule $rule) => $rule->hasTag($tag));
    }

    /**
     * Evaluate all rules against request data
     */
    public function evaluateAll(array $requestData): RuleEvaluationResult
    {
        $startTime = Timestamp::now();
        $matches = [];
        $evaluatedRules = 0;
        $skippedRules = 0;
        $errorCount = 0;
        $errors = [];

        // Process rules by priority
        foreach ($this->rulesByPriority as $rule) {
            if (! $rule->enabled) {
                $skippedRules++;

                continue;
            }

            // Check timeout
            $elapsed = $startTime->diff(Timestamp::now());
            if ($elapsed->isGreaterThan($this->maxEvaluationTime)) {
                $this->logger->warning('Rule evaluation timeout reached', [
                    'elapsed_ms' => $elapsed->toMilliseconds(),
                    'max_ms' => $this->maxEvaluationTime->toMilliseconds(),
                    'evaluated_rules' => $evaluatedRules,
                    'remaining_rules' => count($this->rulesByPriority) - $evaluatedRules - $skippedRules,
                ]);

                break;
            }

            try {
                $match = $rule->evaluate($requestData);
                if ($match !== null) {
                    $matches[] = $match;

                    // Log high-severity matches immediately
                    if ($match->isHighSeverityThreat()) {
                        $this->logger->warning('High-severity threat detected', [
                            'rule_id' => $rule->id->value,
                            'category' => $rule->category->value,
                            'severity' => $rule->severity->value,
                            'message' => $match->message,
                        ]);
                    }
                }

                $evaluatedRules++;

            } catch (\Throwable $e) {
                $errorCount++;
                $errors[] = [
                    'rule_id' => $rule->id->value,
                    'error' => $e->getMessage(),
                    'file' => $e->getFile(),
                    'line' => $e->getLine(),
                ];

                $this->logger->error('Rule evaluation error', [
                    'rule_id' => $rule->id->value,
                    'error' => $e->getMessage(),
                    'trace' => $e->getTraceAsString(),
                ]);
            }
        }

        $endTime = Timestamp::now();
        $totalDuration = $startTime->diff($endTime);

        // Update stats
        $this->updateEvaluationStats($evaluatedRules, $totalDuration, $errorCount);

        return new RuleEvaluationResult(
            matches: $matches,
            evaluatedRules: $evaluatedRules,
            skippedRules: $skippedRules,
            errorCount: $errorCount,
            errors: $errors,
            evaluationTime: $totalDuration,
            timestamp: $endTime
        );
    }

    /**
     * Evaluate specific rule categories only
     */
    public function evaluateCategories(array $categories, array $requestData): RuleEvaluationResult
    {
        $startTime = Timestamp::now();
        $matches = [];
        $evaluatedRules = 0;
        $errorCount = 0;
        $errors = [];

        foreach ($categories as $category) {
            $categoryRules = $this->getRulesByCategory($category);

            foreach ($categoryRules as $rule) {
                if (! $rule->enabled) {
                    continue;
                }

                try {
                    $match = $rule->evaluate($requestData);
                    if ($match !== null) {
                        $matches[] = $match;
                    }
                    $evaluatedRules++;

                } catch (\Throwable $e) {
                    $errorCount++;
                    $errors[] = [
                        'rule_id' => $rule->id->value,
                        'error' => $e->getMessage(),
                    ];
                }
            }
        }

        $endTime = Timestamp::now();
        $totalDuration = $startTime->diff($endTime);

        return new RuleEvaluationResult(
            matches: $matches,
            evaluatedRules: $evaluatedRules,
            skippedRules: 0,
            errorCount: $errorCount,
            errors: $errors,
            evaluationTime: $totalDuration,
            timestamp: $endTime
        );
    }

    /**
     * Quick evaluation for high-priority rules only
     */
    public function quickEvaluate(array $requestData): RuleEvaluationResult
    {
        // Only evaluate rules with priority >= 80
        $highPriorityRules = array_filter(
            $this->rulesByPriority,
            fn (Rule $rule) => $rule->enabled && $rule->priority >= 80
        );

        $startTime = Timestamp::now();
        $matches = [];
        $evaluatedRules = 0;

        foreach ($highPriorityRules as $rule) {
            try {
                $match = $rule->evaluate($requestData);
                if ($match !== null) {
                    $matches[] = $match;
                }
                $evaluatedRules++;

            } catch (\Throwable $e) {
                $this->logger->error('Quick evaluation error', [
                    'rule_id' => $rule->id->value,
                    'error' => $e->getMessage(),
                ]);
            }
        }

        $endTime = Timestamp::now();
        $totalDuration = $startTime->diff($endTime);

        return new RuleEvaluationResult(
            matches: $matches,
            evaluatedRules: $evaluatedRules,
            skippedRules: count($this->getEnabledRules()) - count($highPriorityRules),
            errorCount: 0,
            errors: [],
            evaluationTime: $totalDuration,
            timestamp: $endTime
        );
    }

    /**
     * Get engine statistics
     */
    public function getStatistics(): array
    {
        $enabledRules = $this->getEnabledRules();
        $expensiveRules = array_filter($enabledRules, fn (Rule $rule) => $rule->isExpensive());
        $owaspRules = array_filter($enabledRules, fn (Rule $rule) => $rule->isOwaspTop10());

        $categoryStats = [];
        foreach ($this->rulesByCategory as $category => $rules) {
            $categoryStats[$category] = [
                'total' => count($rules),
                'enabled' => count(array_filter($rules, fn (Rule $r) => $r->enabled)),
                'expensive' => count(array_filter($rules, fn (Rule $r) => $r->enabled && $r->isExpensive())),
            ];
        }

        return [
            'total_rules' => count($this->rules),
            'enabled_rules' => count($enabledRules),
            'expensive_rules' => count($expensiveRules),
            'owasp_top10_rules' => count($owaspRules),
            'categories' => array_keys($this->rulesByCategory),
            'category_stats' => $categoryStats,
            'evaluation_stats' => $this->evaluationStats,
        ];
    }

    /**
     * Get performance metrics
     */
    public function getPerformanceMetrics(): array
    {
        return [
            'avg_evaluation_time_ms' => $this->evaluationStats['avg_evaluation_time'] ?? 0,
            'max_evaluation_time_ms' => $this->maxEvaluationTime->toMilliseconds(),
            'total_evaluations' => $this->evaluationStats['total_evaluations'] ?? 0,
            'total_errors' => $this->evaluationStats['total_errors'] ?? 0,
            'success_rate' => $this->calculateSuccessRate(),
            'cache_enabled' => $this->enableCaching,
            'cache_max_size' => $this->maxCacheSize,
        ];
    }

    /**
     * Clear all rules
     */
    public function clearRules(): void
    {
        $this->rules = [];
        $this->rulesByCategory = [];
        $this->rulesByPriority = [];
    }

    /**
     * Update evaluation statistics
     */
    private function updateEvaluationStats(int $evaluatedRules, Duration $duration, int $errorCount): void
    {
        if (! isset($this->evaluationStats['total_evaluations'])) {
            $this->evaluationStats['total_evaluations'] = 0;
            $this->evaluationStats['total_duration_ms'] = 0;
            $this->evaluationStats['total_errors'] = 0;
        }

        $this->evaluationStats['total_evaluations']++;
        $this->evaluationStats['total_duration_ms'] += $duration->toMilliseconds();
        $this->evaluationStats['total_errors'] += $errorCount;
        $this->evaluationStats['avg_evaluation_time'] =
            $this->evaluationStats['total_duration_ms'] / $this->evaluationStats['total_evaluations'];
        $this->evaluationStats['last_evaluation'] = [
            'rules_evaluated' => $evaluatedRules,
            'duration_ms' => $duration->toMilliseconds(),
            'errors' => $errorCount,
        ];
    }

    /**
     * Calculate success rate
     */
    private function calculateSuccessRate(): float
    {
        $totalEvaluations = $this->evaluationStats['total_evaluations'] ?? 0;
        $totalErrors = $this->evaluationStats['total_errors'] ?? 0;

        if ($totalEvaluations === 0) {
            return 100.0;
        }

        return ((($totalEvaluations - $totalErrors) / $totalEvaluations) * 100.0);
    }
}
