namespace GeoSus.Server;

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;

// Write-Ahead Log a Snapshoty pro persistenci
public class Persistence
{
    private readonly ServerConfig _config;
    private readonly ILogger<Persistence> _logger;
    private readonly object _walLock = new();
    
    public Persistence(ServerConfig config, ILogger<Persistence> logger)
    {
        _config = config;
        _logger = logger;
        
        // Vytvoříme adresář pro data
        Directory.CreateDirectory(_config.DataPath);
    }
    
    #region WAL
    
    public void AppendToWal(string lobbyId, GameEvent evt)
    {
        var lobbyDir = GetLobbyDir(lobbyId);
        Directory.CreateDirectory(lobbyDir);
        
        var walPath = GetCurrentWalPath(lobbyDir);
        var json = JsonSerializer.Serialize(evt, JsonOptions.Default);
        
        lock (_walLock)
        {
            // Kontrola velikosti WAL
            if (File.Exists(walPath))
            {
                var fileInfo = new FileInfo(walPath);
                if (fileInfo.Length > _config.WalMaxSizeMb * 1024 * 1024)
                {
                    // Rotace - nový soubor
                    var newWalPath = Path.Combine(lobbyDir, $"wal_{DateTime.UtcNow:yyyyMMddHHmmss}.ndjson");
                    walPath = newWalPath;
                }
            }
            
            using var fs = new FileStream(walPath, FileMode.Append, FileAccess.Write, FileShare.Read);
            using var writer = new StreamWriter(fs, Encoding.UTF8);
            writer.WriteLine(json);
            writer.Flush();
            fs.Flush(true); // fsync
        }
    }
    
    public List<GameEvent> ReadWal(string lobbyId, long fromEventId = 0)
    {
        var lobbyDir = GetLobbyDir(lobbyId);
        var events = new List<GameEvent>();
        
        if (!Directory.Exists(lobbyDir))
            return events;
            
        // Načteme všechny WAL soubory seřazené podle času
        var walFiles = Directory.GetFiles(lobbyDir, "wal_*.ndjson")
            .OrderBy(f => f)
            .ToList();
            
        foreach (var walPath in walFiles)
        {
            try
            {
                var lines = File.ReadAllLines(walPath, Encoding.UTF8);
                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line)) continue;
                    
                    var evt = JsonSerializer.Deserialize<GameEvent>(line, JsonOptions.Default);
                    if (evt != null && evt.EventId > fromEventId)
                    {
                        events.Add(evt);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Chyba při čtení WAL {Path}", walPath);
            }
        }
        
        return events.OrderBy(e => e.EventId).ToList();
    }
    
    private string GetCurrentWalPath(string lobbyDir)
    {
        // Najdeme nejnovější WAL nebo vytvoříme nový
        var walFiles = Directory.GetFiles(lobbyDir, "wal_*.ndjson");
        if (walFiles.Length == 0)
        {
            return Path.Combine(lobbyDir, $"wal_{DateTime.UtcNow:yyyyMMddHHmmss}.ndjson");
        }
        return walFiles.OrderByDescending(f => f).First();
    }
    
    #endregion
    
    #region Snapshots
    
    public void SaveSnapshot(string lobbyId, LobbySnapshot snapshot)
    {
        var lobbyDir = GetLobbyDir(lobbyId);
        Directory.CreateDirectory(lobbyDir);
        
        // Serializujeme bez checksumu
        snapshot.Timestamp = DateTime.UtcNow;
        var json = JsonSerializer.Serialize(snapshot, JsonOptions.Indented);
        
        // Spočítáme checksum
        var checksum = ComputeChecksum(json);
        snapshot.Checksum = checksum;
        
        // Serializujeme znovu s checksumem
        json = JsonSerializer.Serialize(snapshot, JsonOptions.Indented);
        
        var snapshotPath = Path.Combine(lobbyDir, $"snapshot_{snapshot.LastEventId}.json");
        var tempPath = snapshotPath + ".tmp";
        
        // Atomic write - temp + rename
        File.WriteAllText(tempPath, json, Encoding.UTF8);
        File.Move(tempPath, snapshotPath, overwrite: true);
        
        _logger.LogInformation("Snapshot uložen: {Path}, eventId: {EventId}", snapshotPath, snapshot.LastEventId);
        
        // Čištění starých snapshotů
        CleanupOldSnapshots(lobbyDir);
    }
    
    public LobbySnapshot? LoadLatestSnapshot(string lobbyId)
    {
        var lobbyDir = GetLobbyDir(lobbyId);
        
        if (!Directory.Exists(lobbyDir))
            return null;
            
        var snapshotFiles = Directory.GetFiles(lobbyDir, "snapshot_*.json")
            .OrderByDescending(f => f)
            .ToList();
            
        foreach (var snapshotPath in snapshotFiles)
        {
            try
            {
                var json = File.ReadAllText(snapshotPath, Encoding.UTF8);
                var snapshot = JsonSerializer.Deserialize<LobbySnapshot>(json, JsonOptions.Default);
                
                if (snapshot == null) continue;
                
                // Validace checksumu
                var storedChecksum = snapshot.Checksum;
                snapshot.Checksum = "";
                var jsonWithoutChecksum = JsonSerializer.Serialize(snapshot, JsonOptions.Indented);
                var computedChecksum = ComputeChecksum(jsonWithoutChecksum);
                
                if (storedChecksum != computedChecksum)
                {
                    _logger.LogWarning("Checksum mismatch pro snapshot {Path}", snapshotPath);
                    continue;
                }
                
                snapshot.Checksum = storedChecksum;
                return snapshot;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Chyba při čtení snapshot {Path}", snapshotPath);
            }
        }
        
        return null;
    }
    
    private void CleanupOldSnapshots(string lobbyDir, int keepCount = 5)
    {
        var snapshotFiles = Directory.GetFiles(lobbyDir, "snapshot_*.json")
            .OrderByDescending(f => f)
            .Skip(keepCount)
            .ToList();
            
        foreach (var oldSnapshot in snapshotFiles)
        {
            try
            {
                File.Delete(oldSnapshot);
                _logger.LogDebug("Smazán starý snapshot: {Path}", oldSnapshot);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Nelze smazat snapshot {Path}", oldSnapshot);
            }
        }
    }
    
    private static string ComputeChecksum(string content)
    {
        var bytes = Encoding.UTF8.GetBytes(content);
        var hash = SHA256.HashData(bytes);
        return Convert.ToHexString(hash).ToLowerInvariant();
    }
    
    #endregion
    
    #region Archive
    
    public void ArchiveLobby(string lobbyId)
    {
        var lobbyDir = GetLobbyDir(lobbyId);
        if (!Directory.Exists(lobbyDir))
            return;
            
        var archiveDir = Path.Combine(_config.DataPath, "archive");
        Directory.CreateDirectory(archiveDir);
        
        var archivePath = Path.Combine(archiveDir, $"{lobbyId}_{DateTime.UtcNow:yyyyMMddHHmmss}");
        Directory.Move(lobbyDir, archivePath);
        
        _logger.LogInformation("Lobby archivováno: {LobbyId} -> {ArchivePath}", lobbyId, archivePath);
    }
    
    public void DeleteLobbyData(string lobbyId)
    {
        var lobbyDir = GetLobbyDir(lobbyId);
        if (Directory.Exists(lobbyDir))
        {
            Directory.Delete(lobbyDir, recursive: true);
            _logger.LogInformation("Data lobby smazána: {LobbyId}", lobbyId);
        }
    }
    
    #endregion
    
    private string GetLobbyDir(string lobbyId)
    {
        return Path.Combine(_config.DataPath, "lobbies", lobbyId);
    }
}
