<?php

declare(strict_types=1);

namespace App\Framework\Waf\Rules\ValueObjects;

use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\DetectionCategory;
use App\Framework\Waf\DetectionSeverity;
use App\Framework\Waf\ValueObjects\RuleId;

/**
 * Represents a successful rule match
 */
final readonly class RuleMatch
{
    public function __construct(
        public RuleId $ruleId,
        public string $ruleName,
        public DetectionCategory $category,
        public DetectionSeverity $severity,
        public RuleCondition $condition,
        public string $matchedValue,
        public array $matches,
        public ?Percentage $confidence = null,
        public ?string $message = null,
        public ?Timestamp $timestamp = null,
        public array $metadata = []
    ) {
    }

    /**
     * Create rule match from condition evaluation
     */
    public static function fromCondition(
        RuleId $ruleId,
        string $ruleName,
        DetectionCategory $category,
        DetectionSeverity $severity,
        RuleCondition $condition,
        array $requestData,
        ?string $message = null
    ): self {
        $matchedValue = $condition->extractValue($requestData) ?? '';
        $matches = $condition->getMatches($requestData);

        // Calculate confidence based on match quality
        $confidence = self::calculateConfidence($matches, $condition->pattern);

        $generatedMessage = $message ?? self::generateMessage($condition, $matchedValue);

        return new self(
            ruleId: $ruleId,
            ruleName: $ruleName,
            category: $category,
            severity: $severity,
            condition: $condition,
            matchedValue: $matchedValue,
            matches: $matches,
            confidence: $confidence,
            message: $generatedMessage,
            timestamp: Timestamp::now(),
            metadata: [
                'target' => $condition->target,
                'pattern_type' => $condition->pattern->isRegex ? 'regex' : 'string',
                'negated' => $condition->negated,
            ]
        );
    }

    /**
     * Create high-confidence match
     */
    public static function highConfidence(
        RuleId $ruleId,
        string $ruleName,
        DetectionCategory $category,
        DetectionSeverity $severity,
        RuleCondition $condition,
        string $matchedValue,
        array $matches,
        string $message
    ): self {
        return new self(
            ruleId: $ruleId,
            ruleName: $ruleName,
            category: $category,
            severity: $severity,
            condition: $condition,
            matchedValue: $matchedValue,
            matches: $matches,
            confidence: Percentage::from(95.0),
            message: $message,
            timestamp: Timestamp::now()
        );
    }

    /**
     * Get sanitized matched value (safe for logging)
     */
    public function getSanitizedMatchedValue(): string
    {
        // Truncate long values
        $value = strlen($this->matchedValue) > 200
            ? substr($this->matchedValue, 0, 200) . '...'
            : $this->matchedValue;

        // Remove potential log injection attacks
        $value = str_replace(["\r", "\n", "\t"], ['\r', '\n', '\t'], $value);

        // Mask sensitive patterns
        if ($this->condition->targetsSensitiveData()) {
            return '***REDACTED***';
        }

        return $value;
    }

    /**
     * Get match excerpt showing context around matches
     */
    public function getMatchExcerpt(int $contextLength = 50): array
    {
        $excerpts = [];

        foreach ($this->matches as $match) {
            if (is_array($match) && isset($match['offset'])) {
                $start = max(0, $match['offset'] - $contextLength);
                $length = strlen($match['match']) + (2 * $contextLength);

                $excerpt = substr($this->matchedValue, $start, $length);
                $relativeOffset = $match['offset'] - $start;

                $excerpts[] = [
                    'excerpt' => $excerpt,
                    'match_start' => $relativeOffset,
                    'match_length' => strlen($match['match']),
                    'absolute_offset' => $match['offset'],
                ];
            }
        }

        return $excerpts;
    }

    /**
     * Check if match indicates high-severity threat
     */
    public function isHighSeverityThreat(): bool
    {
        return $this->severity === DetectionSeverity::CRITICAL ||
               $this->severity === DetectionSeverity::HIGH;
    }

    /**
     * Check if match should trigger immediate blocking
     */
    public function shouldBlock(): bool
    {
        if ($this->isHighSeverityThreat()) {
            return true;
        }

        // High confidence medium threats should also block
        if ($this->severity === DetectionSeverity::MEDIUM &&
            $this->confidence !== null &&
            $this->confidence->getValue() >= 90.0) {
            return true;
        }

        return false;
    }

    /**
     * Check if match should trigger alert
     */
    public function shouldAlert(): bool
    {
        return $this->severity->shouldAlert() ||
               ($this->confidence !== null && $this->confidence->getValue() >= 70.0);
    }

    /**
     * Get OWASP category information
     */
    public function getOwaspInfo(): array
    {
        return [
            'category' => $this->category->value,
            'owasp_rank' => $this->category->getOwaspRank(),
            'is_top10' => $this->category->isOwaspTop10(),
            'description' => $this->category->getDescription(),
        ];
    }

    /**
     * Get rule performance metrics
     */
    public function getPerformanceMetrics(): array
    {
        $complexity = 'low';

        if ($this->condition->pattern->isRegex) {
            $complexity = $this->condition->pattern->isPotentiallyDangerous() ? 'high' : 'medium';
        }

        return [
            'pattern_complexity' => $complexity,
            'pattern_type' => $this->condition->pattern->isRegex ? 'regex' : 'string',
            'match_count' => count($this->matches),
            'value_length' => strlen($this->matchedValue),
            'targets_sensitive_data' => $this->condition->targetsSensitiveData(),
        ];
    }

    /**
     * Add metadata
     */
    public function withMetadata(array $metadata): self
    {
        return new self(
            ruleId: $this->ruleId,
            ruleName: $this->ruleName,
            category: $this->category,
            severity: $this->severity,
            condition: $this->condition,
            matchedValue: $this->matchedValue,
            matches: $this->matches,
            confidence: $this->confidence,
            message: $this->message,
            timestamp: $this->timestamp,
            metadata: array_merge($this->metadata, $metadata)
        );
    }

    /**
     * Convert to array
     */
    public function toArray(): array
    {
        return [
            'rule_id' => $this->ruleId->value,
            'rule_name' => $this->ruleName,
            'category' => $this->category->value,
            'severity' => $this->severity->value,
            'message' => $this->message,
            'confidence' => $this->confidence?->getValue(),
            'matched_value' => $this->getSanitizedMatchedValue(),
            'match_excerpts' => $this->getMatchExcerpt(),
            'timestamp' => $this->timestamp?->toIsoString(),
            'should_block' => $this->shouldBlock(),
            'should_alert' => $this->shouldAlert(),
            'owasp_info' => $this->getOwaspInfo(),
            'performance_metrics' => $this->getPerformanceMetrics(),
            'condition' => $this->condition->toArray(),
            'metadata' => $this->metadata,
        ];
    }

    /**
     * Calculate confidence based on match quality
     */
    private static function calculateConfidence(array $matches, RulePattern $pattern): Percentage
    {
        if (empty($matches)) {
            return Percentage::from(0.0);
        }

        $baseConfidence = 70.0;

        // Boost confidence for regex patterns (more precise)
        if ($pattern->isRegex) {
            $baseConfidence += 10.0;
        }

        // Boost confidence for multiple matches
        $matchBonus = min(20.0, count($matches) * 5.0);
        $baseConfidence += $matchBonus;

        // Reduce confidence for potentially dangerous patterns (could be false positive)
        if ($pattern->isPotentiallyDangerous()) {
            $baseConfidence -= 15.0;
        }

        return Percentage::from(max(10.0, min(100.0, $baseConfidence)));
    }

    /**
     * Generate descriptive message for match
     */
    private static function generateMessage(RuleCondition $condition, string $matchedValue): string
    {
        $target = $condition->getTargetDescription();
        $patternType = $condition->pattern->isRegex ? 'pattern' : 'string';
        $valuePreview = strlen($matchedValue) > 50
            ? substr($matchedValue, 0, 50) . '...'
            : $matchedValue;

        if ($condition->negated) {
            return "Negative match: {$target} does not contain expected {$patternType}";
        }

        return "Suspicious {$patternType} detected in {$target}: '{$valuePreview}'";
    }
}
