<?php

declare(strict_types=1);

namespace App\Framework\Waf;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\ValueObjects\DetectionCollection;
use App\Framework\Waf\ValueObjects\ResultMetadata;

/**
 * Result from a WAF security layer analysis
 */
final readonly class LayerResult
{
    // Action constants for middleware compatibility
    public const string ACTION_PASS = 'pass';
    public const string ACTION_BLOCK = 'block';
    public const string ACTION_SUSPICIOUS = 'suspicious';

    public function __construct(
        public string $layerName,
        public Percentage $threatScore,
        public LayerStatus $status,
        public DetectionCollection $detections,
        public string $reason,
        public ?Duration $executionDuration = null,
        public ?Timestamp $timestamp = null,
        public ?ResultMetadata $metadata = null
    ) {
    }

    /**
     * Create a result indicating a threat was detected
     */
    public static function threat(
        string $layerName,
        string $reason,
        LayerStatus $status = LayerStatus::THREAT_DETECTED,
        array $detections = [],
        ?Duration $executionDuration = null,
        ?ResultMetadata $metadata = null
    ): self {
        // Calculate threat score based on detections
        $maxRiskScore = 0.0;
        foreach ($detections as $detection) {
            $threatScore = $detection->getThreatScore()->getValue();
            if ($threatScore > $maxRiskScore) {
                $maxRiskScore = $threatScore;
            }
        }

        return new self(
            layerName: $layerName,
            threatScore: Percentage::from($maxRiskScore),
            status: $status,
            detections: DetectionCollection::fromArray($detections),
            reason: $reason,
            executionDuration: $executionDuration,
            metadata: $metadata ?? ResultMetadata::empty()
        );
    }

    /**
     * Create a result indicating the request is clean
     */
    public static function clean(string $layerName, string $reason = '', ?Duration $executionDuration = null, ?ResultMetadata $metadata = null): self
    {
        return new self(
            layerName: $layerName,
            threatScore: Percentage::from(0.0),
            status: LayerStatus::CLEAN,
            detections: DetectionCollection::empty(),
            reason: $reason,
            executionDuration: $executionDuration,
            metadata: $metadata ?? ResultMetadata::empty()
        );
    }

    /**
     * Create a neutral result (layer couldn't determine threat level)
     */
    public static function neutral(string $layerName, string $reason = '', ?ResultMetadata $metadata = null): self
    {
        return new self(
            layerName: $layerName,
            threatScore: Percentage::from(50.0), // Neutral score
            status: LayerStatus::NEUTRAL,
            detections: DetectionCollection::empty(),
            reason: $reason,
            metadata: $metadata ?? ResultMetadata::empty()
        );
    }

    /**
     * Create a result indicating suspicious activity (lower confidence threat)
     */
    public static function suspicious(string $layerName, string $reason, array $detections = [], ?ResultMetadata $metadata = null): self
    {
        return new self(
            layerName: $layerName,
            threatScore: Percentage::from(60.0), // Moderate threat score
            status: LayerStatus::SUSPICIOUS,
            detections: DetectionCollection::fromArray($detections),
            reason: $reason,
            metadata: $metadata ?? ResultMetadata::empty()
        );
    }

    /**
     * Create a result indicating layer error/failure
     */
    public static function error(string $layerName, string $reason, ?Duration $executionDuration = null, ?ResultMetadata $metadata = null): self
    {
        return new self(
            layerName: $layerName,
            threatScore: Percentage::from(0.0), // Don't penalize on error
            status: LayerStatus::ERROR,
            detections: DetectionCollection::empty(),
            reason: $reason,
            executionDuration: $executionDuration,
            metadata: $metadata ?? ResultMetadata::empty()
        );
    }

    /**
     * Check if this result indicates a threat
     */
    public function isThreat(): bool
    {
        return $this->status === LayerStatus::THREAT_DETECTED;
    }

    /**
     * Check if this result is clean
     */
    public function isClean(): bool
    {
        return $this->status === LayerStatus::CLEAN;
    }

    /**
     * Check if layer had an error
     */
    public function hasError(): bool
    {
        return $this->status === LayerStatus::ERROR;
    }

    /**
     * Get recommended action based on layer result
     */
    public function getAction(): string
    {
        return match ($this->status) {
            LayerStatus::THREAT_DETECTED => self::ACTION_BLOCK,
            LayerStatus::CLEAN => self::ACTION_PASS,
            LayerStatus::NEUTRAL => self::ACTION_SUSPICIOUS,
            LayerStatus::ERROR => self::ACTION_PASS, // Fail open
            LayerStatus::SKIPPED => self::ACTION_PASS, // Continue processing
            LayerStatus::TIMEOUT => self::ACTION_PASS, // Fail open on timeout
        };
    }

    /**
     * Get the reason/message for this result
     */
    public function getMessage(): string
    {
        return $this->reason;
    }

    /**
     * Get the layer name
     */
    public function getLayerName(): string
    {
        return $this->layerName;
    }

    /**
     * Check if result has detections
     */
    public function hasDetections(): bool
    {
        return ! $this->detections->isEmpty();
    }

    /**
     * Get the detections collection
     */
    public function getDetections(): DetectionCollection
    {
        return $this->detections;
    }

    /**
     * Create new result with execution duration
     */
    public function withExecutionDuration(Duration $duration): self
    {
        return new self(
            layerName: $this->layerName,
            threatScore: $this->threatScore,
            status: $this->status,
            detections: $this->detections,
            reason: $this->reason,
            executionDuration: $duration,
            timestamp: $this->timestamp,
            metadata: $this->metadata
        );
    }

    /**
     * Create new result with timestamp
     */
    public function withTimestamp(Timestamp $timestamp): self
    {
        return new self(
            layerName: $this->layerName,
            threatScore: $this->threatScore,
            status: $this->status,
            detections: $this->detections,
            reason: $this->reason,
            executionDuration: $this->executionDuration,
            timestamp: $timestamp,
            metadata: $this->metadata
        );
    }

    /**
     * Add metadata to result
     */
    public function withMetadata(ResultMetadata $additionalMetadata): self
    {
        $combinedMetadata = $this->metadata?->merge($additionalMetadata) ?? $additionalMetadata;

        return new self(
            layerName: $this->layerName,
            threatScore: $this->threatScore,
            status: $this->status,
            detections: $this->detections,
            reason: $this->reason,
            executionDuration: $this->executionDuration,
            timestamp: $this->timestamp,
            metadata: $combinedMetadata
        );
    }
}
