using CalledIt.DataAccess;
using CalledIt.DataAccess.Entities;
using CalledIt.Domain.Dtos;
using Microsoft.EntityFrameworkCore;

namespace CalledIt.Domain.Services;

public class BetService : IBetService
{
    private readonly CalledItDbContext _dbContext;

    public BetService(CalledItDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<BetResponseDto> UpsertAsync(int userId, int matchId, UpsertBetDto dto)
    {
        var match = await _dbContext.Matches.FindAsync(matchId)
            ?? throw new KeyNotFoundException($"Match with ID {matchId} not found.");

        var deadline = match.MatchDate.AddMinutes(-1);
        if (DateTime.UtcNow >= deadline)
        {
            throw new InvalidOperationException("Bets can no longer be placed for this match.");
        }

        var bet = await _dbContext.Bets
            .SingleOrDefaultAsync(b => b.UserId == userId && b.MatchId == matchId);

        var isNewBet = bet is null;
        if (bet is null)
        {
            bet = new BetEntity { UserId = userId, MatchId = matchId };
            _dbContext.Bets.Add(bet);
        }

        bet.HomeScore = dto.HomeScore;
        bet.AwayScore = dto.AwayScore;

        await _dbContext.SaveChangesAsync();

        if (isNewBet)
        {
            var stats = await _dbContext.UserCompetitionStats.FindAsync(userId, match.CompetitionId);
            if (stats is null)
            {
                var username = await _dbContext.Users
                    .Where(u => u.Id == userId)
                    .Select(u => u.Username)
                    .FirstAsync();

                _dbContext.UserCompetitionStats.Add(new CalledIt.DataAccess.Entities.UserCompetitionStatsEntity
                {
                    UserId = userId,
                    CompetitionId = match.CompetitionId,
                    Username = username,
                    BetsPlaced = 1,
                });
            }
            else
            {
                stats.BetsPlaced++;
            }
            await _dbContext.SaveChangesAsync();
        }

        return new BetResponseDto(bet.MatchId, bet.HomeScore, bet.AwayScore, bet.Points);
    }

    public async Task<IReadOnlyList<int>> GetCompetitionIdsWithBetsAsync(int userId)
    {
        return await _dbContext.Bets
            .AsNoTracking()
            .Where(b => b.UserId == userId)
            .Select(b => b.Match.CompetitionId)
            .Distinct()
            .ToListAsync();
    }

    public async Task<IReadOnlyList<BetResponseDto>> GetByCompetitionAndUserAsync(int userId, int competitionId)
    {
        var bets = await _dbContext.Bets
            .AsNoTracking()
            .Where(b => b.UserId == userId && b.Match.CompetitionId == competitionId)
            .Select(b => new BetResponseDto(b.MatchId, b.HomeScore, b.AwayScore, b.Points))
            .ToListAsync();

        return bets;
    }

    public async Task<MatchBetStatsDto> GetMatchStatsAsync(int matchId)
    {
        var bets = await _dbContext.Bets
            .AsNoTracking()
            .Where(b => b.MatchId == matchId)
            .Select(b => new { b.HomeScore, b.AwayScore })
            .ToListAsync();

        if (bets.Count == 0)
        {
            return new MatchBetStatsDto(matchId, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        }

        var total = bets.Count;
        var homeWins = bets.Count(b => b.HomeScore > b.AwayScore);
        var draws = bets.Count(b => b.HomeScore == b.AwayScore);
        var awayWins = bets.Count(b => b.HomeScore < b.AwayScore);

        var topPick = bets
            .GroupBy(b => new { b.HomeScore, b.AwayScore })
            .OrderByDescending(g => g.Count())
            .First();

        return new MatchBetStatsDto(
            MatchId: matchId,
            TotalBets: total,
            HomeWinPercent: Math.Round((double)homeWins / total * 100, 1),
            DrawPercent: Math.Round((double)draws / total * 100, 1),
            AwayWinPercent: Math.Round((double)awayWins / total * 100, 1),
            AvgHomeScore: Math.Round(bets.Average(b => b.HomeScore), 1),
            AvgAwayScore: Math.Round(bets.Average(b => b.AwayScore), 1),
            TopPickHomeScore: topPick.Key.HomeScore,
            TopPickAwayScore: topPick.Key.AwayScore,
            TopPickCount: topPick.Count()
        );
    }
}
