namespace GeoSus.Server;

using Microsoft.Extensions.Logging;
using System.Threading.Channels;
using System.Security.Cryptography;

// Herní logika - kill, meeting, voting, tasks
public class GameLogic
{
    private readonly ServerConfig _config;
    private readonly ILogger _logger;
    private readonly OverpassService? _overpassService;
    private readonly Random _random = new Random(); // Sdílená instance pro lepší náhodnost
    
    public GameLogic(ServerConfig config, ILogger logger, OverpassService? overpassService = null)
    {
        _config = config;
        _logger = logger;
        _overpassService = overpassService;
    }
    
    #region Zabíjení
    
    public (bool Success, string? Error, Body? Body) TryKill(
        Player killer, Player victim, Dictionary<string, Player> players, List<Body> bodies)
    {
        // Validace - killer musí být impostor a alive
        if (killer.Role != PlayerRole.Impostor)
            return (false, "Nejsi impostor", null);
            
        if (killer.State != PlayerState.Alive)
            return (false, "Jsi mrtvý", null);
            
        if (victim.State != PlayerState.Alive)
            return (false, "Oběť je již mrtvá", null);
            
        // Cooldown kontrola
        if (killer.LastKillTime.HasValue)
        {
            var elapsed = (DateTime.UtcNow - killer.LastKillTime.Value).TotalMilliseconds;
            if (elapsed < _config.KillCooldownMs)
            {
                var remaining = (_config.KillCooldownMs - elapsed) / 1000;
                return (false, $"Cooldown: {remaining:F1}s", null);
            }
        }
        
        // Vzdálenost
        var distance = killer.Position.DistanceTo(victim.Position);
        if (distance > _config.KillDistanceM)
            return (false, $"Příliš daleko ({distance:F1}m)", null);
            
        // Kill úspěšný
        victim.State = PlayerState.Dead;
        killer.LastKillTime = DateTime.UtcNow;
        
        var body = new Body
        {
            BodyId = Guid.NewGuid().ToString("N")[..8],
            VictimId = victim.ClientUuid,
            KillerId = killer.ClientUuid,
            Location = victim.Position,
            KilledAt = DateTime.UtcNow
        };
        
        bodies.Add(body);
        
        _logger.LogInformation("Kill: {Killer} zabil {Victim}, body {BodyId}", 
            killer.ClientUuid, victim.ClientUuid, body.BodyId);
        
        return (true, null, body);
    }
    
    #endregion
    
    #region Reporty a meetingy
    
    public (bool Success, string? Error, Body? Body) TryReportBody(
        Player reporter, string bodyId, List<Body> bodies)
    {
        if (reporter.State != PlayerState.Alive)
            return (false, "Jsi mrtvý", null);
            
        var body = bodies.FirstOrDefault(b => b.BodyId == bodyId);
        if (body == null)
            return (false, "Tělo neexistuje", null);
            
        if (body.Reported)
            return (false, "Tělo již bylo reportnuto", null);
            
        // Vzdálenost
        var distance = reporter.Position.DistanceTo(body.Location);
        if (distance > _config.ReportDistanceM)
            return (false, $"Příliš daleko ({distance:F1}m)", null);
            
        body.Reported = true;
        body.ReportedBy = reporter.ClientUuid;
        
        _logger.LogInformation("Report: {Reporter} reportnul tělo {BodyId}", 
            reporter.ClientUuid, bodyId);
        
        return (true, null, body);
    }
    
    public (bool Success, string? Error) TryCallEmergencyMeeting(
        Player caller, int maxMeetings, int cooldownMs)
    {
        if (caller.State != PlayerState.Alive)
            return (false, "Jsi mrtvý");
            
        // Limit na počet emergency meetings
        if (caller.EmergencyMeetingsUsed >= maxMeetings)
            return (false, $"Vyčerpal jsi emergency meetingy ({maxMeetings})");
            
        // Cooldown
        if (caller.LastEmergencyMeetingTime.HasValue)
        {
            var elapsed = (DateTime.UtcNow - caller.LastEmergencyMeetingTime.Value).TotalMilliseconds;
            if (elapsed < cooldownMs)
            {
                var remaining = (cooldownMs - elapsed) / 1000;
                return (false, $"Cooldown: {remaining:F1}s");
            }
        }
        
        caller.EmergencyMeetingsUsed++;
        caller.LastEmergencyMeetingTime = DateTime.UtcNow;
        
        _logger.LogInformation("Emergency meeting: {Caller}", caller.ClientUuid);
        
        return (true, null);
    }
    
    public Meeting StartMeeting(
        MeetingType type, string callerId, Position meetingLocation, 
        string? reportedBodyId, int arrivalBaseMs, int votingPhaseMs, int discussionPhaseMs)
    {
        var now = DateTime.UtcNow;
        
        return new Meeting
        {
            MeetingId = Guid.NewGuid().ToString("N")[..8],
            Type = type,
            CallerId = callerId,
            ReportedBodyId = reportedBodyId,
            MeetingLocation = meetingLocation,
            StartTime = now,
            ArrivalDeadline = now.AddMilliseconds(arrivalBaseMs + _config.ArrivalSafetyMarginMs),
            DiscussionEndTime = discussionPhaseMs > 0 ? now.AddMilliseconds(arrivalBaseMs + discussionPhaseMs) : null,
            VotingEndTime = now.AddMilliseconds(arrivalBaseMs + discussionPhaseMs + votingPhaseMs)
        };
    }
    
    public bool CheckPlayerArrival(Player player, Meeting meeting)
    {
        if (player.State != PlayerState.Alive)
            return false;
            
        var distance = player.Position.DistanceTo(meeting.MeetingLocation);
        var now = DateTime.UtcNow;
        
        // Grace period
        var deadline = meeting.ArrivalDeadline.AddMilliseconds(_config.AllowedLateMs);
        
        if (now <= deadline && distance <= _config.MeetingArrivalRadiusM)
        {
            meeting.ArrivedPlayers.Add(player.ClientUuid);
            return true;
        }
        
        return false;
    }
    
    #endregion
    
    #region Hlasování
    
    public (bool Success, string? Error) TryCastVote(
        Player voter, string? targetId, Meeting meeting, Dictionary<string, Player> players)
    {
        if (voter.State != PlayerState.Alive)
            return (false, "Jsi mrtvý");
            
        if (!meeting.ArrivedPlayers.Contains(voter.ClientUuid))
            return (false, "Nedorazil jsi včas na meeting");
            
        var now = DateTime.UtcNow;
        
        // Kontrola fáze - musíme být ve voting fázi
        if (meeting.DiscussionEndTime.HasValue && now < meeting.DiscussionEndTime.Value)
            return (false, "Ještě probíhá diskuze");
            
        if (now > meeting.VotingEndTime)
            return (false, "Hlasování skončilo");
            
        // Rate limit na změny hlasu
        if (meeting.Votes.ContainsKey(voter.ClientUuid) && meeting.LastVoteChangeTime.HasValue)
        {
            var sinceLastChange = (now - meeting.LastVoteChangeTime.Value).TotalMilliseconds;
            if (sinceLastChange < 2000)
                return (false, "Příliš rychlá změna hlasu");
        }
        
        // Validace targetu
        if (targetId != null)
        {
            if (!players.TryGetValue(targetId, out var target))
                return (false, "Neplatný cíl hlasu");
            if (target.State != PlayerState.Alive)
                return (false, "Cíl je mrtvý");
        }
        
        meeting.Votes[voter.ClientUuid] = targetId;
        meeting.LastVoteChangeTime = now;
        
        return (true, null);
    }
    
    public (string? EjectedId, bool WasTie, Dictionary<string, int> VoteCounts) ResolveVoting(
        Meeting meeting, Dictionary<string, Player> players, TiePolicy tiePolicy)
    {
        var voteCounts = new Dictionary<string, int>();
        var skipCount = 0;
        
        foreach (var (voterId, targetId) in meeting.Votes)
        {
            if (targetId == null)
            {
                skipCount++;
            }
            else
            {
                voteCounts.TryAdd(targetId, 0);
                voteCounts[targetId]++;
            }
        }
        
        voteCounts["__SKIP__"] = skipCount;
        
        // Najdeme maximum
        var maxVotes = voteCounts.Values.Max();
        var topVoted = voteCounts.Where(kv => kv.Value == maxVotes).Select(kv => kv.Key).ToList();
        
        // Kontrola remízy
        if (topVoted.Count > 1 || topVoted.Contains("__SKIP__"))
        {
            // Remíza nebo skip vyhrál
            switch (tiePolicy)
            {
                case TiePolicy.NoEject:
                    return (null, topVoted.Count > 1, voteCounts);
                    
                case TiePolicy.Random:
                    if (topVoted.Contains("__SKIP__"))
                        topVoted.Remove("__SKIP__");
                    if (topVoted.Count > 0)
                    {
                        var random = new Random();
                        var ejected = topVoted[random.Next(topVoted.Count)];
                        return (ejected, true, voteCounts);
                    }
                    return (null, true, voteCounts);
                    
                case TiePolicy.EjectLowestId:
                    if (topVoted.Contains("__SKIP__"))
                        topVoted.Remove("__SKIP__");
                    if (topVoted.Count > 0)
                    {
                        var ejected = topVoted.OrderBy(id => id).First();
                        return (ejected, true, voteCounts);
                    }
                    return (null, true, voteCounts);
                    
                default:
                    return (null, topVoted.Count > 1, voteCounts);
            }
        }
        
        var winner = topVoted[0];
        if (winner == "__SKIP__")
            return (null, false, voteCounts);
            
        return (winner, false, voteCounts);
    }
    
    public void EjectPlayer(Player player)
    {
        player.State = PlayerState.Dead;
        _logger.LogInformation("Ejected: {Player}", player.ClientUuid);
    }
    
    #endregion
    
    #region Tasky
    
    /// <summary>
    /// Pokusí se dokončit task. Kontroluje:
    /// - Hráč je naživu
    /// - Není impostor
    /// - Task existuje a patří hráči
    /// - Task ještě není dokončen
    /// - Hráč je dostatečně blízko
    /// Poznámka: Duchové (mrtví hráči) MOHOU plnit úkoly - to je důležité pro crew!
    /// </summary>
    public (bool Success, string? Error) TryCompleteTask(Player player, string taskId)
    {
        // Duchové mohou plnit úkoly - neblokujeme mrtvé hráče
        // Ale musí být crew role
        if (player.Role == PlayerRole.Impostor)
            return (false, "Impostoři nemohou dělat tasky");
            
        var task = player.Tasks.FirstOrDefault(t => t.TaskId == taskId);
        if (task == null)
            return (false, "Tento task ti nepatří");
            
        if (player.CompletedTaskIds.Contains(taskId))
            return (false, "Task již dokončen");
            
        // Kontrola vzdálenosti
        var distance = player.Position.DistanceTo(task.Location);
        if (distance > _config.TaskStartDistanceM)
            return (false, $"Příliš daleko ({distance:F1}m, max {_config.TaskStartDistanceM}m)");
        
        // Dokončit task
        player.CompletedTaskIds.Add(taskId);
        
        _logger.LogInformation("Task completed: {Player} dokončil {Task} na pozici {Pos} (vzdálenost {Dist:F1}m)", 
            player.ClientUuid, task.Name, task.Location, distance);
        
        return (true, null);
    }
    
    /// <summary>
    /// Najde nejbližší nedokončený task hráče
    /// </summary>
    public GameTask? FindNearestTask(Player player, double maxDistance)
    {
        return player.Tasks
            .Where(t => !player.CompletedTaskIds.Contains(t.TaskId))
            .Where(t => player.Position.DistanceTo(t.Location) <= maxDistance)
            .OrderBy(t => player.Position.DistanceTo(t.Location))
            .FirstOrDefault();
    }
    
    #endregion
    
    #region Win conditions
    
    public (bool GameOver, string? WinningFaction, string? Reason) CheckWinConditions(
        Dictionary<string, Player> players, List<GameTask> tasks)
    {
        var aliveCrew = players.Values.Count(p => p.State == PlayerState.Alive && p.Role == PlayerRole.Crew);
        var aliveImpostors = players.Values.Count(p => p.State == PlayerState.Alive && p.Role == PlayerRole.Impostor);
        
        // Impostoři vyhráli - mají většinu nebo rovnost
        if (aliveImpostors >= aliveCrew && aliveCrew > 0)
        {
            return (true, "Impostor", "Impostoři mají převahu");
        }
        
        // Všichni impostoři mrtví
        if (aliveImpostors == 0)
        {
            return (true, "Crew", "Všichni impostoři eliminováni");
        }
        
        // Všechny tasky hotové (počítáme pouze crew tasky)
        var crewPlayers = players.Values.Where(p => p.Role == PlayerRole.Crew).ToList();
        var totalTasks = crewPlayers.Sum(p => p.Tasks.Count);
        var completedTasks = crewPlayers.Sum(p => p.CompletedTaskIds.Count);
        
        if (totalTasks > 0 && completedTasks >= totalTasks)
        {
            return (true, "Crew", "Všechny tasky dokončeny");
        }
        
        return (false, null, null);
    }
    
    #endregion
    
    #region Role assignment
    
    public void AssignRoles(List<Player> players, int impostorCount)
    {
        if (players.Count < 2)
        {
            // Minimálně 2 hráči
            foreach (var p in players)
                p.Role = PlayerRole.Crew;
            return;
        }
        
        // Omezení počtu impostorů
        var maxImpostors = Math.Max(1, players.Count / 3);
        impostorCount = Math.Min(impostorCount, maxImpostors);
        impostorCount = Math.Min(impostorCount, players.Count - 1);
        
        // Náhodně vybereme impostory
        var shuffled = players.OrderBy(_ => RandomNumberGenerator.GetInt32(int.MaxValue)).ToList();
        
        for (int i = 0; i < players.Count; i++)
        {
            shuffled[i].Role = i < impostorCount ? PlayerRole.Impostor : PlayerRole.Crew;
            shuffled[i].State = PlayerState.Alive;
            shuffled[i].EmergencyMeetingsUsed = 0;
            shuffled[i].LastEmergencyMeetingTime = null;
            shuffled[i].LastKillTime = null;
            shuffled[i].CompletedTaskIds.Clear();
            shuffled[i].CurrentTaskId = null;
        }
        
        _logger.LogInformation("Roles assigned: {ImpostorCount} impostorů z {TotalPlayers} hráčů", 
            impostorCount, players.Count);
    }
    
    public List<GameTask> GenerateTasks(int count, Position center, double radius, MapData? mapData = null, int startIndex = 0)
    {
        var tasks = new List<GameTask>();
        
        var taskNames = new[] { 
            "Opravit kabel", "Zkalibrovat senzor", "Stáhnout data", 
            "Nabít baterii", "Vyčistit filtr", "Nastavit kompas",
            "Opravit antenu", "Zkontrolovat zásoby", "Otestovat reaktor"
        };
        
        // Get task positions - use map data if available
        List<Position> positions;
        
        if (mapData != null && _overpassService != null && mapData.ReachablePositions.Count >= count)
        {
            positions = _overpassService.GetPOIBasedPositions(mapData, count, _random);
            _logger.LogInformation("Generated {Count} positions for tasks using map data", positions.Count);
        }
        else
        {
            positions = GenerateRandomPositions(count, center, radius, _random);
            _logger.LogDebug("Generated {Count} positions for tasks using random fallback", positions.Count);
        }
        
        for (int i = 0; i < count && i < positions.Count; i++)
        {
            string taskId = $"task_{startIndex + i}";
            string taskName = taskNames[(startIndex + i) % taskNames.Length];
            
            tasks.Add(new GameTask
            {
                TaskId = taskId,
                Name = taskName,
                Location = positions[i],
                Type = TaskType.Instant
            });
        }
        
        return tasks;
    }
    
    private List<Position> GenerateRandomPositions(int count, Position center, double radius, Random random)
    {
        var positions = new List<Position>();
        
        for (int i = 0; i < count; i++)
        {
            // Náhodná pozice v kruhu
            var angle = random.NextDouble() * 2 * Math.PI;
            var distance = random.NextDouble() * radius * 0.8; // 80% radius aby nebyly na kraji
            var lat = center.Lat + (distance / 111000) * Math.Cos(angle);
            var lon = center.Lon + (distance / (111000 * Math.Cos(center.Lat * Math.PI / 180))) * Math.Sin(angle);
            positions.Add(new Position(lat, lon));
        }
        
        return positions;
    }
    
    /// <summary>
    /// Generate repair station positions using map data for reachability
    /// </summary>
    public List<Position> GenerateRepairStationPositions(int count, Position center, double radius, MapData? mapData = null)
    {
        if (mapData != null && _overpassService != null && mapData.ReachablePositions.Count >= count)
        {
            // Get well-distributed reachable positions
            var positions = _overpassService.GetDistributedReachablePositions(
                mapData, count, _random, _config.MinTaskSpacingMeters * 2);
            
            if (positions.Count >= count)
            {
                _logger.LogInformation("Generated {Count} repair station positions using map data", positions.Count);
                return positions;
            }
        }
        
        // Fallback: opposite ends of play area
        var result = new List<Position>();
        for (int i = 0; i < count; i++)
        {
            var angle = (2 * Math.PI * i) / count;
            var dist = radius * 0.7;
            var lat = center.Lat + (dist * Math.Cos(angle)) / 111000.0;
            var lon = center.Lon + (dist * Math.Sin(angle)) / (111000.0 * Math.Cos(center.Lat * Math.PI / 180));
            result.Add(new Position(lat, lon));
        }
        
        return result;
    }
    
    #endregion
}
