<?php

declare(strict_types=1);

namespace App\Framework\ErrorAggregation;

use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Ulid\Ulid;

/**
 * Represents a single error event for aggregation and analysis
 */
final readonly class ErrorEvent
{
    public function __construct(
        public Ulid $id,
        public string $service,
        public string $component,
        public string $operation,
        public ErrorCode $errorCode,
        public string $errorMessage,
        public ErrorSeverity $severity,
        public \DateTimeImmutable $occurredAt,
        public array $context = [],
        public array $metadata = [],
        public ?string $requestId = null,
        public ?string $userId = null,
        public ?string $clientIp = null,
        public bool $isSecurityEvent = false,
        public ?string $stackTrace = null,
        public ?string $userAgent = null,
    ) {
    }

    /**
     * Creates ErrorEvent from ErrorHandlerContext
     */
    public static function fromErrorHandlerContext(ErrorHandlerContext $context, \App\Framework\DateTime\Clock $clock): self
    {
        return new self(
            id: new Ulid($clock),
            service: self::extractServiceName($context),
            component: $context->exception->component ?? 'unknown',
            operation: $context->exception->operation ?? 'unknown',
            errorCode: self::extractErrorCode($context),
            errorMessage: self::extractUserMessage($context),
            severity: self::determineSeverity($context),
            occurredAt: new \DateTimeImmutable(),
            context: $context->exception->data,
            metadata: $context->exception->metadata,
            requestId: $context->request->requestId,
            userId: $context->request->userId ?? null,
            clientIp: $context->request->clientIp,
            isSecurityEvent: $context->exception->metadata['security_event'] ?? false,
            stackTrace: self::extractStackTrace($context),
            userAgent: $context->request->userAgent,
        );
    }

    /**
     * Converts to array for storage/transmission
     */
    public function toArray(): array
    {
        return [
            'id' => (string) $this->id,
            'service' => $this->service,
            'component' => $this->component,
            'operation' => $this->operation,
            'error_code' => $this->errorCode->value,
            'error_message' => $this->errorMessage,
            'severity' => $this->severity->value,
            'occurred_at' => $this->occurredAt->format('c'),
            'context' => $this->context,
            'metadata' => $this->metadata,
            'request_id' => $this->requestId,
            'user_id' => $this->userId,
            'client_ip' => $this->clientIp,
            'is_security_event' => $this->isSecurityEvent,
            'stack_trace' => $this->stackTrace,
            'user_agent' => $this->userAgent,
        ];
    }

    /**
     * Creates from array (for deserialization)
     */
    public static function fromArray(array $data): self
    {
        return new self(
            id: Ulid::fromString($data['id']),
            service: $data['service'],
            component: $data['component'],
            operation: $data['operation'],
            errorCode: ErrorCode::from($data['error_code']),
            errorMessage: $data['error_message'],
            severity: ErrorSeverity::from($data['severity']),
            occurredAt: new \DateTimeImmutable($data['occurred_at']),
            context: $data['context'] ?? [],
            metadata: $data['metadata'] ?? [],
            requestId: $data['request_id'],
            userId: $data['user_id'],
            clientIp: $data['client_ip'],
            isSecurityEvent: $data['is_security_event'] ?? false,
            stackTrace: $data['stack_trace'],
            userAgent: $data['user_agent'],
        );
    }

    /**
     * Gets fingerprint for grouping similar errors
     */
    public function getFingerprint(): string
    {
        $components = [
            $this->service,
            $this->component,
            $this->operation,
            $this->errorCode->value,
            // Normalize error message to group similar errors
            $this->normalizeErrorMessage($this->errorMessage),
        ];

        return hash('sha256', implode('|', $components));
    }

    /**
     * Checks if this error should trigger an alert
     */
    public function shouldTriggerAlert(): bool
    {
        // Critical and error severity always trigger alerts
        if (in_array($this->severity, [ErrorSeverity::CRITICAL, ErrorSeverity::ERROR])) {
            return true;
        }

        // Security events always trigger alerts
        if ($this->isSecurityEvent) {
            return true;
        }

        // Check metadata for explicit alert requirement
        return $this->metadata['requires_alert'] ?? false;
    }

    /**
     * Gets alert urgency level
     */
    public function getAlertUrgency(): AlertUrgency
    {
        if ($this->severity === ErrorSeverity::CRITICAL || $this->isSecurityEvent) {
            return AlertUrgency::URGENT;
        }

        if ($this->severity === ErrorSeverity::ERROR) {
            return AlertUrgency::HIGH;
        }

        if ($this->severity === ErrorSeverity::WARNING) {
            return AlertUrgency::MEDIUM;
        }

        return AlertUrgency::LOW;
    }

    private static function extractServiceName(ErrorHandlerContext $context): string
    {
        // Try to extract service from request URI
        $uri = $context->request->requestUri;

        if ($uri !== null) {
            if (str_starts_with($uri, '/api/')) {
                return 'api';
            }

            if (str_starts_with($uri, '/admin/')) {
                return 'admin';
            }
        }

        // Extract from component if available
        if ($context->exception->component) {
            return strtolower($context->exception->component);
        }

        return 'web';
    }

    private static function extractErrorCode(ErrorHandlerContext $context): ErrorCode
    {
        // Try to get ErrorCode from original exception if it's a FrameworkException
        if (isset($context->exception->data['original_exception'])) {
            $originalException = $context->exception->data['original_exception'];
            if ($originalException instanceof \App\Framework\Exception\FrameworkException) {
                $errorCode = $originalException->getErrorCode();
                if ($errorCode !== null) {
                    return $errorCode;
                }
            }
        }

        // Fallback: Use SystemErrorCode::RESOURCE_EXHAUSTED as generic error
        return \App\Framework\Exception\Core\SystemErrorCode::RESOURCE_EXHAUSTED;
    }

    private static function extractUserMessage(ErrorHandlerContext $context): string
    {
        // Try user_message first
        if (isset($context->exception->data['user_message'])) {
            return $context->exception->data['user_message'];
        }

        // Try exception_message (stored by ErrorHandler)
        if (isset($context->exception->data['exception_message'])) {
            return $context->exception->data['exception_message'];
        }

        // Try to get from original_exception if it's a FrameworkException
        if (isset($context->exception->data['original_exception'])) {
            $originalException = $context->exception->data['original_exception'];
            if ($originalException instanceof \App\Framework\Exception\FrameworkException) {
                return $originalException->getMessage();
            }
            if ($originalException instanceof \Throwable) {
                return $originalException->getMessage();
            }
        }

        // Fallback to operation and component
        $operation = $context->exception->operation ?? 'unknown_operation';
        $component = $context->exception->component ?? 'unknown_component';

        return "Error in {$component} during {$operation}";
    }

    private static function determineSeverity(ErrorHandlerContext $context): ErrorSeverity
    {
        // Security events are always critical
        if ($context->exception->metadata['security_event'] ?? false) {
            return ErrorSeverity::CRITICAL;
        }

        // Check explicit severity in metadata
        if (isset($context->exception->metadata['severity'])) {
            return ErrorSeverity::tryFrom($context->exception->metadata['severity']) ?? ErrorSeverity::ERROR;
        }

        // Get severity from ErrorCode if available
        $errorCode = self::extractErrorCode($context);
        if ($errorCode !== null) {
            return $errorCode->getSeverity();
        }

        // Determine from HTTP status
        $httpStatus = $context->metadata['http_status'] ?? 500;

        return match (true) {
            $httpStatus >= 500 => ErrorSeverity::ERROR,
            $httpStatus >= 400 => ErrorSeverity::WARNING,
            default => ErrorSeverity::INFO,
        };
    }

    private static function extractStackTrace(ErrorHandlerContext $context): ?string
    {
        // Don't include stack traces for security events in production
        if (($context->exception->metadata['security_event'] ?? false) && ! ($_ENV['APP_DEBUG'] ?? false)) {
            return null;
        }

        // Extract from exception debug data
        if (isset($context->exception->debug['stack_trace'])) {
            return $context->exception->debug['stack_trace'];
        }

        return null;
    }

    private function normalizeErrorMessage(string $message): string
    {
        // Remove specific details to group similar errors
        $normalized = $message;

        // Remove file paths
        $normalized = preg_replace('#/[^\s]+#', '/path/to/file', $normalized);

        // Remove specific IDs/numbers
        $normalized = preg_replace('#\b\d+\b#', 'N', $normalized);

        // Remove timestamps
        $normalized = preg_replace('#\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2}#', 'TIMESTAMP', $normalized);

        // Remove ULIDs/UUIDs
        $normalized = preg_replace('#[0-9A-HJ-NP-TV-Z]{26}#', 'ULID', $normalized);
        $normalized = preg_replace('#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}#i', 'UUID', $normalized);

        return $normalized;
    }
}
