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

namespace OmsorgCore.Infrastructure.Repositories;

public class AbsenceRepository : IAbsenceRepository
{
    private readonly OmsorgCoreDbContext _db;

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

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

    public async Task<(IReadOnlyList<Absence> Items, int TotalCount)> GetPagedAsync(
        string? status,
        string? type,
        Guid? employeeId,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default,
        Guid? restrictToEmployeeId = null)
    {
        var query = _db.Absences.AsNoTracking().Include(a => a.Employee).Where(a => !a.IsDeleted);

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

        if (!string.IsNullOrWhiteSpace(status))
        {
            query = query.Where(a => a.Status == status);
        }

        if (!string.IsNullOrWhiteSpace(type))
        {
            query = query.Where(a => a.Type == type);
        }

        var totalCount = await query.CountAsync(cancellationToken);

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

        return (items, totalCount);
    }

    public async Task<bool> HasOverlappingAbsenceAsync(
        Guid employeeId,
        DateOnly startDate,
        DateOnly endDate,
        IReadOnlyCollection<string> blockingStatusValues,
        CancellationToken cancellationToken = default)
    {
        if (blockingStatusValues.Count == 0)
        {
            return false;
        }

        return await _db.Absences
            .Where(a => a.EmployeeId == employeeId && !a.IsDeleted)
            .Where(a => blockingStatusValues.Contains(a.Status))
            .Where(a => a.StartDate <= endDate && a.EndDate >= startDate)
            .AnyAsync(cancellationToken);
    }

    public async Task AddAsync(Absence absence, CancellationToken cancellationToken = default)
        => await _db.Absences.AddAsync(absence, cancellationToken);

    public Task UpdateAsync(Absence absence, CancellationToken cancellationToken = default)
    {
        _db.Absences.Update(absence);
        return Task.CompletedTask;
    }

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

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

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

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

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

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

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

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