<?php

declare(strict_types=1);

namespace App\Framework\Console\Analytics\Repository;

use App\Framework\Console\Analytics\ValueObjects\CommandPopularity;
use App\Framework\Console\Analytics\ValueObjects\CommandUsageMetric;
use App\Framework\Console\Analytics\ValueObjects\Period;
use App\Framework\Console\Analytics\ValueObjects\UsageStatistics;
use App\Framework\Console\Analytics\ValueObjects\UsageTrend;
use App\Framework\Console\Analytics\ValueObjects\UsageTrendPoint;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Database\Connection;
use DateTimeImmutable;

final readonly class DatabaseCommandUsageRepository implements CommandUsageRepository
{
    public function __construct(
        private Connection $connection
    ) {
    }

    public function store(CommandUsageMetric $metric): void
    {
        $sql = "INSERT INTO command_usage_analytics (
            command_name, executed_at, execution_time_ms, exit_code,
            argument_count, user_id, metadata
        ) VALUES (?, ?, ?, ?, ?, ?, ?)";

        $this->connection->execute($sql, [
            $metric->commandName,
            $metric->executedAt->format('Y-m-d H:i:s'),
            $metric->executionTime->toMilliseconds(),
            $metric->exitCode->value,
            $metric->argumentCount,
            $metric->userId,
            $metric->metadata ? json_encode($metric->metadata) : null,
        ]);
    }

    public function storeMultiple(CommandUsageMetric ...$metrics): void
    {
        if (empty($metrics)) {
            return;
        }

        $values = [];
        $placeholders = [];

        foreach ($metrics as $metric) {
            $placeholders[] = "(?, ?, ?, ?, ?, ?, ?)";
            $values[] = $metric->commandName;
            $values[] = $metric->executedAt->format('Y-m-d H:i:s');
            $values[] = $metric->executionTime->toMilliseconds();
            $values[] = $metric->exitCode->value;
            $values[] = $metric->argumentCount;
            $values[] = $metric->userId;
            $values[] = $metric->metadata ? json_encode($metric->metadata) : null;
        }

        $sql = "INSERT INTO command_usage_analytics (
            command_name, executed_at, execution_time_ms, exit_code,
            argument_count, user_id, metadata
        ) VALUES " . implode(', ', $placeholders);

        $this->connection->execute($sql, $values);
    }

    public function getUsageStatistics(string $commandName, ?DateTimeImmutable $since = null): UsageStatistics
    {
        $whereClause = 'WHERE command_name = ?';
        $params = [$commandName];

        if ($since) {
            $whereClause .= ' AND executed_at >= ?';
            $params[] = $since->format('Y-m-d H:i:s');
        }

        $sql = "SELECT
            COUNT(*) as total_executions,
            SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) as successful_executions,
            SUM(CASE WHEN exit_code != 0 THEN 1 ELSE 0 END) as failed_executions,
            AVG(execution_time_ms) as avg_execution_time,
            MIN(execution_time_ms) as min_execution_time,
            MAX(execution_time_ms) as max_execution_time,
            GROUP_CONCAT(DISTINCT exit_code) as exit_codes
        FROM command_usage_analytics
        {$whereClause}";

        $result = $this->connection->query($sql, $params);

        if (empty($result) || $result[0]['total_executions'] == 0) {
            return UsageStatistics::empty($commandName);
        }

        $row = $result[0];
        $hourlyDistribution = $this->getHourlyDistribution($commandName, $since);
        $exitCodeDistribution = $this->getExitCodeDistribution($commandName, $since);

        return new UsageStatistics(
            commandName: $commandName,
            totalExecutions: (int) $row['total_executions'],
            successfulExecutions: (int) $row['successful_executions'],
            failedExecutions: (int) $row['failed_executions'],
            averageExecutionTime: Duration::fromMilliseconds((float) $row['avg_execution_time']),
            minExecutionTime: Duration::fromMilliseconds((float) $row['min_execution_time']),
            maxExecutionTime: Duration::fromMilliseconds((float) $row['max_execution_time']),
            medianExecutionTime: $this->getMedianExecutionTime($commandName, $since),
            hourlyDistribution: $hourlyDistribution,
            exitCodeDistribution: $exitCodeDistribution
        );
    }

    public function getPopularCommands(int $limit = 10, ?DateTimeImmutable $since = null): array
    {
        $whereClause = '';
        $params = [];

        if ($since) {
            $whereClause = 'WHERE executed_at >= ?';
            $params[] = $since->format('Y-m-d H:i:s');
        }

        $sql = "SELECT
            command_name,
            COUNT(*) as total_executions,
            AVG(execution_time_ms) as avg_execution_time,
            SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) / COUNT(*) * 100 as success_rate
        FROM command_usage_analytics
        {$whereClause}
        GROUP BY command_name
        ORDER BY total_executions DESC
        LIMIT ?";

        $params[] = $limit;
        $results = $this->connection->query($sql, $params);

        $totalExecutions = $this->getTotalExecutions($since);
        $popularCommands = [];

        foreach ($results as $index => $row) {
            $usagePercentage = $totalExecutions > 0
                ? Percentage::fromValue(($row['total_executions'] / $totalExecutions) * 100)
                : Percentage::zero();

            $popularCommands[] = new CommandPopularity(
                commandName: $row['command_name'],
                rank: $index + 1,
                totalExecutions: (int) $row['total_executions'],
                usagePercentage: $usagePercentage,
                averageExecutionTime: Duration::fromMilliseconds((float) $row['avg_execution_time']),
                successRate: Percentage::fromValue((float) $row['success_rate'])
            );
        }

        return $popularCommands;
    }

    public function getTrendData(
        string $commandName,
        Period $period,
        DateTimeImmutable $start,
        DateTimeImmutable $end
    ): array {
        $groupByClause = match ($period) {
            Period::HOURLY => "DATE_FORMAT(executed_at, '%Y-%m-%d %H:00:00')",
            Period::DAILY => "DATE(executed_at)",
            Period::WEEKLY => "YEARWEEK(executed_at, 1)",
            Period::MONTHLY => "DATE_FORMAT(executed_at, '%Y-%m')",
            Period::YEARLY => "YEAR(executed_at)"
        };

        $sql = "SELECT
            {$groupByClause} as period_date,
            COUNT(*) as execution_count,
            AVG(execution_time_ms) as avg_execution_time,
            SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) / COUNT(*) * 100 as success_rate
        FROM command_usage_analytics
        WHERE command_name = ?
        AND executed_at BETWEEN ? AND ?
        GROUP BY {$groupByClause}
        ORDER BY period_date";

        $results = $this->connection->query($sql, [
            $commandName,
            $start->format('Y-m-d H:i:s'),
            $end->format('Y-m-d H:i:s'),
        ]);

        $trendPoints = [];
        foreach ($results as $row) {
            $date = match ($period) {
                Period::WEEKLY => new DateTimeImmutable($row['period_date'] . '-1'), // First day of week
                default => new DateTimeImmutable($row['period_date'])
            };

            $trendPoints[] = new UsageTrendPoint(
                date: $date,
                executionCount: (int) $row['execution_count'],
                averageExecutionTime: Duration::fromMilliseconds((float) $row['avg_execution_time']),
                successRate: Percentage::fromValue((float) $row['success_rate'])
            );
        }

        // Calculate trend direction and strength
        $trendDirection = $this->calculateTrendDirection($trendPoints);
        $trendStrength = $this->calculateTrendStrength($trendPoints);

        return [new UsageTrend(
            commandName: $commandName,
            period: $period,
            startDate: $start,
            endDate: $end,
            dataPoints: $trendPoints,
            trendDirection: $trendDirection,
            trendStrength: $trendStrength
        )];
    }

    public function getAllCommandNames(): array
    {
        $sql = "SELECT DISTINCT command_name FROM command_usage_analytics ORDER BY command_name";
        $results = $this->connection->query($sql);

        return array_column($results, 'command_name');
    }

    public function cleanup(DateTimeImmutable $before): int
    {
        $sql = "DELETE FROM command_usage_analytics WHERE executed_at < ?";

        return $this->connection->execute($sql, [$before->format('Y-m-d H:i:s')]);
    }

    private function getHourlyDistribution(string $commandName, ?DateTimeImmutable $since): array
    {
        $whereClause = 'WHERE command_name = ?';
        $params = [$commandName];

        if ($since) {
            $whereClause .= ' AND executed_at >= ?';
            $params[] = $since->format('Y-m-d H:i:s');
        }

        $sql = "SELECT
            HOUR(executed_at) as hour,
            COUNT(*) as count
        FROM command_usage_analytics
        {$whereClause}
        GROUP BY HOUR(executed_at)
        ORDER BY hour";

        $results = $this->connection->query($sql, $params);

        // Initialize all hours to 0
        $distribution = array_fill(0, 24, 0);

        foreach ($results as $row) {
            $distribution[(int) $row['hour']] = (int) $row['count'];
        }

        return $distribution;
    }

    private function getExitCodeDistribution(string $commandName, ?DateTimeImmutable $since): array
    {
        $whereClause = 'WHERE command_name = ?';
        $params = [$commandName];

        if ($since) {
            $whereClause .= ' AND executed_at >= ?';
            $params[] = $since->format('Y-m-d H:i:s');
        }

        $sql = "SELECT
            exit_code,
            COUNT(*) as count
        FROM command_usage_analytics
        {$whereClause}
        GROUP BY exit_code
        ORDER BY exit_code";

        $results = $this->connection->query($sql, $params);

        $distribution = [];
        foreach ($results as $row) {
            $distribution[$row['exit_code']] = (int) $row['count'];
        }

        return $distribution;
    }

    private function getMedianExecutionTime(string $commandName, ?DateTimeImmutable $since): ?Duration
    {
        $whereClause = 'WHERE command_name = ?';
        $params = [$commandName];

        if ($since) {
            $whereClause .= ' AND executed_at >= ?';
            $params[] = $since->format('Y-m-d H:i:s');
        }

        // MySQL median calculation using percentiles
        $sql = "SELECT
            PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY execution_time_ms) as median_time
        FROM command_usage_analytics
        {$whereClause}";

        $result = $this->connection->query($sql, $params);

        if (empty($result) || $result[0]['median_time'] === null) {
            return null;
        }

        return Duration::fromMilliseconds((float) $result[0]['median_time']);
    }

    private function getTotalExecutions(?DateTimeImmutable $since): int
    {
        $whereClause = '';
        $params = [];

        if ($since) {
            $whereClause = 'WHERE executed_at >= ?';
            $params[] = $since->format('Y-m-d H:i:s');
        }

        $sql = "SELECT COUNT(*) as total FROM command_usage_analytics {$whereClause}";
        $result = $this->connection->query($sql, $params);

        return (int) $result[0]['total'];
    }

    private function calculateTrendDirection(array $trendPoints): float
    {
        if (count($trendPoints) < 2) {
            return 0.0;
        }

        $xValues = array_keys($trendPoints);
        $yValues = array_map(fn ($point) => $point->executionCount, $trendPoints);

        // Simple linear regression slope calculation
        $n = count($trendPoints);
        $sumX = array_sum($xValues);
        $sumY = array_sum($yValues);
        $sumXY = 0;
        $sumXX = 0;

        for ($i = 0; $i < $n; $i++) {
            $sumXY += $xValues[$i] * $yValues[$i];
            $sumXX += $xValues[$i] * $xValues[$i];
        }

        $denominator = $n * $sumXX - $sumX * $sumX;
        if ($denominator == 0) {
            return 0.0;
        }

        return ($n * $sumXY - $sumX * $sumY) / $denominator;
    }

    private function calculateTrendStrength(array $trendPoints): float
    {
        if (count($trendPoints) < 2) {
            return 0.0;
        }

        $values = array_map(fn ($point) => $point->executionCount, $trendPoints);
        $mean = array_sum($values) / count($values);

        // Calculate coefficient of variation
        $variance = 0;
        foreach ($values as $value) {
            $variance += ($value - $mean) ** 2;
        }
        $variance /= count($values);
        $stdDev = sqrt($variance);

        if ($mean == 0) {
            return 0.0;
        }

        $coefficientOfVariation = $stdDev / $mean;

        // Convert to 0-1 scale where higher variation = stronger trend
        return min(1.0, $coefficientOfVariation);
    }
}
