namespace GeoSus.Server;

using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;

// SQLite databáze pro statistiky hráčů
public class StatsDb : IDisposable
{
    private readonly SqliteConnection _connection;
    private readonly ILogger<StatsDb> _logger;
    private readonly object _lock = new();
    
    public StatsDb(string dbPath, ILogger<StatsDb> logger)
    {
        _logger = logger;
        
        var directory = Path.GetDirectoryName(dbPath);
        if (!string.IsNullOrEmpty(directory))
            Directory.CreateDirectory(directory);
            
        _connection = new SqliteConnection($"Data Source={dbPath}");
        _connection.Open();
        
        InitializeSchema();
    }
    
    private void InitializeSchema()
    {
        var sql = @"
            CREATE TABLE IF NOT EXISTS player_stats (
                client_uuid TEXT PRIMARY KEY,
                display_name TEXT,
                total_games INTEGER DEFAULT 0,
                games_as_crew INTEGER DEFAULT 0,
                games_as_impostor INTEGER DEFAULT 0,
                crew_wins INTEGER DEFAULT 0,
                impostor_wins INTEGER DEFAULT 0,
                total_kills INTEGER DEFAULT 0,
                total_deaths INTEGER DEFAULT 0,
                tasks_completed INTEGER DEFAULT 0,
                bodies_reported INTEGER DEFAULT 0,
                emergency_meetings_called INTEGER DEFAULT 0,
                times_voted_out INTEGER DEFAULT 0,
                successful_votes INTEGER DEFAULT 0,
                total_playtime_seconds INTEGER DEFAULT 0,
                cheat_incidents INTEGER DEFAULT 0,
                last_seen_utc TEXT,
                created_at_utc TEXT
            );
            
            CREATE INDEX IF NOT EXISTS idx_crew_wins ON player_stats(crew_wins DESC);
            CREATE INDEX IF NOT EXISTS idx_impostor_wins ON player_stats(impostor_wins DESC);
            CREATE INDEX IF NOT EXISTS idx_total_games ON player_stats(total_games DESC);
        ";
        
        using var cmd = _connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.ExecuteNonQuery();
        
        _logger.LogInformation("StatsDb inicializována");
    }
    
    #region CRUD operace
    
    public void EnsurePlayerExists(string clientUuid, string displayName)
    {
        lock (_lock)
        {
            var sql = @"
                INSERT INTO player_stats (client_uuid, display_name, created_at_utc, last_seen_utc)
                VALUES (@uuid, @name, @now, @now)
                ON CONFLICT(client_uuid) DO UPDATE SET 
                    display_name = @name,
                    last_seen_utc = @now
            ";
            
            using var cmd = _connection.CreateCommand();
            cmd.CommandText = sql;
            cmd.Parameters.AddWithValue("@uuid", clientUuid);
            cmd.Parameters.AddWithValue("@name", displayName);
            cmd.Parameters.AddWithValue("@now", DateTime.UtcNow.ToString("o"));
            cmd.ExecuteNonQuery();
        }
    }
    
    public PlayerStats? GetPlayerStats(string clientUuid)
    {
        lock (_lock)
        {
            var sql = "SELECT * FROM player_stats WHERE client_uuid = @uuid";
            
            using var cmd = _connection.CreateCommand();
            cmd.CommandText = sql;
            cmd.Parameters.AddWithValue("@uuid", clientUuid);
            
            using var reader = cmd.ExecuteReader();
            if (reader.Read())
            {
                return ReadPlayerStats(reader);
            }
            
            return null;
        }
    }
    
    public List<PlayerStats> GetLeaderboard(string sortBy, int limit = 100)
    {
        lock (_lock)
        {
            var validColumns = new[] { 
                "crew_wins", "impostor_wins", "total_games", 
                "total_kills", "tasks_completed" 
            };
            
            if (!validColumns.Contains(sortBy))
                sortBy = "total_games";
                
            var sql = $"SELECT * FROM player_stats ORDER BY {sortBy} DESC LIMIT @limit";
            
            using var cmd = _connection.CreateCommand();
            cmd.CommandText = sql;
            cmd.Parameters.AddWithValue("@limit", limit);
            
            var results = new List<PlayerStats>();
            using var reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                results.Add(ReadPlayerStats(reader));
            }
            
            return results;
        }
    }
    
    #endregion
    
    #region Inkrementální aktualizace
    
    public void IncrementKills(string clientUuid)
    {
        IncrementColumn(clientUuid, "total_kills");
    }
    
    public void IncrementDeaths(string clientUuid)
    {
        IncrementColumn(clientUuid, "total_deaths");
    }
    
    public void IncrementTasksCompleted(string clientUuid)
    {
        IncrementColumn(clientUuid, "tasks_completed");
    }
    
    public void IncrementBodiesReported(string clientUuid)
    {
        IncrementColumn(clientUuid, "bodies_reported");
    }
    
    public void IncrementEmergencyMeetings(string clientUuid)
    {
        IncrementColumn(clientUuid, "emergency_meetings_called");
    }
    
    public void IncrementTimesVotedOut(string clientUuid)
    {
        IncrementColumn(clientUuid, "times_voted_out");
    }
    
    public void IncrementSuccessfulVotes(string clientUuid)
    {
        IncrementColumn(clientUuid, "successful_votes");
    }
    
    public void IncrementCheatIncidents(string clientUuid)
    {
        IncrementColumn(clientUuid, "cheat_incidents");
    }
    
    private void IncrementColumn(string clientUuid, string column)
    {
        lock (_lock)
        {
            var sql = $@"
                UPDATE player_stats 
                SET {column} = {column} + 1, last_seen_utc = @now
                WHERE client_uuid = @uuid
            ";
            
            using var cmd = _connection.CreateCommand();
            cmd.CommandText = sql;
            cmd.Parameters.AddWithValue("@uuid", clientUuid);
            cmd.Parameters.AddWithValue("@now", DateTime.UtcNow.ToString("o"));
            cmd.ExecuteNonQuery();
        }
    }
    
    #endregion
    
    #region Game end aktualizace
    
    public void RecordGameEnd(GameEndStats stats)
    {
        lock (_lock)
        {
            using var transaction = _connection.BeginTransaction();
            
            try
            {
                foreach (var player in stats.Players)
                {
                    var sql = @"
                        UPDATE player_stats SET
                            total_games = total_games + 1,
                            games_as_crew = games_as_crew + @asCrew,
                            games_as_impostor = games_as_impostor + @asImpostor,
                            crew_wins = crew_wins + @crewWin,
                            impostor_wins = impostor_wins + @impostorWin,
                            total_playtime_seconds = total_playtime_seconds + @playtime,
                            last_seen_utc = @now
                        WHERE client_uuid = @uuid
                    ";
                    
                    using var cmd = _connection.CreateCommand();
                    cmd.CommandText = sql;
                    cmd.Parameters.AddWithValue("@uuid", player.ClientUuid);
                    cmd.Parameters.AddWithValue("@asCrew", player.WasCrew ? 1 : 0);
                    cmd.Parameters.AddWithValue("@asImpostor", player.WasImpostor ? 1 : 0);
                    cmd.Parameters.AddWithValue("@crewWin", player.WasCrew && stats.CrewWon ? 1 : 0);
                    cmd.Parameters.AddWithValue("@impostorWin", player.WasImpostor && !stats.CrewWon ? 1 : 0);
                    cmd.Parameters.AddWithValue("@playtime", player.PlaytimeSeconds);
                    cmd.Parameters.AddWithValue("@now", DateTime.UtcNow.ToString("o"));
                    cmd.ExecuteNonQuery();
                }
                
                transaction.Commit();
                _logger.LogInformation("Game stats uloženy pro {Count} hráčů", stats.Players.Count);
            }
            catch
            {
                transaction.Rollback();
                throw;
            }
        }
    }
    
    #endregion
    
    private PlayerStats ReadPlayerStats(SqliteDataReader reader)
    {
        return new PlayerStats
        {
            ClientUuid = reader.GetString(reader.GetOrdinal("client_uuid")),
            DisplayName = reader.IsDBNull(reader.GetOrdinal("display_name")) 
                ? null : reader.GetString(reader.GetOrdinal("display_name")),
            TotalGames = reader.GetInt32(reader.GetOrdinal("total_games")),
            GamesAsCrew = reader.GetInt32(reader.GetOrdinal("games_as_crew")),
            GamesAsImpostor = reader.GetInt32(reader.GetOrdinal("games_as_impostor")),
            CrewWins = reader.GetInt32(reader.GetOrdinal("crew_wins")),
            ImpostorWins = reader.GetInt32(reader.GetOrdinal("impostor_wins")),
            TotalKills = reader.GetInt32(reader.GetOrdinal("total_kills")),
            TotalDeaths = reader.GetInt32(reader.GetOrdinal("total_deaths")),
            TasksCompleted = reader.GetInt32(reader.GetOrdinal("tasks_completed")),
            BodiesReported = reader.GetInt32(reader.GetOrdinal("bodies_reported")),
            EmergencyMeetingsCalled = reader.GetInt32(reader.GetOrdinal("emergency_meetings_called")),
            TimesVotedOut = reader.GetInt32(reader.GetOrdinal("times_voted_out")),
            SuccessfulVotes = reader.GetInt32(reader.GetOrdinal("successful_votes")),
            TotalPlaytimeSeconds = reader.GetInt32(reader.GetOrdinal("total_playtime_seconds")),
            CheatIncidents = reader.GetInt32(reader.GetOrdinal("cheat_incidents")),
            LastSeenUtc = reader.IsDBNull(reader.GetOrdinal("last_seen_utc")) 
                ? null : reader.GetString(reader.GetOrdinal("last_seen_utc")),
            CreatedAtUtc = reader.IsDBNull(reader.GetOrdinal("created_at_utc")) 
                ? null : reader.GetString(reader.GetOrdinal("created_at_utc"))
        };
    }
    
    public void Dispose()
    {
        _connection.Close();
        _connection.Dispose();
    }
}

#region DTO

public class PlayerStats
{
    public required string ClientUuid { get; set; }
    public string? DisplayName { get; set; }
    public int TotalGames { get; set; }
    public int GamesAsCrew { get; set; }
    public int GamesAsImpostor { get; set; }
    public int CrewWins { get; set; }
    public int ImpostorWins { get; set; }
    public int TotalKills { get; set; }
    public int TotalDeaths { get; set; }
    public int TasksCompleted { get; set; }
    public int BodiesReported { get; set; }
    public int EmergencyMeetingsCalled { get; set; }
    public int TimesVotedOut { get; set; }
    public int SuccessfulVotes { get; set; }
    public int TotalPlaytimeSeconds { get; set; }
    public int CheatIncidents { get; set; }
    public string? LastSeenUtc { get; set; }
    public string? CreatedAtUtc { get; set; }
    
    // Computed properties pro API
    public double CrewWinRate => GamesAsCrew > 0 ? (double)CrewWins / GamesAsCrew : 0;
    public double ImpostorWinRate => GamesAsImpostor > 0 ? (double)ImpostorWins / GamesAsImpostor : 0;
    public double KillDeathRatio => TotalDeaths > 0 ? (double)TotalKills / TotalDeaths : TotalKills;
    public double AverageTasksPerGame => TotalGames > 0 ? (double)TasksCompleted / TotalGames : 0;
}

public class GameEndStats
{
    public bool CrewWon { get; set; }
    public List<PlayerGameStats> Players { get; set; } = new();
}

public class PlayerGameStats
{
    public required string ClientUuid { get; set; }
    public bool WasCrew { get; set; }
    public bool WasImpostor { get; set; }
    public int PlaytimeSeconds { get; set; }
}

#endregion
