<?php

declare(strict_types=1);

namespace App\Framework\ErrorReporting\Analytics;

use App\Framework\DateTime\Clock;
use App\Framework\ErrorReporting\Analytics\ValueObjects\ErrorAnomaly;
use App\Framework\ErrorReporting\Analytics\ValueObjects\ErrorVelocity;
use App\Framework\ErrorReporting\ErrorReportCriteria;
use App\Framework\ErrorReporting\Storage\ErrorReportStorageInterface;
use DateTimeImmutable;

/**
 * Advanced analytics engine for error reporting
 */
final readonly class ErrorAnalyticsEngine
{
    public function __construct(
        private ErrorReportStorageInterface $storage,
        private Clock $clock
    ) {
    }

    /**
     * Detect error anomalies and spikes
     * @return array<ErrorAnomaly>
     */
    public function detectAnomalies(DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        $trends = $this->storage->getTrends($from, $to, 'hour');

        if (count($trends) < 3) {
            return [];
        }

        $values = array_column($trends, 'count');
        $mean = array_sum($values) / count($values);
        $stdDev = $this->calculateStandardDeviation($values, $mean);

        $anomalies = [];
        foreach ($trends as $trend) {
            $zScore = $stdDev > 0 ? abs($trend['count'] - $mean) / $stdDev : 0;

            if ($zScore > 2.5) { // More than 2.5 standard deviations
                $anomalies[] = ErrorAnomaly::create(
                    period: $this->clock->fromString($trend['period']),
                    count: (int) $trend['count'],
                    mean: $mean,
                    zScore: $zScore
                );
            }
        }

        return $anomalies;
    }

    /**
     * Calculate error velocity (rate of change)
     * @return array<ErrorVelocity>
     */
    public function calculateErrorVelocity(DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        $trends = $this->storage->getTrends($from, $to, 'hour');

        if (count($trends) < 2) {
            return [];
        }

        $velocities = [];
        for ($i = 1; $i < count($trends); $i++) {
            $previous = $trends[$i - 1];
            $current = $trends[$i];

            $velocities[] = ErrorVelocity::calculate(
                period: $this->clock->fromString($current['period']),
                currentCount: (int) $current['count'],
                previousCount: (int) $previous['count']
            );
        }

        return $velocities;
    }

    /**
     * Identify error patterns and correlations
     */
    public function identifyPatterns(DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        $criteria = ErrorReportCriteria::recent()->withTimeRange($from, $to);
        $reports = $this->storage->findByCriteria($criteria);

        $patterns = [
            'route_correlations' => $this->findRouteCorrelations($reports),
            'time_patterns' => $this->findTimePatterns($reports),
            'user_patterns' => $this->findUserPatterns($reports),
            'cascade_patterns' => $this->findCascadePatterns($reports),
        ];

        return $patterns;
    }

    /**
     * Generate predictive insights
     */
    public function generatePredictiveInsights(DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        $trends = $this->storage->getTrends($from, $to, 'day');

        if (count($trends) < 7) {
            return [
                'prediction' => 'insufficient_data',
                'message' => 'Need at least 7 days of data for predictions',
            ];
        }

        // Simple linear regression for trend prediction
        $prediction = $this->predictTrend($trends);

        // Seasonal pattern detection
        $seasonal = $this->detectSeasonalPatterns($trends);

        // Risk assessment
        $risk = $this->assessRisk($trends);

        return [
            'trend_prediction' => $prediction,
            'seasonal_patterns' => $seasonal,
            'risk_assessment' => $risk,
            'recommendations' => $this->generateRecommendations($prediction, $seasonal, $risk),
        ];
    }

    /**
     * Calculate error impact metrics
     */
    public function calculateImpactMetrics(DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        $criteria = ErrorReportCriteria::recent()->withTimeRange($from, $to);
        $reports = $this->storage->findByCriteria($criteria);

        $impact = [
            'user_impact' => $this->calculateUserImpact($reports),
            'business_impact' => $this->calculateBusinessImpact($reports),
            'system_impact' => $this->calculateSystemImpact($reports),
            'availability_impact' => $this->calculateAvailabilityImpact($reports),
        ];

        return $impact;
    }

    /**
     * Generate error health report
     */
    public function generateHealthReport(DateTimeImmutable $from, DateTimeImmutable $to): array
    {
        $statistics = $this->storage->getStatistics($from, $to);
        $anomalies = $this->detectAnomalies($from, $to);
        $velocity = $this->calculateErrorVelocity($from, $to);
        $patterns = $this->identifyPatterns($from, $to);
        $predictions = $this->generatePredictiveInsights($from, $to);
        $impact = $this->calculateImpactMetrics($from, $to);

        $healthScore = $this->calculateOverallHealthScore([
            'statistics' => $statistics,
            'anomalies' => $anomalies,
            'velocity' => $velocity,
            'impact' => $impact,
        ]);

        return [
            'period' => [
                'from' => $from->format('c'),
                'to' => $to->format('c'),
                'duration' => $from->diff($to)->format('%d days, %h hours'),
            ],
            'health_score' => $healthScore,
            'statistics' => $statistics->toArray(),
            'anomalies' => $anomalies,
            'velocity' => $velocity,
            'patterns' => $patterns,
            'predictions' => $predictions,
            'impact' => $impact,
            'generated_at' => $this->clock->now()->format('c'),
        ];
    }

    private function calculateStandardDeviation(array $values, float $mean): float
    {
        if (count($values) <= 1) {
            return 0.0;
        }

        $variance = array_sum(array_map(fn ($value) => pow($value - $mean, 2), $values)) / count($values);

        return sqrt($variance);
    }

    private function findRouteCorrelations(array $reports): array
    {
        $routeErrors = [];

        foreach ($reports as $report) {
            if ($report->route) {
                $routeErrors[$report->route][] = $report;
            }
        }

        $correlations = [];
        foreach ($routeErrors as $route => $errors) {
            if (count($errors) < 2) {
                continue;
            }

            $timeWindows = [];
            foreach ($errors as $error) {
                $window = $error->timestamp->format('Y-m-d H:i'); // Minute precision
                $timeWindows[$window] = ($timeWindows[$window] ?? 0) + 1;
            }

            $clustered = array_filter($timeWindows, fn ($count) => $count > 1);

            if (! empty($clustered)) {
                $correlations[] = [
                    'route' => $route,
                    'total_errors' => count($errors),
                    'clustered_periods' => count($clustered),
                    'max_cluster_size' => max($clustered),
                    'pattern' => 'temporal_clustering',
                ];
            }
        }

        return $correlations;
    }

    private function findTimePatterns(array $reports): array
    {
        $hourly = [];
        $daily = [];

        foreach ($reports as $report) {
            $hour = (int) $report->timestamp->format('H');
            $dayOfWeek = (int) $report->timestamp->format('N'); // 1-7

            $hourly[$hour] = ($hourly[$hour] ?? 0) + 1;
            $daily[$dayOfWeek] = ($daily[$dayOfWeek] ?? 0) + 1;
        }

        // Find peak hours and days
        arsort($hourly);
        arsort($daily);

        return [
            'peak_hour' => array_key_first($hourly),
            'peak_day' => array_key_first($daily),
            'hourly_distribution' => $hourly,
            'daily_distribution' => $daily,
            'patterns' => $this->identifyTimePatterns($hourly, $daily),
        ];
    }

    private function findUserPatterns(array $reports): array
    {
        $userErrors = [];

        foreach ($reports as $report) {
            if ($report->userId) {
                $userErrors[$report->userId][] = $report;
            }
        }

        $patterns = [];
        foreach ($userErrors as $userId => $errors) {
            if (count($errors) > 5) { // Users with many errors
                $patterns[] = [
                    'user_id' => $userId,
                    'error_count' => count($errors),
                    'pattern' => 'high_error_user',
                    'first_error' => min(array_map(fn ($e) => $e->timestamp->getTimestamp(), $errors)),
                    'last_error' => max(array_map(fn ($e) => $e->timestamp->getTimestamp(), $errors)),
                ];
            }
        }

        return $patterns;
    }

    private function findCascadePatterns(array $reports): array
    {
        // Group errors by timestamp windows
        $windows = [];
        foreach ($reports as $report) {
            $window = floor($report->timestamp->getTimestamp() / 60) * 60; // 1-minute windows
            $windows[$window][] = $report;
        }

        $cascades = [];
        foreach ($windows as $timestamp => $errors) {
            if (count($errors) > 3) { // Multiple errors in same minute
                $cascades[] = [
                    'timestamp' => $timestamp,
                    'error_count' => count($errors),
                    'unique_exceptions' => count(array_unique(array_map(fn ($e) => $e->exception, $errors))),
                    'pattern' => 'error_cascade',
                ];
            }
        }

        return $cascades;
    }

    private function predictTrend(array $trends): array
    {
        $n = count($trends);
        $values = array_column($trends, 'count');

        // Simple linear regression
        $sumX = array_sum(range(1, $n));
        $sumY = array_sum($values);
        $sumXY = 0;
        $sumX2 = 0;

        for ($i = 0; $i < $n; $i++) {
            $x = $i + 1;
            $y = $values[$i];
            $sumXY += $x * $y;
            $sumX2 += $x * $x;
        }

        $slope = ($n * $sumXY - $sumX * $sumY) / ($n * $sumX2 - $sumX * $sumX);
        $intercept = ($sumY - $slope * $sumX) / $n;

        // Predict next 3 periods
        $predictions = [];
        for ($i = 1; $i <= 3; $i++) {
            $nextX = $n + $i;
            $prediction = $slope * $nextX + $intercept;
            $predictions[] = [
                'period' => $i,
                'predicted_count' => max(0, round($prediction)),
            ];
        }

        return [
            'slope' => round($slope, 4),
            'trend' => $slope > 0.1 ? 'increasing' : ($slope < -0.1 ? 'decreasing' : 'stable'),
            'predictions' => $predictions,
        ];
    }

    private function detectSeasonalPatterns(array $trends): array
    {
        // Simple day-of-week seasonality
        $dayOfWeek = [];
        foreach ($trends as $trend) {
            $date = new DateTimeImmutable($trend['period']);
            $dow = (int) $date->format('N');
            $dayOfWeek[$dow] = ($dayOfWeek[$dow] ?? 0) + $trend['count'];
        }

        return [
            'weekly_pattern' => $dayOfWeek,
            'peak_day' => array_key_first($dayOfWeek),
        ];
    }

    private function assessRisk(array $trends): array
    {
        $recent = array_slice($trends, -3); // Last 3 periods
        $recentAvg = array_sum(array_column($recent, 'count')) / count($recent);

        $overall = array_column($trends, 'count');
        $overallAvg = array_sum($overall) / count($overall);

        $riskLevel = 'low';
        if ($recentAvg > $overallAvg * 1.5) {
            $riskLevel = 'high';
        } elseif ($recentAvg > $overallAvg * 1.2) {
            $riskLevel = 'medium';
        }

        return [
            'level' => $riskLevel,
            'recent_average' => round($recentAvg, 2),
            'overall_average' => round($overallAvg, 2),
            'risk_factor' => round($recentAvg / $overallAvg, 2),
        ];
    }

    private function generateRecommendations(array $prediction, array $seasonal, array $risk): array
    {
        $recommendations = [];

        if ($prediction['trend'] === 'increasing') {
            $recommendations[] = [
                'type' => 'trend',
                'priority' => 'high',
                'message' => 'Error trend is increasing. Investigate root causes and implement fixes.',
            ];
        }

        if ($risk['level'] === 'high') {
            $recommendations[] = [
                'type' => 'risk',
                'priority' => 'critical',
                'message' => 'High risk detected. Recent error rate is significantly above normal.',
            ];
        }

        return $recommendations;
    }

    private function calculateUserImpact(array $reports): array
    {
        $uniqueUsers = array_unique(array_filter(array_map(fn ($r) => $r->userId, $reports)));
        $userSessions = array_unique(array_filter(array_map(fn ($r) => $r->sessionId, $reports)));

        return [
            'affected_users' => count($uniqueUsers),
            'affected_sessions' => count($userSessions),
            'user_error_ratio' => count($uniqueUsers) > 0 ? count($reports) / count($uniqueUsers) : 0,
        ];
    }

    private function calculateBusinessImpact(array $reports): array
    {
        $criticalErrors = array_filter($reports, fn ($r) => $r->isCritical());
        $routes = array_unique(array_filter(array_map(fn ($r) => $r->route, $reports)));

        return [
            'critical_errors' => count($criticalErrors),
            'affected_endpoints' => count($routes),
            'business_critical_routes' => $this->identifyBusinessCriticalRoutes($reports),
        ];
    }

    private function calculateSystemImpact(array $reports): array
    {
        $avgMemory = array_filter(array_map(fn ($r) => $r->memoryUsage, $reports));
        $avgExecutionTime = array_filter(array_map(fn ($r) => $r->executionTime?->toMilliseconds(), $reports));

        return [
            'average_memory_usage' => ! empty($avgMemory) ? array_sum($avgMemory) / count($avgMemory) : 0,
            'average_execution_time' => ! empty($avgExecutionTime) ? array_sum($avgExecutionTime) / count($avgExecutionTime) : 0,
            'system_errors' => count(array_filter($reports, fn ($r) => str_contains($r->exception, 'System'))),
        ];
    }

    private function calculateAvailabilityImpact(array $reports): array
    {
        // This would need additional metrics to be accurate
        return [
            'estimated_downtime' => 0,
            'availability_score' => 99.9, // Placeholder
        ];
    }

    private function identifyBusinessCriticalRoutes(array $reports): array
    {
        $criticalRoutes = ['login', 'checkout', 'payment', 'api/payment'];
        $affected = [];

        foreach ($reports as $report) {
            if ($report->route && array_filter($criticalRoutes, fn ($cr) => str_contains($report->route, $cr))) {
                $affected[] = $report->route;
            }
        }

        return array_unique($affected);
    }

    private function identifyTimePatterns(array $hourly, array $daily): array
    {
        $patterns = [];

        // Night time errors (00:00-06:00)
        $nightErrors = array_sum(array_intersect_key($hourly, array_flip(range(0, 6))));
        if ($nightErrors > array_sum($hourly) * 0.3) {
            $patterns[] = 'high_nighttime_activity';
        }

        // Weekend errors
        $weekendErrors = ($daily[6] ?? 0) + ($daily[7] ?? 0); // Saturday + Sunday
        $weekdayErrors = array_sum(array_intersect_key($daily, array_flip(range(1, 5))));
        if ($weekendErrors > $weekdayErrors * 0.4) {
            $patterns[] = 'high_weekend_activity';
        }

        return $patterns;
    }

    private function calculateOverallHealthScore(array $data): int
    {
        $score = 100;

        // Statistics impact
        $stats = $data['statistics'];
        if ($stats->getCriticalErrorCount() > 0) {
            $score -= min(30, $stats->getCriticalErrorCount() * 5);
        }

        // Anomalies impact
        $anomalies = $data['anomalies'];
        foreach ($anomalies as $anomaly) {
            $penalty = $anomaly['severity'] === 'high' ? 15 : 10;
            $score -= $penalty;
        }

        // Velocity impact
        $velocity = end($data['velocity']);
        if ($velocity && $velocity['velocity_percent'] > 50) {
            $score -= 20;
        }

        return max(0, min(100, $score));
    }
}
