<?php

declare(strict_types=1);

namespace App\Framework\Waf\Feedback\Analytics;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Waf\DetectionCategory;
use App\Framework\Waf\DetectionSeverity;
use App\Framework\Waf\Feedback\FeedbackRepositoryInterface;
use App\Framework\Waf\Feedback\FeedbackType;

/**
 * Generates detailed reports and analytics from WAF feedback data
 */
final readonly class FeedbackReportGenerator
{
    /**
     * @param FeedbackRepositoryInterface $repository Repository for accessing feedback data
     */
    public function __construct(
        private FeedbackRepositoryInterface $repository
    ) {
    }

    /**
     * Generate a comprehensive report on WAF feedback
     *
     * @param Timestamp|null $since Optional timestamp to filter feedback after a certain date
     * @return array<string, mixed> Comprehensive report data
     */
    public function generateComprehensiveReport(?Timestamp $since = null): array
    {
        // Get basic statistics
        $stats = $this->repository->getFeedbackStats();

        // Get feedback by type
        $falsePositives = $this->repository->getFeedbackByFeedbackType(
            FeedbackType::FALSE_POSITIVE,
            $since
        );

        $falseNegatives = $this->repository->getFeedbackByFeedbackType(
            FeedbackType::FALSE_NEGATIVE,
            $since
        );

        $correctDetections = $this->repository->getFeedbackByFeedbackType(
            FeedbackType::CORRECT_DETECTION,
            $since
        );

        $severityAdjustments = $this->repository->getFeedbackByFeedbackType(
            FeedbackType::SEVERITY_ADJUSTMENT,
            $since
        );

        // Calculate overall metrics
        $totalFeedback = count($falsePositives) + count($falseNegatives) + count($correctDetections);

        $accuracy = $totalFeedback > 0
            ? (count($correctDetections) / $totalFeedback) * 100
            : 0;

        $falsePositiveRate = $totalFeedback > 0
            ? (count($falsePositives) / $totalFeedback) * 100
            : 0;

        $falseNegativeRate = $totalFeedback > 0
            ? (count($falseNegatives) / $totalFeedback) * 100
            : 0;

        // Generate category reports
        $categoryReports = $this->generateCategoryReports($since);

        // Generate severity reports
        $severityReports = $this->generateSeverityReports($since);

        // Generate trend analysis
        $trendAnalysis = $this->generateTrendAnalysis($since);

        // Generate common false positive patterns
        $falsePositivePatterns = $this->analyzefalsePositivePatterns($falsePositives);

        // Generate common false negative patterns
        $falseNegativePatterns = $this->analyzeFalseNegativePatterns($falseNegatives);

        // Generate severity adjustment patterns
        $severityAdjustmentPatterns = $this->analyzeSeverityAdjustmentPatterns($severityAdjustments);

        return [
            'generated_at' => Timestamp::now()->toIso8601String(),
            'period' => $since ? 'since_' . $since->toIso8601String() : 'all_time',
            'total_feedback' => $totalFeedback,
            'metrics' => [
                'accuracy' => round($accuracy, 1),
                'false_positive_rate' => round($falsePositiveRate, 1),
                'false_negative_rate' => round($falseNegativeRate, 1),
                'false_positives_count' => count($falsePositives),
                'false_negatives_count' => count($falseNegatives),
                'correct_detections_count' => count($correctDetections),
                'severity_adjustments_count' => count($severityAdjustments),
            ],
            'category_reports' => $categoryReports,
            'severity_reports' => $severityReports,
            'trend_analysis' => $trendAnalysis,
            'patterns' => [
                'false_positives' => $falsePositivePatterns,
                'false_negatives' => $falseNegativePatterns,
                'severity_adjustments' => $severityAdjustmentPatterns,
            ],
            'raw_stats' => $stats,
        ];
    }

    /**
     * Generate reports for each detection category
     *
     * @param Timestamp|null $since Optional timestamp to filter feedback after a certain date
     * @return array<string, array<string, mixed>> Reports by category
     */
    public function generateCategoryReports(?Timestamp $since = null): array
    {
        $reports = [];

        // Get all categories
        $categories = DetectionCategory::cases();

        foreach ($categories as $category) {
            // Get feedback for this category
            $feedback = $this->repository->getFeedbackByCategory($category, $since);

            if (empty($feedback)) {
                continue;
            }

            // Group feedback by type
            $feedbackByType = [];
            foreach ($feedback as $item) {
                $type = $item->feedbackType->value;
                if (! isset($feedbackByType[$type])) {
                    $feedbackByType[$type] = [];
                }
                $feedbackByType[$type][] = $item;
            }

            // Calculate metrics
            $falsePositives = $feedbackByType[FeedbackType::FALSE_POSITIVE->value] ?? [];
            $falseNegatives = $feedbackByType[FeedbackType::FALSE_NEGATIVE->value] ?? [];
            $correctDetections = $feedbackByType[FeedbackType::CORRECT_DETECTION->value] ?? [];
            $severityAdjustments = $feedbackByType[FeedbackType::SEVERITY_ADJUSTMENT->value] ?? [];

            $totalFeedback = count($falsePositives) + count($falseNegatives) + count($correctDetections);

            if ($totalFeedback === 0) {
                continue;
            }

            $accuracy = (count($correctDetections) / $totalFeedback) * 100;
            $falsePositiveRate = (count($falsePositives) / $totalFeedback) * 100;
            $falseNegativeRate = (count($falseNegatives) / $totalFeedback) * 100;

            // Analyze severity adjustments
            $severityChanges = [];
            foreach ($severityAdjustments as $adjustment) {
                $suggestedSeverity = $adjustment->getSuggestedSeverity();
                if ($suggestedSeverity === null) {
                    continue;
                }

                $key = $adjustment->severity->value . '_to_' . $suggestedSeverity->value;
                $severityChanges[$key] = ($severityChanges[$key] ?? 0) + 1;
            }

            $reports[$category->value] = [
                'category' => $category,
                'total_feedback' => $totalFeedback,
                'metrics' => [
                    'accuracy' => round($accuracy, 1),
                    'false_positive_rate' => round($falsePositiveRate, 1),
                    'false_negative_rate' => round($falseNegativeRate, 1),
                    'false_positives_count' => count($falsePositives),
                    'false_negatives_count' => count($falseNegatives),
                    'correct_detections_count' => count($correctDetections),
                    'severity_adjustments_count' => count($severityAdjustments),
                ],
                'severity_changes' => $severityChanges,
                'is_owasp_top10' => $category->isOwaspTop10(),
                'owasp_rank' => $category->getOwaspRank(),
                'default_severity' => $category->getDefaultSeverity()->value,
            ];
        }

        // Sort by total feedback count (descending)
        uasort($reports, fn ($a, $b) => $b['total_feedback'] <=> $a['total_feedback']);

        return $reports;
    }

    /**
     * Generate reports for each severity level
     *
     * @param Timestamp|null $since Optional timestamp to filter feedback after a certain date
     * @return array<string, array<string, mixed>> Reports by severity
     */
    public function generateSeverityReports(?Timestamp $since = null): array
    {
        $reports = [];

        // Get all severities
        $severities = DetectionSeverity::cases();

        foreach ($severities as $severity) {
            $reports[$severity->value] = [
                'severity' => $severity,
                'feedback_by_type' => [
                    'false_positive' => 0,
                    'false_negative' => 0,
                    'correct_detection' => 0,
                    'severity_adjustment' => 0,
                ],
                'total_feedback' => 0,
            ];
        }

        // Get all feedback
        $allFeedback = [];
        foreach (FeedbackType::cases() as $type) {
            $feedback = $this->repository->getFeedbackByFeedbackType($type, $since);
            $allFeedback = array_merge($allFeedback, $feedback);
        }

        // Group feedback by severity and type
        foreach ($allFeedback as $item) {
            $severityKey = $item->severity->value;
            $typeKey = $item->feedbackType->value;

            if (! isset($reports[$severityKey])) {
                continue;
            }

            $reports[$severityKey]['feedback_by_type'][$typeKey] =
                ($reports[$severityKey]['feedback_by_type'][$typeKey] ?? 0) + 1;

            $reports[$severityKey]['total_feedback']++;
        }

        // Calculate metrics for each severity
        foreach ($reports as $severity => &$report) {
            if ($report['total_feedback'] === 0) {
                continue;
            }

            $falsePositives = $report['feedback_by_type']['false_positive'];
            $falseNegatives = $report['feedback_by_type']['false_negative'];
            $correctDetections = $report['feedback_by_type']['correct_detection'];

            $totalClassifiable = $falsePositives + $falseNegatives + $correctDetections;

            if ($totalClassifiable > 0) {
                $accuracy = ($correctDetections / $totalClassifiable) * 100;
                $falsePositiveRate = ($falsePositives / $totalClassifiable) * 100;
                $falseNegativeRate = ($falseNegatives / $totalClassifiable) * 100;

                $report['metrics'] = [
                    'accuracy' => round($accuracy, 1),
                    'false_positive_rate' => round($falsePositiveRate, 1),
                    'false_negative_rate' => round($falseNegativeRate, 1),
                ];
            }
        }

        // Sort by total feedback count (descending)
        uasort($reports, fn ($a, $b) => $b['total_feedback'] <=> $a['total_feedback']);

        return $reports;
    }

    /**
     * Generate trend analysis for feedback over time
     *
     * @param Timestamp|null $since Optional timestamp to filter feedback after a certain date
     * @return array<string, mixed> Trend analysis data
     */
    public function generateTrendAnalysis(?Timestamp $since = null): array
    {
        // Get basic statistics
        $stats = $this->repository->getFeedbackStats();

        // Extract trend data
        $trendData = $stats['trend_data'] ?? [];

        // Calculate moving averages
        $movingAverages = $this->calculateMovingAverages($trendData, 7);

        // Calculate accuracy trend
        $accuracyTrend = $this->calculateAccuracyTrend($trendData);

        // Calculate detection rate trend
        $detectionRateTrend = $this->calculateDetectionRateTrend($trendData);

        return [
            'raw_trend_data' => $trendData,
            'moving_averages' => $movingAverages,
            'accuracy_trend' => $accuracyTrend,
            'detection_rate_trend' => $detectionRateTrend,
        ];
    }

    /**
     * Analyze patterns in false positive feedback
     *
     * @param array $falsePositives Array of false positive feedback
     * @return array<string, mixed> Analysis of false positive patterns
     */
    private function analyzefalsePositivePatterns(array $falsePositives): array
    {
        if (empty($falsePositives)) {
            return [
                'count' => 0,
                'message' => 'No false positive feedback available for analysis',
            ];
        }

        // Group by category
        $byCategory = [];
        foreach ($falsePositives as $feedback) {
            $category = $feedback->category->value;
            if (! isset($byCategory[$category])) {
                $byCategory[$category] = [];
            }
            $byCategory[$category][] = $feedback;
        }

        // Find top categories
        $categoryCounts = array_map('count', $byCategory);
        arsort($categoryCounts);

        // Analyze comments for patterns
        $commentPatterns = [];
        foreach ($falsePositives as $feedback) {
            if (empty($feedback->comment)) {
                continue;
            }

            // Extract keywords from comment
            $keywords = $this->extractKeywords($feedback->comment);

            foreach ($keywords as $keyword) {
                $commentPatterns[$keyword] = ($commentPatterns[$keyword] ?? 0) + 1;
            }
        }

        // Sort by frequency
        arsort($commentPatterns);

        // Limit to top 10
        $commentPatterns = array_slice($commentPatterns, 0, 10, true);

        return [
            'count' => count($falsePositives),
            'top_categories' => $categoryCounts,
            'comment_patterns' => $commentPatterns,
        ];
    }

    /**
     * Analyze patterns in false negative feedback
     *
     * @param array $falseNegatives Array of false negative feedback
     * @return array<string, mixed> Analysis of false negative patterns
     */
    private function analyzeFalseNegativePatterns(array $falseNegatives): array
    {
        if (empty($falseNegatives)) {
            return [
                'count' => 0,
                'message' => 'No false negative feedback available for analysis',
            ];
        }

        // Group by category
        $byCategory = [];
        foreach ($falseNegatives as $feedback) {
            $category = $feedback->category->value;
            if (! isset($byCategory[$category])) {
                $byCategory[$category] = [];
            }
            $byCategory[$category][] = $feedback;
        }

        // Find top categories
        $categoryCounts = array_map('count', $byCategory);
        arsort($categoryCounts);

        // Analyze comments for patterns
        $commentPatterns = [];
        foreach ($falseNegatives as $feedback) {
            if (empty($feedback->comment)) {
                continue;
            }

            // Extract keywords from comment
            $keywords = $this->extractKeywords($feedback->comment);

            foreach ($keywords as $keyword) {
                $commentPatterns[$keyword] = ($commentPatterns[$keyword] ?? 0) + 1;
            }
        }

        // Sort by frequency
        arsort($commentPatterns);

        // Limit to top 10
        $commentPatterns = array_slice($commentPatterns, 0, 10, true);

        return [
            'count' => count($falseNegatives),
            'top_categories' => $categoryCounts,
            'comment_patterns' => $commentPatterns,
        ];
    }

    /**
     * Analyze patterns in severity adjustment feedback
     *
     * @param array $severityAdjustments Array of severity adjustment feedback
     * @return array<string, mixed> Analysis of severity adjustment patterns
     */
    private function analyzeSeverityAdjustmentPatterns(array $severityAdjustments): array
    {
        if (empty($severityAdjustments)) {
            return [
                'count' => 0,
                'message' => 'No severity adjustment feedback available for analysis',
            ];
        }

        // Group by category
        $byCategory = [];
        foreach ($severityAdjustments as $feedback) {
            $category = $feedback->category->value;
            if (! isset($byCategory[$category])) {
                $byCategory[$category] = [];
            }
            $byCategory[$category][] = $feedback;
        }

        // Find top categories
        $categoryCounts = array_map('count', $byCategory);
        arsort($categoryCounts);

        // Analyze severity changes
        $severityChanges = [];
        foreach ($severityAdjustments as $feedback) {
            $suggestedSeverity = $feedback->getSuggestedSeverity();
            if ($suggestedSeverity === null) {
                continue;
            }

            $key = $feedback->severity->value . '_to_' . $suggestedSeverity->value;
            $severityChanges[$key] = ($severityChanges[$key] ?? 0) + 1;
        }

        // Sort by frequency
        arsort($severityChanges);

        return [
            'count' => count($severityAdjustments),
            'top_categories' => $categoryCounts,
            'severity_changes' => $severityChanges,
        ];
    }

    /**
     * Calculate moving averages for trend data
     *
     * @param array $trendData Raw trend data
     * @param int $window Window size for moving average
     * @return array<string, array<string, float>> Moving averages
     */
    private function calculateMovingAverages(array $trendData, int $window): array
    {
        if (empty($trendData)) {
            return [];
        }

        $result = [];
        $dates = array_keys($trendData);
        sort($dates);

        foreach ($dates as $i => $date) {
            if ($i < $window - 1) {
                continue; // Not enough data for window
            }

            $windowData = [];
            for ($j = $i - $window + 1; $j <= $i; $j++) {
                $windowDate = $dates[$j];
                foreach ($trendData[$windowDate] as $type => $count) {
                    $windowData[$type] = ($windowData[$type] ?? 0) + $count;
                }
            }

            $result[$date] = [];
            foreach ($windowData as $type => $count) {
                $result[$date][$type] = $count / $window;
            }
        }

        return $result;
    }

    /**
     * Calculate accuracy trend from trend data
     *
     * @param array $trendData Raw trend data
     * @return array<string, float> Accuracy trend
     */
    private function calculateAccuracyTrend(array $trendData): array
    {
        if (empty($trendData)) {
            return [];
        }

        $result = [];

        foreach ($trendData as $date => $data) {
            $falsePositives = $data[FeedbackType::FALSE_POSITIVE->value] ?? 0;
            $falseNegatives = $data[FeedbackType::FALSE_NEGATIVE->value] ?? 0;
            $correctDetections = $data[FeedbackType::CORRECT_DETECTION->value] ?? 0;

            $total = $falsePositives + $falseNegatives + $correctDetections;

            if ($total > 0) {
                $result[$date] = ($correctDetections / $total) * 100;
            }
        }

        return $result;
    }

    /**
     * Calculate detection rate trend from trend data
     *
     * @param array $trendData Raw trend data
     * @return array<string, array<string, float>> Detection rate trend
     */
    private function calculateDetectionRateTrend(array $trendData): array
    {
        if (empty($trendData)) {
            return [];
        }

        $result = [];

        foreach ($trendData as $date => $data) {
            $falsePositives = $data[FeedbackType::FALSE_POSITIVE->value] ?? 0;
            $falseNegatives = $data[FeedbackType::FALSE_NEGATIVE->value] ?? 0;
            $correctDetections = $data[FeedbackType::CORRECT_DETECTION->value] ?? 0;

            $total = $falsePositives + $falseNegatives + $correctDetections;

            if ($total > 0) {
                $result[$date] = [
                    'false_positive_rate' => ($falsePositives / $total) * 100,
                    'false_negative_rate' => ($falseNegatives / $total) * 100,
                    'correct_detection_rate' => ($correctDetections / $total) * 100,
                ];
            }
        }

        return $result;
    }

    /**
     * Extract keywords from a comment
     *
     * @param string $comment The comment to extract keywords from
     * @return array<string> Array of keywords
     */
    private function extractKeywords(string $comment): array
    {
        // Simple keyword extraction - in a real implementation, this would be more sophisticated
        $comment = strtolower($comment);
        $comment = preg_replace('/[^\p{L}\p{N}\s]/u', '', $comment);
        $words = preg_split('/\s+/', $comment);

        // Filter out common words and short words
        $stopWords = ['the', 'and', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'with', 'by', 'is', 'are', 'was', 'were'];
        $words = array_filter($words, function ($word) use ($stopWords) {
            return strlen($word) > 3 && ! in_array($word, $stopWords);
        });

        return array_values($words);
    }
}
