<?php

declare(strict_types=1);

namespace App\Framework\DDoS\Response\ValueObjects;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\Http\Headers;
use App\Framework\Http\Status;

/**
 * DDoS Response Result - Encapsulates the mitigation response
 */
final readonly class DDoSResponse
{
    public function __construct(
        public ResponseType $type,
        public string $reason,
        public ?string $message = null,
        public ?Headers $headers = null,
        public ?string $body = null,
        public ?Status $httpStatus = null,
        public ?Duration $processingTime = null,
        public ?Timestamp $timestamp = null,
        public array $metadata = []
    ) {
    }

    /**
     * Create an allow response
     */
    public static function allow(string $reason, ?string $message = null): self
    {
        return new self(
            type: ResponseType::ALLOW,
            reason: $reason,
            message: $message,
            httpStatus: Status::OK,
            timestamp: Timestamp::fromFloat(microtime(true))
        );
    }

    /**
     * Create a block response
     */
    public static function block(
        string $reason,
        ?string $message = null,
        ?Duration $blockDuration = null
    ): self {
        $headers = new Headers([
            'Content-Type' => 'application/json',
            'X-DDoS-Protection' => 'blocked',
            'X-Block-Reason' => $reason,
        ]);

        if ($blockDuration) {
            $headers = $headers->with('Retry-After', (string) $blockDuration->toSeconds());
        }

        $body = json_encode([
            'error' => 'Request blocked',
            'message' => $message ?? 'Your request has been blocked due to suspicious activity',
            'reason' => $reason,
            'timestamp' => date('c'),
            'retry_after' => $blockDuration?->toSeconds(),
        ], JSON_PRETTY_PRINT);

        return new self(
            type: ResponseType::BLOCK,
            reason: $reason,
            message: $message,
            headers: $headers,
            body: $body,
            httpStatus: Status::FORBIDDEN,
            timestamp: Timestamp::fromFloat(microtime(true)),
            metadata: ['block_duration' => $blockDuration?->toSeconds()]
        );
    }

    /**
     * Create a rate limit response
     */
    public static function rateLimit(
        string $reason,
        int $limit,
        int $remaining,
        ?Duration $resetTime = null,
        ?string $message = null
    ): self {
        $headers = new Headers([
            'Content-Type' => 'application/json',
            'X-RateLimit-Limit' => (string) $limit,
            'X-RateLimit-Remaining' => (string) $remaining,
            'X-DDoS-Protection' => 'rate_limited',
        ]);

        if ($resetTime) {
            $resetTimestamp = time() + (int) $resetTime->toSeconds();
            $headers = $headers
                ->with('X-RateLimit-Reset', (string) $resetTimestamp)
                ->with('Retry-After', (string) $resetTime->toSeconds());
        }

        $body = json_encode([
            'error' => 'Rate limit exceeded',
            'message' => $message ?? 'Too many requests. Please slow down.',
            'reason' => $reason,
            'limit' => $limit,
            'remaining' => $remaining,
            'reset_time' => $resetTime?->toSeconds(),
            'timestamp' => date('c'),
        ], JSON_PRETTY_PRINT);

        return new self(
            type: ResponseType::RATE_LIMITED,
            reason: $reason,
            message: $message,
            headers: $headers,
            body: $body,
            httpStatus: Status::TOO_MANY_REQUESTS,
            timestamp: Timestamp::fromFloat(microtime(true)),
            metadata: [
                'limit' => $limit,
                'remaining' => $remaining,
                'reset_time' => $resetTime?->toSeconds(),
            ]
        );
    }

    /**
     * Create a challenge response (CAPTCHA, etc.)
     */
    public static function challenge(
        string $challengeType,
        string $challengeData,
        string $reason,
        ?string $message = null
    ): self {
        $headers = new Headers([
            'Content-Type' => 'text/html',
            'X-DDoS-Protection' => 'challenge',
            'X-Challenge-Type' => $challengeType,
        ]);

        // This would typically render a challenge page
        $body = $challengeData;

        return new self(
            type: ResponseType::CHALLENGE,
            reason: $reason,
            message: $message,
            headers: $headers,
            body: $body,
            httpStatus: Status::OK,
            timestamp: Timestamp::fromFloat(microtime(true)),
            metadata: [
                'challenge_type' => $challengeType,
                'challenge_id' => hash('sha256', $challengeData . time()),
            ]
        );
    }

    /**
     * Create a tar pit response (slow response)
     */
    public static function tarPit(
        string $reason,
        Duration $delay,
        ?string $message = null
    ): self {
        $headers = new Headers([
            'Content-Type' => 'application/json',
            'X-DDoS-Protection' => 'tar_pit',
            'X-Delay' => (string) $delay->toSeconds(),
        ]);

        $body = json_encode([
            'message' => $message ?? 'Processing your request...',
            'reason' => $reason,
            'delay' => $delay->toSeconds(),
            'timestamp' => date('c'),
        ], JSON_PRETTY_PRINT);

        return new self(
            type: ResponseType::TAR_PIT,
            reason: $reason,
            message: $message,
            headers: $headers,
            body: $body,
            httpStatus: Status::OK,
            timestamp: Timestamp::fromFloat(microtime(true)),
            metadata: ['delay_seconds' => $delay->toSeconds()]
        );
    }

    /**
     * Add processing time to response
     */
    public function withProcessingTime(Duration $processingTime): self
    {
        return new self(
            type: $this->type,
            reason: $this->reason,
            message: $this->message,
            headers: $this->headers,
            body: $this->body,
            httpStatus: $this->httpStatus,
            processingTime: $processingTime,
            timestamp: $this->timestamp,
            metadata: array_merge($this->metadata, [
                'processing_time_ms' => $processingTime->toMilliseconds(),
            ])
        );
    }

    /**
     * Add headers to response
     */
    public function withHeaders(Headers $additionalHeaders): self
    {
        $mergedHeaders = $this->headers ?
            $this->headers->merge($additionalHeaders) :
            $additionalHeaders;

        return new self(
            type: $this->type,
            reason: $this->reason,
            message: $this->message,
            headers: $mergedHeaders,
            body: $this->body,
            httpStatus: $this->httpStatus,
            processingTime: $this->processingTime,
            timestamp: $this->timestamp,
            metadata: $this->metadata
        );
    }

    /**
     * Add metadata to response
     */
    public function withMetadata(array $additionalMetadata): self
    {
        return new self(
            type: $this->type,
            reason: $this->reason,
            message: $this->message,
            headers: $this->headers,
            body: $this->body,
            httpStatus: $this->httpStatus,
            processingTime: $this->processingTime,
            timestamp: $this->timestamp,
            metadata: array_merge($this->metadata, $additionalMetadata)
        );
    }

    /**
     * Check if response should block the request
     */
    public function shouldBlock(): bool
    {
        return in_array($this->type, [
            ResponseType::BLOCK,
            ResponseType::RATE_LIMITED,
        ]);
    }

    /**
     * Check if response requires user interaction
     */
    public function requiresUserInteraction(): bool
    {
        return $this->type === ResponseType::CHALLENGE;
    }

    /**
     * Get response summary for logging
     */
    public function getSummary(): string
    {
        return sprintf(
            '%s: %s (%s)',
            $this->type->name,
            $this->reason,
            $this->message ?? 'no message'
        );
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'type' => $this->type->value,
            'reason' => $this->reason,
            'message' => $this->message,
            'http_status' => $this->httpStatus?->value,
            'processing_time_ms' => $this->processingTime?->toMilliseconds(),
            'timestamp' => $this->timestamp?->format('c'),
            'metadata' => $this->metadata,
            'should_block' => $this->shouldBlock(),
            'requires_interaction' => $this->requiresUserInteraction(),
        ];
    }

    /**
     * Check if response indicates successful mitigation
     */
    public function isSuccessfulMitigation(): bool
    {
        return match ($this->type) {
            ResponseType::BLOCK,
            ResponseType::RATE_LIMITED,
            ResponseType::CHALLENGE,
            ResponseType::TAR_PIT => true,
            ResponseType::ALLOW => false
        };
    }

    /**
     * Get recommended cache duration for this response type
     */
    public function getCacheDuration(): Duration
    {
        return match ($this->type) {
            ResponseType::BLOCK => Duration::fromMinutes(5),
            ResponseType::RATE_LIMITED => Duration::fromMinutes(1),
            ResponseType::CHALLENGE => Duration::fromMinutes(10),
            ResponseType::TAR_PIT => Duration::fromSeconds(30),
            ResponseType::ALLOW => Duration::fromMinutes(1)
        };
    }

    /**
     * Get response age
     */
    public function getAge(Clock $clock): Duration
    {
        if (! $this->timestamp) {
            return Duration::zero();
        }

        return $this->timestamp->age($clock);
    }
}
