<?php

declare(strict_types=1);

namespace App\Framework\Waf\Rules\ValueObjects;

use App\Framework\Waf\Rules\RuleType;

/**
 * Condition that defines what to check in a request
 */
final readonly class RuleCondition
{
    public function __construct(
        public RuleType $type,
        public string $target,
        public RulePattern $pattern,
        public bool $negated = false,
        public ?string $transformation = null
    ) {
    }

    /**
     * Create condition for request headers
     */
    public static function header(string $headerName, RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::HEADER_CHECK,
            target: "headers.{$headerName}",
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for query parameters
     */
    public static function queryParameter(string $paramName, RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::PARAMETER_CHECK,
            target: "query.{$paramName}",
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for POST parameters
     */
    public static function postParameter(string $paramName, RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::PARAMETER_CHECK,
            target: "post.{$paramName}",
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for request body
     */
    public static function requestBody(RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::BODY_CHECK,
            target: 'body',
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for URL path
     */
    public static function urlPath(RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::PATH_CHECK,
            target: 'path',
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for HTTP method
     */
    public static function httpMethod(RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::METHOD_CHECK,
            target: 'method',
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for User-Agent header
     */
    public static function userAgent(RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::USER_AGENT_CHECK,
            target: 'headers.User-Agent',
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for Content-Type header
     */
    public static function contentType(RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::CONTENT_TYPE_CHECK,
            target: 'headers.Content-Type',
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition for cookies
     */
    public static function cookie(string $cookieName, RulePattern $pattern, bool $negated = false): self
    {
        return new self(
            type: RuleType::COOKIE_CHECK,
            target: "cookies.{$cookieName}",
            pattern: $pattern,
            negated: $negated
        );
    }

    /**
     * Create condition with transformation
     */
    public function withTransformation(string $transformation): self
    {
        return new self(
            type: $this->type,
            target: $this->target,
            pattern: $this->pattern,
            negated: $this->negated,
            transformation: $transformation
        );
    }

    /**
     * Create negated version of condition
     */
    public function negate(): self
    {
        return new self(
            type: $this->type,
            target: $this->target,
            pattern: $this->pattern,
            negated: ! $this->negated,
            transformation: $this->transformation
        );
    }

    /**
     * Extract value from request data based on target
     */
    public function extractValue(array $requestData): ?string
    {
        $parts = explode('.', $this->target);
        $value = $requestData;

        foreach ($parts as $part) {
            if (! is_array($value) || ! isset($value[$part])) {
                return null;
            }
            $value = $value[$part];
        }

        if (! is_string($value)) {
            $value = (string) $value;
        }

        // Apply transformation if specified
        if ($this->transformation) {
            $value = $this->applyTransformation($value);
        }

        return $value;
    }

    /**
     * Evaluate condition against request data
     */
    public function evaluate(array $requestData): bool
    {
        $value = $this->extractValue($requestData);

        if ($value === null) {
            // No value found - condition fails unless negated
            return $this->negated;
        }

        $matches = $this->pattern->matches($value);

        // Apply negation if specified
        return $this->negated ? ! $matches : $matches;
    }

    /**
     * Get all matches from request data
     */
    public function getMatches(array $requestData): array
    {
        $value = $this->extractValue($requestData);

        if ($value === null) {
            return [];
        }

        return $this->pattern->getAllMatches($value);
    }

    /**
     * Check if condition targets sensitive data
     */
    public function targetsSensitiveData(): bool
    {
        $sensitiveTargets = [
            'headers.Authorization',
            'headers.Cookie',
            'headers.X-Auth-Token',
            'post.password',
            'post.passwd',
            'post.secret',
            'post.token',
            'query.password',
            'query.passwd',
            'query.secret',
            'query.token',
        ];

        return in_array($this->target, $sensitiveTargets, true);
    }

    /**
     * Get target description
     */
    public function getTargetDescription(): string
    {
        return match (true) {
            str_starts_with($this->target, 'headers.') => 'HTTP Header: ' . substr($this->target, 8),
            str_starts_with($this->target, 'query.') => 'Query Parameter: ' . substr($this->target, 6),
            str_starts_with($this->target, 'post.') => 'POST Parameter: ' . substr($this->target, 5),
            str_starts_with($this->target, 'cookies.') => 'Cookie: ' . substr($this->target, 8),
            $this->target === 'body' => 'Request Body',
            $this->target === 'path' => 'URL Path',
            $this->target === 'method' => 'HTTP Method',
            default => ucfirst($this->target)
        };
    }

    /**
     * Convert to array
     */
    public function toArray(): array
    {
        return [
            'type' => $this->type->value,
            'target' => $this->target,
            'target_description' => $this->getTargetDescription(),
            'pattern' => $this->pattern->toArray(),
            'negated' => $this->negated,
            'transformation' => $this->transformation,
            'targets_sensitive_data' => $this->targetsSensitiveData(),
        ];
    }

    /**
     * Apply transformation to value
     */
    private function applyTransformation(string $value): string
    {
        return match ($this->transformation) {
            'lowercase' => strtolower($value),
            'uppercase' => strtoupper($value),
            'trim' => trim($value),
            'url_decode' => urldecode($value),
            'html_decode' => html_entity_decode($value, ENT_QUOTES | ENT_HTML5),
            'base64_decode' => base64_decode($value, true) ?: $value,
            'remove_whitespace' => preg_replace('/\s+/', '', $value),
            'normalize_path' => str_replace(['\\', '//'], ['/', '/'], $value),
            default => $value
        };
    }
}
