<?php

declare(strict_types=1);

namespace App\Framework\ErrorReporting\Storage;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\ConnectionInterface;
use App\Framework\ErrorReporting\ErrorReport;
use App\Framework\ErrorReporting\ErrorReportCriteria;
use App\Framework\ErrorReporting\ErrorStatistics;
use DateTimeImmutable;

/**
 * Database storage implementation for error reports
 */
final readonly class DatabaseErrorReportStorage implements ErrorReportStorageInterface
{
    public function __construct(
        private ConnectionInterface $connection
    ) {
    }

    public function store(ErrorReport $report): void
    {
        $sql = '
            INSERT INTO error_reports (
                id, timestamp, level, message, exception, file, line, trace, context,
                user_id, session_id, request_id, user_agent, ip_address, route, method,
                request_data, execution_time, memory_usage, tags, breadcrumbs,
                release, environment, server_info, custom_data, fingerprint, severity_level
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ';

        $this->connection->execute($sql, [
            $report->id,
            $report->timestamp->format('Y-m-d H:i:s'),
            $report->level,
            $report->message,
            $report->exception,
            $report->file,
            $report->line,
            $report->trace,
            json_encode($report->context),
            $report->userId,
            $report->sessionId,
            $report->requestId,
            $report->userAgent,
            $report->ipAddress,
            $report->route,
            $report->method,
            $report->requestData ? json_encode($report->requestData) : null,
            $report->executionTime?->toMilliseconds(),
            $report->memoryUsage,
            json_encode($report->tags),
            json_encode($report->breadcrumbs),
            $report->release,
            $report->environment,
            json_encode($report->serverInfo),
            json_encode($report->customData),
            $report->getFingerprint(),
            $report->getSeverityLevel(),
        ]);
    }

    public function storeBatch(array $reports): void
    {
        if (empty($reports)) {
            return;
        }

        $this->connection->transaction(function () use ($reports) {
            foreach ($reports as $report) {
                $this->store($report);
            }
        });
    }

    public function find(string $reportId): ?ErrorReport
    {
        $sql = 'SELECT * FROM error_reports WHERE id = ?';
        $result = $this->connection->query($sql, [$reportId]);

        if (empty($result)) {
            return null;
        }

        return $this->hydrate($result[0]);
    }

    public function findRecent(int $limit = 100, int $offset = 0): array
    {
        $sql = '
            SELECT * FROM error_reports 
            ORDER BY timestamp DESC 
            LIMIT ? OFFSET ?
        ';

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

        return array_map([$this, 'hydrate'], $results);
    }

    public function findByCriteria(ErrorReportCriteria $criteria): array
    {
        [$sql, $params] = $this->buildCriteriaQuery($criteria);

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

        return array_map([$this, 'hydrate'], $results);
    }

    public function countByCriteria(ErrorReportCriteria $criteria): int
    {
        [$sql, $params] = $this->buildCriteriaQuery($criteria, true);

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

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

    public function getStatistics(DateTimeImmutable $from, DateTimeImmutable $to): ErrorStatistics
    {
        // Total and unique errors
        $totalSql = 'SELECT COUNT(*) as total, COUNT(DISTINCT fingerprint) as unique FROM error_reports WHERE timestamp BETWEEN ? AND ?';
        $totalResult = $this->connection->query($totalSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $totalErrors = (int) ($totalResult[0]['total'] ?? 0);
        $uniqueErrors = (int) ($totalResult[0]['unique'] ?? 0);

        // Errors by level
        $levelSql = 'SELECT level, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? GROUP BY level ORDER BY count DESC';
        $levelResults = $this->connection->query($levelSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $errorsByLevel = [];
        foreach ($levelResults as $row) {
            $errorsByLevel[$row['level']] = (int) $row['count'];
        }

        // Errors by exception
        $exceptionSql = 'SELECT exception, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? GROUP BY exception ORDER BY count DESC LIMIT 10';
        $exceptionResults = $this->connection->query($exceptionSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $errorsByException = [];
        foreach ($exceptionResults as $row) {
            $errorsByException[$row['exception']] = (int) $row['count'];
        }

        // Errors by route
        $routeSql = 'SELECT route, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? AND route IS NOT NULL GROUP BY route ORDER BY count DESC LIMIT 10';
        $routeResults = $this->connection->query($routeSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $errorsByRoute = [];
        foreach ($routeResults as $row) {
            $errorsByRoute[$row['route']] = (int) $row['count'];
        }

        // Errors by user
        $userSql = 'SELECT user_id, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? AND user_id IS NOT NULL GROUP BY user_id ORDER BY count DESC LIMIT 10';
        $userResults = $this->connection->query($userSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $errorsByUser = [];
        foreach ($userResults as $row) {
            $errorsByUser[$row['user_id']] = (int) $row['count'];
        }

        // Errors by hour
        $hourSql = 'SELECT HOUR(timestamp) as hour, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? GROUP BY HOUR(timestamp) ORDER BY hour';
        $hourResults = $this->connection->query($hourSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $errorsByHour = [];
        foreach ($hourResults as $row) {
            $errorsByHour[(int) $row['hour']] = (int) $row['count'];
        }

        // Errors by day
        $daySql = 'SELECT DATE(timestamp) as day, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? GROUP BY DATE(timestamp) ORDER BY day';
        $dayResults = $this->connection->query($daySql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $errorsByDay = [];
        foreach ($dayResults as $row) {
            $errorsByDay[$row['day']] = (int) $row['count'];
        }

        // Top errors by fingerprint
        $topErrorsSql = '
            SELECT fingerprint, exception, file, line, COUNT(*) as count, MAX(timestamp) as last_seen
            FROM error_reports 
            WHERE timestamp BETWEEN ? AND ? 
            GROUP BY fingerprint, exception, file, line 
            ORDER BY count DESC 
            LIMIT 10
        ';
        $topErrorsResults = $this->connection->query($topErrorsSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $topErrors = [];
        foreach ($topErrorsResults as $row) {
            $topErrors[] = [
                'fingerprint' => $row['fingerprint'],
                'exception' => $row['exception'],
                'file' => $row['file'],
                'line' => (int) $row['line'],
                'count' => (int) $row['count'],
                'last_seen' => $row['last_seen'],
            ];
        }

        // Environment breakdown
        $envSql = 'SELECT environment, COUNT(*) as count FROM error_reports WHERE timestamp BETWEEN ? AND ? GROUP BY environment ORDER BY count DESC';
        $envResults = $this->connection->query($envSql, [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')]);
        $environmentBreakdown = [];
        foreach ($envResults as $row) {
            $environmentBreakdown[$row['environment'] ?? 'unknown'] = (int) $row['count'];
        }

        // Calculate error rate (simplified - would need request metrics for accurate rate)
        $errorRate = 0.0; // This would need additional request tracking

        return new ErrorStatistics(
            totalErrors: $totalErrors,
            uniqueErrors: $uniqueErrors,
            errorsByLevel: $errorsByLevel,
            errorsByException: $errorsByException,
            errorsByRoute: $errorsByRoute,
            errorsByUser: $errorsByUser,
            errorsByHour: $errorsByHour,
            errorsByDay: $errorsByDay,
            topErrors: $topErrors,
            trendingErrors: [], // Would need more complex calculation
            errorRate: $errorRate,
            responseTimeImpact: [], // Would need response time tracking
            environmentBreakdown: $environmentBreakdown,
            periodStart: $from,
            periodEnd: $to
        );
    }

    public function getTrends(DateTimeImmutable $from, DateTimeImmutable $to, string $groupBy = 'hour'): array
    {
        $groupFormat = match ($groupBy) {
            'minute' => '%Y-%m-%d %H:%i:00',
            'hour' => '%Y-%m-%d %H:00:00',
            'day' => '%Y-%m-%d',
            'week' => '%Y-%u',
            'month' => '%Y-%m',
            default => '%Y-%m-%d %H:00:00',
        };

        $sql = "
            SELECT DATE_FORMAT(timestamp, '{$groupFormat}') as period, COUNT(*) as count
            FROM error_reports 
            WHERE timestamp BETWEEN ? AND ?
            GROUP BY DATE_FORMAT(timestamp, '{$groupFormat}')
            ORDER BY period
        ";

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

        $trends = [];
        foreach ($results as $row) {
            $trends[] = [
                'period' => $row['period'],
                'count' => (int) $row['count'],
            ];
        }

        return $trends;
    }

    public function getTopErrors(DateTimeImmutable $from, DateTimeImmutable $to, int $limit = 10): array
    {
        $sql = '
            SELECT fingerprint, exception, message, file, line, COUNT(*) as count, MAX(timestamp) as last_seen
            FROM error_reports 
            WHERE timestamp BETWEEN ? AND ?
            GROUP BY fingerprint, exception, message, file, line
            ORDER BY count DESC
            LIMIT ?
        ';

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

        $topErrors = [];
        foreach ($results as $row) {
            $topErrors[] = [
                'fingerprint' => $row['fingerprint'],
                'exception' => $row['exception'],
                'message' => $row['message'],
                'file' => $row['file'],
                'line' => (int) $row['line'],
                'count' => (int) $row['count'],
                'last_seen' => $row['last_seen'],
            ];
        }

        return $topErrors;
    }

    public function findByFingerprint(string $fingerprint, int $limit = 100): array
    {
        $sql = '
            SELECT * FROM error_reports 
            WHERE fingerprint = ? 
            ORDER BY timestamp DESC 
            LIMIT ?
        ';

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

        return array_map([$this, 'hydrate'], $results);
    }

    public function deleteOlderThan(DateTimeImmutable $before): int
    {
        $sql = 'DELETE FROM error_reports WHERE timestamp < ?';

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

    public function deleteByCriteria(ErrorReportCriteria $criteria): int
    {
        [$sql, $params] = $this->buildCriteriaQuery($criteria, false, true);

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

    public function getHealthInfo(): array
    {
        // Total records
        $totalSql = 'SELECT COUNT(*) as total FROM error_reports';
        $totalResult = $this->connection->query($totalSql);
        $totalRecords = (int) ($totalResult[0]['total'] ?? 0);

        // Recent activity (last 24 hours)
        $recentSql = 'SELECT COUNT(*) as recent FROM error_reports WHERE timestamp > DATE_SUB(NOW(), INTERVAL 24 HOUR)';
        $recentResult = $this->connection->query($recentSql);
        $recentRecords = (int) ($recentResult[0]['recent'] ?? 0);

        // Table size
        $sizeSql = "
            SELECT 
                ROUND(((data_length + index_length) / 1024 / 1024), 2) as size_mb
            FROM information_schema.tables 
            WHERE table_schema = DATABASE() AND table_name = 'error_reports'
        ";
        $sizeResult = $this->connection->query($sizeSql);
        $sizeMb = (float) ($sizeResult[0]['size_mb'] ?? 0);

        // Oldest record
        $oldestSql = 'SELECT MIN(timestamp) as oldest FROM error_reports';
        $oldestResult = $this->connection->query($oldestSql);
        $oldest = $oldestResult[0]['oldest'] ?? null;

        return [
            'total_records' => $totalRecords,
            'recent_records_24h' => $recentRecords,
            'storage_size_mb' => $sizeMb,
            'oldest_record' => $oldest,
            'storage_type' => 'database',
        ];
    }

    private function buildCriteriaQuery(ErrorReportCriteria $criteria, bool $isCount = false, bool $isDelete = false): array
    {
        $select = $isCount ? 'SELECT COUNT(*) as count' : 'SELECT *';
        $operation = $isDelete ? 'DELETE' : $select;

        $sql = "{$operation} FROM error_reports WHERE 1=1";
        $params = [];

        if ($criteria->from) {
            $sql .= ' AND timestamp >= ?';
            $params[] = $criteria->from->format('Y-m-d H:i:s');
        }

        if ($criteria->to) {
            $sql .= ' AND timestamp <= ?';
            $params[] = $criteria->to->format('Y-m-d H:i:s');
        }

        if ($criteria->levels) {
            $placeholders = str_repeat('?,', count($criteria->levels) - 1) . '?';
            $sql .= " AND level IN ({$placeholders})";
            $params = array_merge($params, $criteria->levels);
        }

        if ($criteria->exceptions) {
            $placeholders = str_repeat('?,', count($criteria->exceptions) - 1) . '?';
            $sql .= " AND exception IN ({$placeholders})";
            $params = array_merge($params, $criteria->exceptions);
        }

        if ($criteria->routes) {
            $placeholders = str_repeat('?,', count($criteria->routes) - 1) . '?';
            $sql .= " AND route IN ({$placeholders})";
            $params = array_merge($params, $criteria->routes);
        }

        if ($criteria->methods) {
            $placeholders = str_repeat('?,', count($criteria->methods) - 1) . '?';
            $sql .= " AND method IN ({$placeholders})";
            $params = array_merge($params, $criteria->methods);
        }

        if ($criteria->userId) {
            $sql .= ' AND user_id = ?';
            $params[] = $criteria->userId;
        }

        if ($criteria->environment) {
            $sql .= ' AND environment = ?';
            $params[] = $criteria->environment;
        }

        if ($criteria->fingerprint) {
            $sql .= ' AND fingerprint = ?';
            $params[] = $criteria->fingerprint;
        }

        if ($criteria->minSeverity !== null) {
            $sql .= ' AND severity_level >= ?';
            $params[] = $criteria->minSeverity;
        }

        if ($criteria->maxSeverity !== null) {
            $sql .= ' AND severity_level <= ?';
            $params[] = $criteria->maxSeverity;
        }

        if ($criteria->search) {
            $sql .= ' AND (message LIKE ? OR exception LIKE ? OR file LIKE ?)';
            $searchTerm = '%' . $criteria->search . '%';
            $params[] = $searchTerm;
            $params[] = $searchTerm;
            $params[] = $searchTerm;
        }

        if (! $isCount && ! $isDelete) {
            $sql .= " ORDER BY {$criteria->orderBy} {$criteria->orderDir}";
            $sql .= " LIMIT {$criteria->limit} OFFSET {$criteria->offset}";
        }

        return [$sql, $params];
    }

    private function hydrate(array $row): ErrorReport
    {
        return new ErrorReport(
            id: $row['id'],
            timestamp: new DateTimeImmutable($row['timestamp']),
            level: $row['level'],
            message: $row['message'],
            exception: $row['exception'],
            file: $row['file'],
            line: (int) $row['line'],
            trace: $row['trace'],
            context: json_decode($row['context'] ?? '[]', true),
            userId: $row['user_id'],
            sessionId: $row['session_id'],
            requestId: $row['request_id'],
            userAgent: $row['user_agent'],
            ipAddress: $row['ip_address'],
            route: $row['route'],
            method: $row['method'],
            requestData: $row['request_data'] ? json_decode($row['request_data'], true) : null,
            executionTime: $row['execution_time'] ? Duration::fromMilliseconds((float) $row['execution_time']) : null,
            memoryUsage: $row['memory_usage'] ? (int) $row['memory_usage'] : null,
            tags: json_decode($row['tags'] ?? '[]', true),
            breadcrumbs: json_decode($row['breadcrumbs'] ?? '[]', true),
            release: $row['release'],
            environment: $row['environment'],
            serverInfo: json_decode($row['server_info'] ?? '[]', true),
            customData: json_decode($row['custom_data'] ?? '[]', true)
        );
    }
}
