<?php

declare(strict_types=1);

namespace App\Framework\ErrorReporting;

use App\Framework\DateTime\Clock;
use App\Framework\ErrorReporting\Storage\ErrorReportStorageInterface;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Queue\Queue;
use Throwable;

/**
 * Central error reporting service
 */
final readonly class ErrorReporter implements ErrorReporterInterface
{
    public function __construct(
        private ErrorReportStorageInterface $storage,
        private Clock $clock,
        private ?Logger $logger = null,
        private ?Queue $queue = null,
        private bool $asyncProcessing = true,
        private array $processors = [],
        private array $filters = []
    ) {
    }

    /**
     * Report an error from Throwable
     */
    public function reportThrowable(
        Throwable $throwable,
        string $level = 'error',
        array $context = []
    ): string {
        $report = ErrorReport::fromThrowable($throwable, $level, $context);

        return $this->report($report);
    }

    /**
     * Report a manual error
     */
    public function reportError(
        string $level,
        string $message,
        array $context = [],
        ?Throwable $exception = null
    ): string {
        $report = ErrorReport::create($level, $message, $context, $exception);

        return $this->report($report);
    }

    /**
     * Report an error with full context
     */
    public function report(ErrorReport $report): string
    {
        try {
            // Apply filters - skip reporting if any filter returns false
            foreach ($this->filters as $filter) {
                if (! $filter($report)) {
                    $this->logDebug("Error report filtered out", ['report_id' => $report->id]);

                    return $report->id;
                }
            }

            // Apply processors to enrich the report
            $enrichedReport = $this->applyProcessors($report);

            // Store or queue the report
            if ($this->asyncProcessing && $this->queue) {
                $this->queueReport($enrichedReport);
            } else {
                $this->storeReport($enrichedReport);
            }

            $this->logInfo("Error report created", [
                'report_id' => $enrichedReport->id,
                'level' => $enrichedReport->level,
                'exception' => $enrichedReport->exception,
            ]);

            return $enrichedReport->id;

        } catch (Throwable $e) {
            // Fallback logging if reporting system fails
            $this->logError("Failed to report error", [
                'original_message' => $report->message,
                'reporting_error' => $e->getMessage(),
            ]);

            return $report->id;
        }
    }

    /**
     * Report multiple errors in batch
     */
    public function reportBatch(array $reports): array
    {
        $reportIds = [];

        foreach ($reports as $report) {
            if ($report instanceof ErrorReport) {
                $reportIds[] = $this->report($report);
            } else {
                $this->logError("Invalid report in batch", ['report' => gettype($report)]);
            }
        }

        return $reportIds;
    }

    /**
     * Add request context to current thread
     */
    public function withRequestContext(
        string $method,
        string $route,
        ?string $requestId = null,
        ?string $userAgent = null,
        ?string $ipAddress = null,
        ?array $requestData = null
    ): RequestContextualReporter {
        return new RequestContextualReporter(
            reporter: $this,
            method: $method,
            route: $route,
            requestId: $requestId,
            userAgent: $userAgent,
            ipAddress: $ipAddress,
            requestData: $requestData
        );
    }

    /**
     * Add user context to current thread
     */
    public function withUserContext(string $userId, ?string $sessionId = null): UserContextualReporter
    {
        return new UserContextualReporter(
            reporter: $this,
            userId: $userId,
            sessionId: $sessionId
        );
    }

    /**
     * Get error report by ID
     */
    public function getReport(string $reportId): ?ErrorReport
    {
        try {
            return $this->storage->find($reportId);
        } catch (Throwable $e) {
            $this->logError("Failed to retrieve error report", [
                'report_id' => $reportId,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }

    /**
     * Get recent error reports
     */
    public function getRecentReports(int $limit = 100, int $offset = 0): array
    {
        try {
            return $this->storage->findRecent($limit, $offset);
        } catch (Throwable $e) {
            $this->logError("Failed to retrieve recent error reports", [
                'error' => $e->getMessage(),
            ]);

            return [];
        }
    }

    /**
     * Get error reports by criteria
     */
    public function findReports(ErrorReportCriteria $criteria): array
    {
        try {
            return $this->storage->findByCriteria($criteria);
        } catch (Throwable $e) {
            $this->logError("Failed to find error reports", [
                'error' => $e->getMessage(),
            ]);

            return [];
        }
    }

    /**
     * Get error statistics
     */
    public function getStatistics(\DateTimeImmutable $from, \DateTimeImmutable $to): ErrorStatistics
    {
        try {
            return $this->storage->getStatistics($from, $to);
        } catch (Throwable $e) {
            $this->logError("Failed to get error statistics", [
                'error' => $e->getMessage(),
            ]);

            return ErrorStatistics::empty();
        }
    }

    /**
     * Clean up old error reports
     */
    public function cleanup(\DateTimeImmutable $before): int
    {
        try {
            $deleted = $this->storage->deleteOlderThan($before);
            $this->logInfo("Cleaned up old error reports", ['deleted_count' => $deleted]);

            return $deleted;
        } catch (Throwable $e) {
            $this->logError("Failed to cleanup error reports", [
                'error' => $e->getMessage(),
            ]);

            return 0;
        }
    }

    private function applyProcessors(ErrorReport $report): ErrorReport
    {
        $processedReport = $report;

        foreach ($this->processors as $processor) {
            try {
                $processedReport = $processor($processedReport);
            } catch (Throwable $e) {
                $this->logError("Error processor failed", [
                    'processor' => $processor::class ?? 'closure',
                    'error' => $e->getMessage(),
                ]);
            }
        }

        return $processedReport;
    }

    private function storeReport(ErrorReport $report): void
    {
        $this->storage->store($report);
    }

    private function queueReport(ErrorReport $report): void
    {
        $this->queue->push('error_report_processing', $report->toArray());
    }

    private function logDebug(string $message, array $context = []): void
    {
        $this->logger?->debug("[ErrorReporter] {$message}", LogContext::withData($context));
    }

    private function logInfo(string $message, array $context = []): void
    {
        $this->logger?->info("[ErrorReporter] {$message}", LogContext::withData($context));
    }

    private function logError(string $message, array $context = []): void
    {
        $this->logger?->error("[ErrorReporter] {$message}", LogContext::withData($context));
    }
}
