<?php

declare(strict_types=1);

namespace App\Framework\Database\QueryOptimization\Analysis;

use App\Framework\Database\QueryOptimization\ValueObjects\QueryLog;
use App\Framework\Database\QueryOptimization\ValueObjects\QueryPattern;
use App\Framework\Database\QueryOptimization\ValueObjects\NPlusOneDetection;

/**
 * N+1 Query Detector
 *
 * Analyzes query logs to detect N+1 query problems
 */
final readonly class NPlusOneDetector
{
    public function __construct(
        private int $minExecutionCount = 5,
        private float $minSeverityScore = 4.0
    ) {}

    /**
     * Analyze query logs and detect N+1 patterns
     *
     * @param array<QueryLog> $queryLogs
     * @return array<NPlusOneDetection>
     */
    public function analyze(array $queryLogs): array
    {
        // Group queries by pattern
        $patterns = $this->groupByPattern($queryLogs);

        // Filter potential N+1 queries
        $nPlusOnePatterns = array_filter(
            $patterns,
            fn(QueryPattern $pattern) => $pattern->isPotentialNPlusOne()
                && $pattern->getNPlusOneSeverity() >= $this->minSeverityScore
        );

        // Generate detections with recommendations
        return array_map(
            fn(QueryPattern $pattern) => $this->createDetection($pattern),
            $nPlusOnePatterns
        );
    }

    /**
     * Group queries by their normalized pattern
     *
     * @param array<QueryLog> $queryLogs
     * @return array<QueryPattern>
     */
    private function groupByPattern(array $queryLogs): array
    {
        $grouped = [];

        foreach ($queryLogs as $log) {
            $pattern = $log->getPattern();

            if (!isset($grouped[$pattern])) {
                $grouped[$pattern] = [];
            }

            $grouped[$pattern][] = $log;
        }

        // Convert to QueryPattern objects
        $patterns = [];
        foreach ($grouped as $patternStr => $queries) {
            if (count($queries) >= $this->minExecutionCount) {
                $patterns[] = new QueryPattern($patternStr, $queries);
            }
        }

        return $patterns;
    }

    /**
     * Create N+1 detection with recommendation
     */
    private function createDetection(QueryPattern $pattern): NPlusOneDetection
    {
        $recommendation = $this->generateRecommendation($pattern);
        $metadata = $this->generateMetadata($pattern);

        return new NPlusOneDetection(
            pattern: $pattern,
            recommendation: $recommendation,
            metadata: $metadata
        );
    }

    /**
     * Generate recommendation for fixing N+1
     */
    private function generateRecommendation(QueryPattern $pattern): string
    {
        $tableName = $pattern->getTableName();
        $callers = $pattern->getCallerLocations();

        $recommendation = "N+1 Query detected in table '{$tableName}'\n\n";

        // Caller location
        if ($pattern->hasConsistentCaller()) {
            $recommendation .= "Location: {$callers[0]}\n\n";
        } else {
            $recommendation .= "Multiple locations:\n";
            foreach ($callers as $caller) {
                $recommendation .= "  - {$caller}\n";
            }
            $recommendation .= "\n";
        }

        // Recommendation
        $recommendation .= "Recommended fix:\n";
        $recommendation .= "1. Use eager loading instead of lazy loading\n";
        $recommendation .= "   Example: \$repository->findWithRelations(\$ids, ['{$tableName}'])\n\n";
        $recommendation .= "2. Or use batch loading:\n";
        $recommendation .= "   Example: \$repository->batchLoad(\$ids)\n\n";
        $recommendation .= "3. Or add JOIN to initial query:\n";
        $recommendation .= "   Example: SELECT * FROM parent LEFT JOIN {$tableName} ON ...\n";

        return $recommendation;
    }

    /**
     * Generate metadata for detection
     */
    private function generateMetadata(QueryPattern $pattern): array
    {
        return [
            'execution_count' => $pattern->getExecutionCount(),
            'total_time_ms' => $pattern->getTotalExecutionTimeMs(),
            'average_time_ms' => $pattern->getAverageExecutionTimeMs(),
            'table_name' => $pattern->getTableName(),
            'first_query_sql' => $pattern->queries[0]->sql,
            'sample_bindings' => $pattern->queries[0]->bindings,
        ];
    }

    /**
     * Quick check if query logs contain potential N+1
     *
     * @param array<QueryLog> $queryLogs
     */
    public function hasNPlusOne(array $queryLogs): bool
    {
        $patterns = $this->groupByPattern($queryLogs);

        foreach ($patterns as $pattern) {
            if ($pattern->isPotentialNPlusOne()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get statistics about query patterns
     *
     * @param array<QueryLog> $queryLogs
     */
    public function getStatistics(array $queryLogs): array
    {
        $patterns = $this->groupByPattern($queryLogs);
        $nPlusOnePatterns = array_filter(
            $patterns,
            fn(QueryPattern $p) => $p->isPotentialNPlusOne()
        );

        $totalQueries = count($queryLogs);
        $totalTime = array_reduce(
            $queryLogs,
            fn(float $sum, QueryLog $log) => $sum + $log->executionTimeMs,
            0.0
        );

        $nPlusOneQueries = array_reduce(
            $nPlusOnePatterns,
            fn(int $sum, QueryPattern $p) => $sum + $p->getExecutionCount(),
            0
        );

        $nPlusOneTime = array_reduce(
            $nPlusOnePatterns,
            fn(float $sum, QueryPattern $p) => $sum + $p->getTotalExecutionTimeMs(),
            0.0
        );

        return [
            'total_queries' => $totalQueries,
            'total_patterns' => count($patterns),
            'total_execution_time_ms' => $totalTime,
            'n_plus_one_patterns' => count($nPlusOnePatterns),
            'n_plus_one_queries' => $nPlusOneQueries,
            'n_plus_one_time_ms' => $nPlusOneTime,
            'n_plus_one_percentage' => $totalQueries > 0
                ? ($nPlusOneQueries / $totalQueries * 100)
                : 0.0,
            'time_wasted_percentage' => $totalTime > 0
                ? ($nPlusOneTime / $totalTime * 100)
                : 0.0,
        ];
    }
}
