using Microsoft.EntityFrameworkCore;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Infrastructure.Persistence;

namespace OmsorgCore.Infrastructure.Repositories;

public class AssignmentRepository : IAssignmentRepository
{
    private readonly OmsorgCoreDbContext _db;

    public AssignmentRepository(OmsorgCoreDbContext db)
    {
        _db = db;
    }

    public Task<Assignment?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
        => _db.Assignments.Include(a => a.Employee).FirstOrDefaultAsync(a => a.Id == id, cancellationToken);

    public async Task<IReadOnlyList<Assignment>> GetAllAsync(CancellationToken cancellationToken = default)
        => await _db.Assignments.AsNoTracking().Include(a => a.Employee).ToListAsync(cancellationToken);

    public async Task<(IReadOnlyList<Assignment> Items, int TotalCount)> GetPagedAsync(
        Guid? orderId = null,
        Guid? employeeId = null,
        DateOnly? fromDate = null,
        DateOnly? toDate = null,
        int page = 1,
        int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        var query = _db.Assignments.AsNoTracking().Include(a => a.Employee).Where(a => !a.IsDeleted);

        if (orderId.HasValue)
        {
            query = query.Where(a => a.OrderId == orderId.Value);
        }

        if (employeeId.HasValue)
        {
            query = query.Where(a => a.EmployeeId == employeeId.Value);
        }

        if (fromDate.HasValue && toDate.HasValue)
        {
            query = query.Where(a => a.StartDate <= toDate.Value && a.EndDate >= fromDate.Value);
        }

        var totalCount = await query.CountAsync(cancellationToken);

        var items = await query
            .OrderBy(a => a.StartDate)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);

        return (items, totalCount);
    }

    public async Task<bool> HasOverlapAsync(
        Guid employeeId,
        DateOnly startDate,
        DateOnly endDate,
        Guid? excludeAssignmentId = null,
        CancellationToken cancellationToken = default)
    {
        var query = _db.Assignments
            .Where(a => a.EmployeeId == employeeId && !a.IsDeleted)
            .Where(a => a.StartDate <= endDate && a.EndDate >= startDate);

        if (excludeAssignmentId.HasValue)
        {
            query = query.Where(a => a.Id != excludeAssignmentId.Value);
        }

        return await query.AnyAsync(cancellationToken);
    }

    public async Task<int> GetAssignedDayCountInWeekAsync(
        Guid employeeId,
        DateOnly weekStart,
        DateOnly weekEnd,
        Guid? excludeAssignmentId = null,
        CancellationToken cancellationToken = default)
    {
        var query = _db.Assignments
            .Where(a => a.EmployeeId == employeeId && !a.IsDeleted)
            .Where(a => a.StartDate <= weekEnd && a.EndDate >= weekStart);

        if (excludeAssignmentId.HasValue)
        {
            query = query.Where(a => a.Id != excludeAssignmentId.Value);
        }

        var overlapping = await query
            .Select(a => new { a.StartDate, a.EndDate })
            .ToListAsync(cancellationToken);

        var days = 0;
        foreach (var a in overlapping)
        {
            var rangeStart = a.StartDate > weekStart ? a.StartDate : weekStart;
            var rangeEnd = a.EndDate < weekEnd ? a.EndDate : weekEnd;
            days += rangeEnd.DayNumber - rangeStart.DayNumber + 1;
        }

        return days;
    }

    public async Task<int> CountForOrderAsync(Guid orderId, CancellationToken cancellationToken = default)
        => await _db.Assignments.CountAsync(a => a.OrderId == orderId && !a.IsDeleted, cancellationToken);

    public async Task AddAsync(Assignment assignment, CancellationToken cancellationToken = default)
        => await _db.Assignments.AddAsync(assignment, cancellationToken);

    public Task UpdateAsync(Assignment assignment, CancellationToken cancellationToken = default)
    {
        _db.Assignments.Update(assignment);
        return Task.CompletedTask;
    }

    public async Task<bool> SoftDeleteAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var assignment = await _db.Assignments.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
        if (assignment is null)
        {
            return false;
        }

        assignment.IsDeleted = true;
        assignment.DeletedAt = DateTime.UtcNow;
        return true;
    }

    public async Task<IReadOnlyList<Assignment>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default)
    {
        var query = _db.Assignments.IgnoreQueryFilters().AsNoTracking().Include(a => a.Employee).Where(a => a.IsDeleted);

        if (!string.IsNullOrWhiteSpace(search))
        {
            query = query.Where(a =>
                EF.Functions.ILike(a.Employee.FirstName, $"%{search}%") ||
                EF.Functions.ILike(a.Employee.LastName, $"%{search}%"));
        }

        return await query.OrderByDescending(a => a.DeletedAt).ToListAsync(cancellationToken);
    }

    public async Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var assignment = await _db.Assignments.IgnoreQueryFilters().FirstOrDefaultAsync(a => a.Id == id && a.IsDeleted, cancellationToken);
        if (assignment is null)
        {
            return false;
        }

        assignment.IsDeleted = false;
        assignment.DeletedAt = null;
        return true;
    }

    public Task SaveChangesAsync(CancellationToken cancellationToken = default)
        => _db.SaveChangesAsync(cancellationToken);
}
