<?php

declare(strict_types=1);

namespace App\Framework\ErrorReporting;

use App\Framework\Core\ValueObjects\Duration;
use DateTimeImmutable;
use Throwable;

/**
 * Structured error report with comprehensive context
 */
final readonly class ErrorReport
{
    public function __construct(
        public string $id,
        public DateTimeImmutable $timestamp,
        public string $level,
        public string $message,
        public string $exception,
        public string $file,
        public int $line,
        public string $trace,
        public array $context,
        public ?string $userId = null,
        public ?string $sessionId = null,
        public ?string $requestId = null,
        public ?string $userAgent = null,
        public ?string $ipAddress = null,
        public ?string $route = null,
        public ?string $method = null,
        public ?array $requestData = null,
        public ?Duration $executionTime = null,
        public ?int $memoryUsage = null,
        public array $tags = [],
        public array $breadcrumbs = [],
        public ?string $release = null,
        public ?string $environment = null,
        public array $serverInfo = [],
        public array $customData = []
    ) {
    }

    /**
     * Create from Throwable
     */
    public static function fromThrowable(
        Throwable $throwable,
        string $level = 'error',
        array $context = []
    ): self {
        return new self(
            id: self::generateId(),
            timestamp: new DateTimeImmutable(),
            level: $level,
            message: $throwable->getMessage(),
            exception: $throwable::class,
            file: $throwable->getFile(),
            line: $throwable->getLine(),
            trace: $throwable->getTraceAsString(),
            context: $context,
            environment: $_ENV['APP_ENV'] ?? 'production',
            serverInfo: self::getServerInfo()
        );
    }

    /**
     * Create from manual report
     */
    public static function create(
        string $level,
        string $message,
        array $context = [],
        ?Throwable $exception = null
    ): self {
        return new self(
            id: self::generateId(),
            timestamp: new DateTimeImmutable(),
            level: $level,
            message: $message,
            exception: $exception?->class ?? 'N/A',
            file: $exception?->getFile() ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'] ?? 'unknown',
            line: $exception?->getLine() ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['line'] ?? 0,
            trace: $exception?->getTraceAsString() ?? self::getCurrentTrace(),
            context: $context,
            environment: $_ENV['APP_ENV'] ?? 'production',
            serverInfo: self::getServerInfo()
        );
    }

    /**
     * Add user context
     */
    public function withUser(string $userId, ?string $sessionId = null): self
    {
        return new self(
            id: $this->id,
            timestamp: $this->timestamp,
            level: $this->level,
            message: $this->message,
            exception: $this->exception,
            file: $this->file,
            line: $this->line,
            trace: $this->trace,
            context: $this->context,
            userId: $userId,
            sessionId: $sessionId ?? $this->sessionId,
            requestId: $this->requestId,
            userAgent: $this->userAgent,
            ipAddress: $this->ipAddress,
            route: $this->route,
            method: $this->method,
            requestData: $this->requestData,
            executionTime: $this->executionTime,
            memoryUsage: $this->memoryUsage,
            tags: $this->tags,
            breadcrumbs: $this->breadcrumbs,
            release: $this->release,
            environment: $this->environment,
            serverInfo: $this->serverInfo,
            customData: $this->customData
        );
    }

    /**
     * Add HTTP request context
     */
    public function withRequest(
        string $method,
        string $route,
        ?string $requestId = null,
        ?string $userAgent = null,
        ?string $ipAddress = null,
        ?array $requestData = null
    ): self {
        return new self(
            id: $this->id,
            timestamp: $this->timestamp,
            level: $this->level,
            message: $this->message,
            exception: $this->exception,
            file: $this->file,
            line: $this->line,
            trace: $this->trace,
            context: $this->context,
            userId: $this->userId,
            sessionId: $this->sessionId,
            requestId: $requestId,
            userAgent: $userAgent,
            ipAddress: $ipAddress,
            route: $route,
            method: $method,
            requestData: $requestData,
            executionTime: $this->executionTime,
            memoryUsage: $this->memoryUsage,
            tags: $this->tags,
            breadcrumbs: $this->breadcrumbs,
            release: $this->release,
            environment: $this->environment,
            serverInfo: $this->serverInfo,
            customData: $this->customData
        );
    }

    /**
     * Add performance context
     */
    public function withPerformance(Duration $executionTime, int $memoryUsage): self
    {
        return new self(
            id: $this->id,
            timestamp: $this->timestamp,
            level: $this->level,
            message: $this->message,
            exception: $this->exception,
            file: $this->file,
            line: $this->line,
            trace: $this->trace,
            context: $this->context,
            userId: $this->userId,
            sessionId: $this->sessionId,
            requestId: $this->requestId,
            userAgent: $this->userAgent,
            ipAddress: $this->ipAddress,
            route: $this->route,
            method: $this->method,
            requestData: $this->requestData,
            executionTime: $executionTime,
            memoryUsage: $memoryUsage,
            tags: $this->tags,
            breadcrumbs: $this->breadcrumbs,
            release: $this->release,
            environment: $this->environment,
            serverInfo: $this->serverInfo,
            customData: $this->customData
        );
    }

    /**
     * Add tags
     */
    public function withTags(array $tags): self
    {
        return new self(
            id: $this->id,
            timestamp: $this->timestamp,
            level: $this->level,
            message: $this->message,
            exception: $this->exception,
            file: $this->file,
            line: $this->line,
            trace: $this->trace,
            context: $this->context,
            userId: $this->userId,
            sessionId: $this->sessionId,
            requestId: $this->requestId,
            userAgent: $this->userAgent,
            ipAddress: $this->ipAddress,
            route: $this->route,
            method: $this->method,
            requestData: $this->requestData,
            executionTime: $this->executionTime,
            memoryUsage: $this->memoryUsage,
            tags: array_unique(array_merge($this->tags, $tags)),
            breadcrumbs: $this->breadcrumbs,
            release: $this->release,
            environment: $this->environment,
            serverInfo: $this->serverInfo,
            customData: $this->customData
        );
    }

    /**
     * Add breadcrumbs
     */
    public function withBreadcrumbs(array $breadcrumbs): self
    {
        return new self(
            id: $this->id,
            timestamp: $this->timestamp,
            level: $this->level,
            message: $this->message,
            exception: $this->exception,
            file: $this->file,
            line: $this->line,
            trace: $this->trace,
            context: $this->context,
            userId: $this->userId,
            sessionId: $this->sessionId,
            requestId: $this->requestId,
            userAgent: $this->userAgent,
            ipAddress: $this->ipAddress,
            route: $this->route,
            method: $this->method,
            requestData: $this->requestData,
            executionTime: $this->executionTime,
            memoryUsage: $this->memoryUsage,
            tags: $this->tags,
            breadcrumbs: array_merge($this->breadcrumbs, $breadcrumbs),
            release: $this->release,
            environment: $this->environment,
            serverInfo: $this->serverInfo,
            customData: $this->customData
        );
    }

    /**
     * Add custom data
     */
    public function withCustomData(array $customData): self
    {
        return new self(
            id: $this->id,
            timestamp: $this->timestamp,
            level: $this->level,
            message: $this->message,
            exception: $this->exception,
            file: $this->file,
            line: $this->line,
            trace: $this->trace,
            context: $this->context,
            userId: $this->userId,
            sessionId: $this->sessionId,
            requestId: $this->requestId,
            userAgent: $this->userAgent,
            ipAddress: $this->ipAddress,
            route: $this->route,
            method: $this->method,
            requestData: $this->requestData,
            executionTime: $this->executionTime,
            memoryUsage: $this->memoryUsage,
            tags: $this->tags,
            breadcrumbs: $this->breadcrumbs,
            release: $this->release,
            environment: $this->environment,
            serverInfo: $this->serverInfo,
            customData: array_merge($this->customData, $customData)
        );
    }

    /**
     * Get severity level as integer
     */
    public function getSeverityLevel(): int
    {
        return match (strtolower($this->level)) {
            'emergency' => 0,
            'alert' => 1,
            'critical' => 2,
            'error' => 3,
            'warning' => 4,
            'notice' => 5,
            'info' => 6,
            'debug' => 7,
            default => 3,
        };
    }

    /**
     * Check if error is critical
     */
    public function isCritical(): bool
    {
        return $this->getSeverityLevel() <= 2;
    }

    /**
     * Get fingerprint for grouping similar errors
     */
    public function getFingerprint(): string
    {
        $key = $this->exception . '|' . $this->file . '|' . $this->line;

        return hash('xxh64', $key);
    }

    /**
     * Convert to array for storage/transmission
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'timestamp' => $this->timestamp->format('c'),
            'level' => $this->level,
            'message' => $this->message,
            'exception' => $this->exception,
            'file' => $this->file,
            'line' => $this->line,
            'trace' => $this->trace,
            'context' => $this->context,
            'user_id' => $this->userId,
            'session_id' => $this->sessionId,
            'request_id' => $this->requestId,
            'user_agent' => $this->userAgent,
            'ip_address' => $this->ipAddress,
            'route' => $this->route,
            'method' => $this->method,
            'request_data' => $this->requestData,
            'execution_time' => $this->executionTime?->toMilliseconds(),
            'memory_usage' => $this->memoryUsage,
            'tags' => $this->tags,
            'breadcrumbs' => $this->breadcrumbs,
            'release' => $this->release,
            'environment' => $this->environment,
            'server_info' => $this->serverInfo,
            'custom_data' => $this->customData,
            'fingerprint' => $this->getFingerprint(),
            'severity_level' => $this->getSeverityLevel(),
        ];
    }

    private static function generateId(): string
    {
        return bin2hex(random_bytes(16));
    }

    private static function getCurrentTrace(): string
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        array_shift($trace); // Remove current method

        $result = [];
        foreach ($trace as $frame) {
            $file = $frame['file'] ?? 'unknown';
            $line = $frame['line'] ?? 0;
            $function = $frame['function'] ?? 'unknown';
            $class = isset($frame['class']) ? $frame['class'] . '::' : '';

            $result[] = "#{$file}({$line}): {$class}{$function}()";
        }

        return implode("\n", $result);
    }

    private static function getServerInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown',
            'server_name' => $_SERVER['SERVER_NAME'] ?? 'unknown',
            'request_time' => $_SERVER['REQUEST_TIME'] ?? time(),
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
        ];
    }
}
