<?php

declare(strict_types=1);

namespace App\Framework\ErrorAggregation\Storage;

use App\Framework\Database\ConnectionInterface;
use App\Framework\ErrorAggregation\ErrorEvent;
use App\Framework\ErrorAggregation\ErrorPattern;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorCode;
use App\Framework\Ulid\Ulid;

/**
 * Database-based error storage implementation
 */
final readonly class DatabaseErrorStorage implements ErrorStorageInterface
{
    public function __construct(
        private ConnectionInterface $connection
    ) {
    }

    public function storeEvent(ErrorEvent $event): void
    {
        $sql = "
            INSERT INTO error_events (
                id, service, component, operation, error_code, error_message, 
                severity, occurred_at, context, metadata, request_id, user_id, 
                client_ip, is_security_event, stack_trace, user_agent
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ";

        $this->connection->execute($sql, [
            $event->id->toString(),
            $event->service,
            $event->component,
            $event->operation,
            $event->errorCode->value,
            $event->errorMessage,
            $event->severity->value,
            $event->occurredAt->format('Y-m-d H:i:s'),
            json_encode($event->context),
            json_encode($event->metadata),
            $event->requestId,
            $event->userId,
            $event->clientIp,
            $event->isSecurityEvent ? 1 : 0,
            $event->stackTrace,
            $event->userAgent,
        ]);
    }

    public function storeEventsBatch(array $events): void
    {
        if (empty($events)) {
            return;
        }

        $sql = "
            INSERT INTO error_events (
                id, service, component, operation, error_code, error_message, 
                severity, occurred_at, context, metadata, request_id, user_id, 
                client_ip, is_security_event, stack_trace, user_agent
            ) VALUES " . str_repeat('(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),', count($events));

        $sql = rtrim($sql, ',');

        $params = [];
        foreach ($events as $event) {
            $params = array_merge($params, [
                $event->id->toString(),
                $event->service,
                $event->component,
                $event->operation,
                $event->errorCode->value,
                $event->errorMessage,
                $event->severity->value,
                $event->occurredAt->format('Y-m-d H:i:s'),
                json_encode($event->context),
                json_encode($event->metadata),
                $event->requestId,
                $event->userId,
                $event->clientIp,
                $event->isSecurityEvent ? 1 : 0,
                $event->stackTrace,
                $event->userAgent,
            ]);
        }

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

    public function storePattern(ErrorPattern $pattern): void
    {
        $sql = "
            INSERT INTO error_patterns (
                id, fingerprint, service, component, operation, error_code, 
                normalized_message, severity, occurrence_count, first_occurrence, 
                last_occurrence, affected_users, affected_ips, is_active, 
                is_acknowledged, acknowledged_by, acknowledged_at, resolution, metadata
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
                occurrence_count = VALUES(occurrence_count),
                last_occurrence = VALUES(last_occurrence),
                affected_users = VALUES(affected_users),
                affected_ips = VALUES(affected_ips),
                is_active = VALUES(is_active),
                is_acknowledged = VALUES(is_acknowledged),
                acknowledged_by = VALUES(acknowledged_by),
                acknowledged_at = VALUES(acknowledged_at),
                resolution = VALUES(resolution),
                metadata = VALUES(metadata)
        ";

        $this->connection->execute($sql, [
            $pattern->id->toString(),
            $pattern->fingerprint,
            $pattern->service,
            $pattern->component,
            $pattern->operation,
            $pattern->errorCode,
            $pattern->normalizedMessage,
            $pattern->severity->value,
            $pattern->occurrenceCount,
            $pattern->firstOccurrence->format('Y-m-d H:i:s'),
            $pattern->lastOccurrence->format('Y-m-d H:i:s'),
            json_encode($pattern->affectedUsers),
            json_encode($pattern->affectedIps),
            $pattern->isActive ? 1 : 0,
            $pattern->isAcknowledged ? 1 : 0,
            $pattern->acknowledgedBy,
            $pattern->acknowledgedAt?->format('Y-m-d H:i:s'),
            $pattern->resolution,
            json_encode($pattern->metadata),
        ]);
    }

    public function getPatternById(string $patternId): ?ErrorPattern
    {
        $sql = "SELECT * FROM error_patterns WHERE id = ?";
        $result = $this->connection->query($sql, [$patternId]);

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

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

    public function getPatternByFingerprint(string $fingerprint): ?ErrorPattern
    {
        $sql = "SELECT * FROM error_patterns WHERE fingerprint = ?";
        $result = $this->connection->query($sql, [$fingerprint]);

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

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

    public function getActivePatterns(int $limit = 50, int $offset = 0): array
    {
        $sql = "
            SELECT * FROM error_patterns 
            WHERE is_active = 1 
            ORDER BY last_occurrence DESC, occurrence_count DESC
            LIMIT ? OFFSET ?
        ";

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

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

    public function getPatternsByService(string $service, int $limit = 50): array
    {
        $sql = "
            SELECT * FROM error_patterns 
            WHERE service = ? AND is_active = 1
            ORDER BY last_occurrence DESC, occurrence_count DESC
            LIMIT ?
        ";

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

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

    public function getRecentEvents(int $limit = 100, ?ErrorSeverity $severity = null): array
    {
        $sql = "SELECT * FROM error_events";
        $params = [];

        if ($severity) {
            $sql .= " WHERE severity = ?";
            $params[] = $severity->value;
        }

        $sql .= " ORDER BY occurred_at DESC LIMIT ?";
        $params[] = $limit;

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

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

    public function getStatistics(\DateTimeImmutable $from, \DateTimeImmutable $to): array
    {
        $sql = "
            SELECT 
                COUNT(*) as total_events,
                COUNT(DISTINCT service) as services_affected,
                COUNT(DISTINCT user_id) as users_affected,
                COUNT(DISTINCT client_ip) as ips_affected,
                severity,
                COUNT(*) as severity_count
            FROM error_events 
            WHERE occurred_at BETWEEN ? AND ?
            GROUP BY severity
        ";

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

        $stats = [
            'total_events' => 0,
            'services_affected' => 0,
            'users_affected' => 0,
            'ips_affected' => 0,
            'by_severity' => [],
        ];

        foreach ($results as $row) {
            $stats['total_events'] += $row['severity_count'];
            $stats['services_affected'] = max($stats['services_affected'], $row['services_affected']);
            $stats['users_affected'] = max($stats['users_affected'], $row['users_affected']);
            $stats['ips_affected'] = max($stats['ips_affected'], $row['ips_affected']);
            $stats['by_severity'][$row['severity']] = $row['severity_count'];
        }

        return $stats;
    }

    public function getErrorTrends(
        \DateTimeImmutable $from,
        \DateTimeImmutable $to,
        string $groupBy = 'hour'
    ): array {
        $dateFormat = match ($groupBy) {
            '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(occurred_at, ?) as time_bucket,
                severity,
                COUNT(*) as count
            FROM error_events 
            WHERE occurred_at BETWEEN ? AND ?
            GROUP BY time_bucket, severity
            ORDER BY time_bucket ASC
        ";

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

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

        return $trends;
    }

    public function getTopPatterns(int $limit = 10, ?string $service = null): array
    {
        $sql = "
            SELECT * FROM error_patterns 
            WHERE is_active = 1
        ";
        $params = [];

        if ($service) {
            $sql .= " AND service = ?";
            $params[] = $service;
        }

        $sql .= " ORDER BY occurrence_count DESC LIMIT ?";
        $params[] = $limit;

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

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

    public function deleteOldEvents(\DateTimeImmutable $cutoffDate, ErrorSeverity $severity): int
    {
        $sql = "DELETE FROM error_events WHERE occurred_at < ? AND severity = ?";

        $affectedRows = $this->connection->execute($sql, [
            $cutoffDate->format('Y-m-d H:i:s'),
            $severity->value,
        ]);

        return $affectedRows;
    }

    public function deleteOldPatterns(\DateTimeImmutable $cutoffDate): int
    {
        $sql = "DELETE FROM error_patterns WHERE is_active = 0 AND last_occurrence < ?";

        $affectedRows = $this->connection->execute($sql, [
            $cutoffDate->format('Y-m-d H:i:s'),
        ]);

        return $affectedRows;
    }

    public function exportEvents(
        \DateTimeImmutable $from,
        \DateTimeImmutable $to,
        array $filters = []
    ): \Generator {
        $sql = "SELECT * FROM error_events WHERE occurred_at BETWEEN ? AND ?";
        $params = [$from->format('Y-m-d H:i:s'), $to->format('Y-m-d H:i:s')];

        // Add filters
        if (isset($filters['service'])) {
            $sql .= " AND service = ?";
            $params[] = $filters['service'];
        }

        if (isset($filters['severity'])) {
            $sql .= " AND severity = ?";
            $params[] = $filters['severity'];
        }

        if (isset($filters['security_events_only']) && $filters['security_events_only']) {
            $sql .= " AND is_security_event = 1";
        }

        $sql .= " ORDER BY occurred_at ASC";

        // Use cursor-based pagination for large datasets
        $batchSize = 1000;
        $offset = 0;

        do {
            $batchSql = $sql . " LIMIT ? OFFSET ?";
            $batchParams = array_merge($params, [$batchSize, $offset]);

            $results = $this->connection->query($batchSql, $batchParams);

            foreach ($results as $row) {
                yield $this->hydrateEvent($row);
            }

            $offset += $batchSize;
        } while (count($results) === $batchSize);
    }

    public function getHealthStatus(): array
    {
        try {
            // Test connection
            $this->connection->query('SELECT 1');

            // Get table stats
            $eventCount = $this->connection->query('SELECT COUNT(*) as count FROM error_events')[0]['count'] ?? 0;
            $patternCount = $this->connection->query('SELECT COUNT(*) as count FROM error_patterns')[0]['count'] ?? 0;

            return [
                'status' => 'healthy',
                'event_count' => (int) $eventCount,
                'pattern_count' => (int) $patternCount,
                'connection' => 'ok',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'unhealthy',
                'error' => $e->getMessage(),
            ];
        }
    }

    private function hydratePattern(array $row): ErrorPattern
    {
        return new ErrorPattern(
            id: Ulid::fromString($row['id']),
            fingerprint: $row['fingerprint'],
            service: $row['service'],
            component: $row['component'],
            operation: $row['operation'],
            errorCode: $row['error_code'],
            normalizedMessage: $row['normalized_message'],
            severity: ErrorSeverity::from($row['severity']),
            occurrenceCount: (int) $row['occurrence_count'],
            firstOccurrence: new \DateTimeImmutable($row['first_occurrence']),
            lastOccurrence: new \DateTimeImmutable($row['last_occurrence']),
            affectedUsers: json_decode($row['affected_users'], true) ?? [],
            affectedIps: json_decode($row['affected_ips'], true) ?? [],
            isActive: (bool) $row['is_active'],
            isAcknowledged: (bool) $row['is_acknowledged'],
            acknowledgedBy: $row['acknowledged_by'],
            acknowledgedAt: $row['acknowledged_at'] ? new \DateTimeImmutable($row['acknowledged_at']) : null,
            resolution: $row['resolution'],
            metadata: json_decode($row['metadata'], true) ?? [],
        );
    }

    private function hydrateEvent(array $row): ErrorEvent
    {
        return new ErrorEvent(
            id: Ulid::fromString($row['id']),
            service: $row['service'],
            component: $row['component'],
            operation: $row['operation'],
            errorCode: ErrorCode::from($row['error_code']),
            errorMessage: $row['error_message'],
            severity: ErrorSeverity::from($row['severity']),
            occurredAt: new \DateTimeImmutable($row['occurred_at']),
            context: json_decode($row['context'], true) ?? [],
            metadata: json_decode($row['metadata'], true) ?? [],
            requestId: $row['request_id'],
            userId: $row['user_id'],
            clientIp: $row['client_ip'],
            isSecurityEvent: (bool) $row['is_security_event'],
            stackTrace: $row['stack_trace'],
            userAgent: $row['user_agent'],
        );
    }
}
