using Microsoft.EntityFrameworkCore;
using WorkClub.Application.Interfaces;
using WorkClub.Application.Shifts.DTOs;
using WorkClub.Domain.Entities;
using WorkClub.Infrastructure.Data;

namespace WorkClub.Api.Services;

public class ShiftService
{
    private readonly AppDbContext _context;
    private readonly ITenantProvider _tenantProvider;

    public ShiftService(AppDbContext context, ITenantProvider tenantProvider)
    {
        _context = context;
        _tenantProvider = tenantProvider;
    }

    public async Task<ShiftListDto> GetShiftsAsync(DateTimeOffset? from, DateTimeOffset? to, int page, int pageSize, string? currentExternalUserId = null)
    {
        var query = _context.Shifts.AsQueryable();

        if (from.HasValue)
            query = query.Where(s => s.StartTime >= from.Value);

        if (to.HasValue)
            query = query.Where(s => s.StartTime <= to.Value);

        var total = await query.CountAsync();

        var shifts = await query
            .OrderBy(s => s.StartTime)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        var shiftIds = shifts.Select(s => s.Id).ToList();
        var signupCounts = await _context.ShiftSignups
            .Where(ss => shiftIds.Contains(ss.ShiftId))
            .GroupBy(ss => ss.ShiftId)
            .Select(g => new { ShiftId = g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.ShiftId, x => x.Count);

        var tenantId = _tenantProvider.GetTenantId();
        var memberId = currentExternalUserId != null 
            ? await _context.Members
                .Where(m => m.ExternalUserId == currentExternalUserId && m.TenantId == tenantId)
                .Select(m => (Guid?)m.Id)
                .FirstOrDefaultAsync()
            : null;

        var userSignups = memberId.HasValue
            ? await _context.ShiftSignups
                .Where(ss => shiftIds.Contains(ss.ShiftId) && ss.MemberId == memberId.Value)
                .Select(ss => ss.ShiftId)
                .ToListAsync()
            : new List<Guid>();
            
        var userSignedUpShiftIds = userSignups.ToHashSet();

        var items = shifts.Select(s => new ShiftListItemDto(
            s.Id,
            s.Title,
            s.StartTime,
            s.EndTime,
            s.Capacity,
            signupCounts.GetValueOrDefault(s.Id, 0),
            userSignedUpShiftIds.Contains(s.Id)
        )).ToList();

        return new ShiftListDto(items, total, page, pageSize);
    }

    public async Task<ShiftDetailDto?> GetShiftByIdAsync(Guid id, string? currentExternalUserId = null)
    {
        var shift = await _context.Shifts.FindAsync(id);

  if (shift == null)
    return null;

  var signups = await (from ss in _context.ShiftSignups
    where ss.ShiftId == id
    join m in _context.Members on ss.MemberId equals m.Id
    orderby ss.SignedUpAt
    select new { ss.Id, ss.MemberId, m.DisplayName, m.ExternalUserId, ss.SignedUpAt })
    .ToListAsync();

  var signupDtos = signups.Select(ss => new ShiftSignupDto(
    ss.Id,
    ss.MemberId,
    ss.DisplayName,
    ss.ExternalUserId,
    ss.SignedUpAt
  )).ToList();

  var isSignedUp = currentExternalUserId != null && signupDtos.Any(s => s.ExternalUserId == currentExternalUserId);

  return new ShiftDetailDto(
            shift.Id,
            shift.Title,
            shift.Description,
            shift.Location,
            shift.StartTime,
            shift.EndTime,
            shift.Capacity,
            signupDtos,
            shift.ClubId,
            shift.CreatedById,
            shift.CreatedAt,
            shift.UpdatedAt,
            isSignedUp
        );
    }

    public async Task<(ShiftDetailDto? shift, string? error)> CreateShiftAsync(CreateShiftRequest request, Guid createdById)
    {
        var tenantId = _tenantProvider.GetTenantId();

        var shift = new Shift
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            Title = request.Title,
            Description = request.Description,
            Location = request.Location,
            StartTime = request.StartTime,
            EndTime = request.EndTime,
            Capacity = request.Capacity,
            ClubId = request.ClubId,
            CreatedById = createdById,
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow
        };

        _context.Shifts.Add(shift);
        await _context.SaveChangesAsync();

        var dto = new ShiftDetailDto(
            shift.Id,
            shift.Title,
            shift.Description,
            shift.Location,
            shift.StartTime,
            shift.EndTime,
            shift.Capacity,
            new List<ShiftSignupDto>(),
            shift.ClubId,
            shift.CreatedById,
            shift.CreatedAt,
            shift.UpdatedAt,
            false
        );

        return (dto, null);
    }

    public async Task<(ShiftDetailDto? shift, string? error, bool isConflict)> UpdateShiftAsync(Guid id, UpdateShiftRequest request, string? currentExternalUserId = null)
    {
        var shift = await _context.Shifts.FindAsync(id);

        if (shift == null)
            return (null, "Shift not found", false);

        if (request.Title != null)
            shift.Title = request.Title;

        if (request.Description != null)
            shift.Description = request.Description;

        if (request.Location != null)
            shift.Location = request.Location;

        if (request.StartTime.HasValue)
            shift.StartTime = request.StartTime.Value;

        if (request.EndTime.HasValue)
            shift.EndTime = request.EndTime.Value;

        if (request.Capacity.HasValue)
            shift.Capacity = request.Capacity.Value;

        shift.UpdatedAt = DateTimeOffset.UtcNow;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            return (null, "Shift was modified by another user. Please refresh and try again.", true);
        }

  var signups = await (from ss in _context.ShiftSignups
    where ss.ShiftId == id
    join m in _context.Members on ss.MemberId equals m.Id
    orderby ss.SignedUpAt
    select new { ss.Id, ss.MemberId, m.DisplayName, m.ExternalUserId, ss.SignedUpAt })
    .ToListAsync();

  var signupDtos = signups.Select(ss => new ShiftSignupDto(
    ss.Id,
    ss.MemberId,
    ss.DisplayName,
    ss.ExternalUserId,
    ss.SignedUpAt
  )).ToList();

  var isSignedUp = currentExternalUserId != null && signupDtos.Any(s => s.ExternalUserId == currentExternalUserId);

  var dto = new ShiftDetailDto(
            shift.Id,
            shift.Title,
            shift.Description,
            shift.Location,
            shift.StartTime,
            shift.EndTime,
            shift.Capacity,
            signupDtos,
            shift.ClubId,
            shift.CreatedById,
            shift.CreatedAt,
            shift.UpdatedAt,
            isSignedUp
        );

        return (dto, null, false);
    }

    public async Task<bool> DeleteShiftAsync(Guid id)
    {
        var shift = await _context.Shifts.FindAsync(id);

        if (shift == null)
            return false;

        _context.Shifts.Remove(shift);
        await _context.SaveChangesAsync();

        return true;
    }

    public async Task<(bool success, string? error, bool isConflict)> SignUpForShiftAsync(Guid shiftId, string externalUserId)
    {
        var tenantId = _tenantProvider.GetTenantId();

        var member = await _context.Members
            .FirstOrDefaultAsync(m => m.ExternalUserId == externalUserId && m.TenantId == tenantId);

        if (member == null)
            return (false, "Member not found", false);

        var memberId = member.Id;

        var shift = await _context.Shifts.FindAsync(shiftId);

        if (shift == null)
            return (false, "Shift not found", false);

        if (shift.StartTime <= DateTimeOffset.UtcNow)
        {
            return (false, "Cannot sign up for past shifts", false);
        }

        var existingSignup = await _context.ShiftSignups
            .FirstOrDefaultAsync(ss => ss.ShiftId == shiftId && ss.MemberId == memberId);

        if (existingSignup != null)
        {
            return (false, "Already signed up for this shift", true);
        }

        for (int attempt = 0; attempt < 2; attempt++)
        {
            try
            {
                var currentSignups = await _context.ShiftSignups
                    .Where(ss => ss.ShiftId == shiftId)
                    .CountAsync();

                if (currentSignups >= shift.Capacity)
                {
                    return (false, "Shift is at full capacity", true);
                }

                var signup = new ShiftSignup
                {
                    Id = Guid.NewGuid(),
                    TenantId = tenantId,
                    ShiftId = shiftId,
                    MemberId = memberId,
                    SignedUpAt = DateTimeOffset.UtcNow
                };

                _context.ShiftSignups.Add(signup);
                await _context.SaveChangesAsync();

                return (true, null, false);
            }
            catch (DbUpdateConcurrencyException) when (attempt == 0)
            {
                _context.Entry(shift).Reload();
            }
        }

        return (false, "Shift capacity changed during sign-up", true);
    }

    public async Task<(bool success, string? error)> CancelSignupAsync(Guid shiftId, string externalUserId)
    {
        var tenantId = _tenantProvider.GetTenantId();

        var member = await _context.Members
            .FirstOrDefaultAsync(m => m.ExternalUserId == externalUserId && m.TenantId == tenantId);

        if (member == null)
            return (false, "Member not found");

        var signup = await _context.ShiftSignups
            .FirstOrDefaultAsync(ss => ss.ShiftId == shiftId && ss.MemberId == member.Id);

        if (signup == null)
        {
            return (false, "Sign-up not found");
        }

        _context.ShiftSignups.Remove(signup);
        await _context.SaveChangesAsync();

        return (true, null);
    }
}
