<?php

declare(strict_types=1);

namespace App\Application\Security;

use App\Application\Security\ValueObjects\{
    OWASPEventIdentifier,
    OWASPLogFormat,
    OWASPLogLevel,
    RequestContext,
    SecurityContext
};
use App\Framework\Waf\ValueObjects\Detection;

final class OWASPSecurityEventFactory
{
    public function __construct(
        private string $applicationId = 'app.security'
    ) {
    }

    public function createFromSecurityEvent(
        SecurityEvent $event,
        SecurityContext $context,
        RequestContext $requestContext
    ): OWASPLogFormat {
        $eventIdentifier = $this->createEventIdentifier($event);
        $logLevel = OWASPLogLevel::fromSecurityEventType($event->type);
        $description = $this->createDescription($event, $eventIdentifier);

        return OWASPLogFormat::create(
            $this->applicationId,
            $eventIdentifier->toString(),
            $logLevel->value,
            $description,
            $context,
            $requestContext
        );
    }

    /**
     * Create OWASP Log Format from WAF Detection
     *
     * This method consolidates WAF detections with OWASP security events,
     * eliminating the need for separate event systems.
     */
    public function createFromDetection(
        Detection $detection,
        OWASPSecurityEvent $owaspEvent,
        SecurityContext $context,
        RequestContext $requestContext
    ): OWASPLogFormat {
        $eventIdentifier = $owaspEvent->getOWASPEventIdentifier();
        $logLevel = $owaspEvent->getOWASPLogLevel();
        $description = $this->createDetectionDescription($detection, $owaspEvent);

        return OWASPLogFormat::create(
            $this->applicationId . '.waf',
            $eventIdentifier->toString(),
            $logLevel->value,
            $description,
            $context,
            $requestContext
        );
    }

    private function createEventIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        return match ($event->type) {
            SecurityEventType::LOGIN_FAILED => $this->createLoginFailedIdentifier($event),
            SecurityEventType::LOGIN_SUCCESS => $this->createLoginSuccessIdentifier($event),
            SecurityEventType::LOGOUT => $this->createLogoutIdentifier($event),
            SecurityEventType::PASSWORD_CHANGE => $this->createPasswordChangeIdentifier($event),
            SecurityEventType::ACCOUNT_LOCKED => $this->createAccountLockedIdentifier($event),
            SecurityEventType::ACCESS_DENIED => $this->createAccessDeniedIdentifier($event),
            SecurityEventType::PRIVILEGE_ESCALATION => $this->createPrivilegeEscalationIdentifier($event),
            SecurityEventType::DATA_ACCESS => $this->createDataAccessIdentifier($event),
            SecurityEventType::INJECTION_ATTEMPT => $this->createInjectionAttemptIdentifier($event),
            SecurityEventType::FILE_UPLOAD => $this->createFileUploadIdentifier($event),
            SecurityEventType::SESSION_HIJACK => $this->createSessionHijackIdentifier($event),
            SecurityEventType::SESSION_TIMEOUT => $this->createSessionTimeoutIdentifier($event),
            SecurityEventType::MALWARE_DETECTED => $this->createMalwareDetectedIdentifier($event),
            SecurityEventType::AUDIT_FAILURE => $this->createAuditFailureIdentifier($event),
        };
    }

    private function createLoginFailedIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::authenticationFailure($username);
    }

    private function createLoginSuccessIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::authenticationSuccess($username);
    }

    private function createLogoutIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::sessionTermination($username);
    }

    private function createPasswordChangeIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::passwordChange($username);
    }

    private function createAccountLockedIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::accountLocked($username);
    }

    private function createAccessDeniedIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);
        $resource = $this->extractProperty($event, 'resource', 'unknown_resource');

        return OWASPEventIdentifier::authorizationFailure($username, $resource);
    }

    private function createPrivilegeEscalationIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);
        $fromRole = $this->extractProperty($event, 'fromRole', 'user');
        $toRole = $this->extractProperty($event, 'toRole', 'admin');

        return OWASPEventIdentifier::privilegeEscalation($username, $fromRole, $toRole);
    }

    private function createDataAccessIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $field = $this->extractProperty($event, 'field', 'unknown');

        return OWASPEventIdentifier::inputValidationFailure($field);
    }

    private function createInjectionAttemptIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $attackType = $this->extractProperty($event, 'attackType', 'injection');

        return OWASPEventIdentifier::maliciousInput($attackType);
    }

    private function createFileUploadIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $filename = $this->extractProperty($event, 'filename', 'unknown.file');

        return OWASPEventIdentifier::fileUploadFailure($filename);
    }

    private function createSessionHijackIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::sessionHijacking($username);
    }

    private function createSessionTimeoutIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $username = $this->extractUsername($event);

        return OWASPEventIdentifier::sessionTimeout($username);
    }

    private function createMalwareDetectedIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $malwareType = $this->extractProperty($event, 'malwareType', 'unknown');

        return OWASPEventIdentifier::malwareDetected($malwareType);
    }

    private function createAuditFailureIdentifier(SecurityEvent $event): OWASPEventIdentifier
    {
        $eventType = $this->extractProperty($event, 'eventType', $event->type->value);

        return OWASPEventIdentifier::auditFailure($eventType);
    }

    private function createDescription(SecurityEvent $event, OWASPEventIdentifier $identifier): string
    {
        return match ($event->type) {
            SecurityEventType::LOGIN_FAILED => "User {$this->extractUsername($event)} login failed",
            SecurityEventType::LOGIN_SUCCESS => "User {$this->extractUsername($event)} login successfully",
            SecurityEventType::LOGOUT => "User {$this->extractUsername($event)} logged out",
            SecurityEventType::PASSWORD_CHANGE => "User {$this->extractUsername($event)} changed password",
            SecurityEventType::ACCOUNT_LOCKED => "Account {$this->extractUsername($event)} locked",
            SecurityEventType::ACCESS_DENIED => "Access denied for user {$this->extractUsername($event)} to resource {$this->extractProperty($event, 'resource', 'unknown_resource')}",
            SecurityEventType::PRIVILEGE_ESCALATION => "Privilege escalation attempt by user {$this->extractUsername($event)}",
            SecurityEventType::DATA_ACCESS => "Input validation failure for field {$this->extractProperty($event, 'field', 'unknown')}",
            SecurityEventType::INJECTION_ATTEMPT => "Malicious input detected: {$this->extractProperty($event, 'attackType', 'injection')}",
            SecurityEventType::FILE_UPLOAD => "File upload failed: {$this->extractProperty($event, 'filename', 'unknown.file')}",
            SecurityEventType::SESSION_HIJACK => "Session hijacking detected for user {$this->extractUsername($event)}",
            SecurityEventType::SESSION_TIMEOUT => "Session timeout for user {$this->extractUsername($event)}",
            SecurityEventType::MALWARE_DETECTED => "Malware detected: {$this->extractProperty($event, 'malwareType', 'unknown')}",
            SecurityEventType::AUDIT_FAILURE => "Audit logging failure for event {$event->type->value}",
        };
    }

    private function extractUsername(SecurityEvent $event): string
    {
        $usernameFields = ['username', 'email', 'user', 'userId'];

        foreach ($usernameFields as $field) {
            $value = $this->extractProperty($event, $field);
            if ($value !== null) {
                return $this->maskEmail($value);
            }
        }

        return 'anonymous';
    }

    private function extractProperty(SecurityEvent $event, string $propertyName, ?string $default = null): ?string
    {
        try {
            $reflection = new \ReflectionObject($event);

            if ($reflection->hasProperty($propertyName)) {
                $property = $reflection->getProperty($propertyName);
                $property->setAccessible(true);
                $value = $property->getValue($event);

                return is_string($value) ? $value : (string)$value;
            }
        } catch (\Exception) {
            // Property nicht gefunden oder nicht lesbar
        }

        return $default;
    }

    private function maskEmail(string $value): string
    {
        if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
            return $value;
        }

        [$local, $domain] = explode('@', $value, 2);
        $maskedLocal = substr($local, 0, 2) . str_repeat('*', max(0, strlen($local) - 2));

        return $maskedLocal . '@' . $domain;
    }

    // ===== WAF Detection Integration Methods =====

    /**
     * Create description for WAF detection
     */
    private function createDetectionDescription(Detection $detection, OWASPSecurityEvent $owaspEvent): string
    {
        $baseDescription = $owaspEvent->getDescription();
        $wafInfo = $detection->ruleId ? " [WAF Rule: {$detection->ruleId->value}]" : " [WAF Detection]";
        $confidence = $detection->confidence ? " (Confidence: {$detection->confidence->getValue()}%)" : "";

        return $baseDescription . $wafInfo . $confidence;
    }

    /**
     * Build metadata for WAF detection logging
     * @return array<string, mixed>
     */
    private function buildDetectionMetadata(Detection $detection): array
    {
        return array_filter([
            'waf_category' => $detection->category->value,
            'waf_severity' => $detection->severity->value,
            'waf_rule_id' => $detection->ruleId?->value,
            'waf_confidence' => $detection->confidence?->getValue(),
            'waf_threat_score' => $detection->getThreatScore()->getValue(),
            'waf_location' => $detection->location,
            'waf_timestamp' => $detection->timestamp?->toIsoString(),
            'waf_owasp_rank' => $detection->getOwaspRank(),
            'waf_should_block' => $detection->shouldBlock(),
            'waf_should_alert' => $detection->shouldAlert(),
            'waf_payload_sample' => $detection->payload?->getSample(),
            'waf_context' => $detection->context?->toArray(),
        ], fn ($value) => $value !== null);
    }
}
