using ERPCore.Domain.Entities;
using ERPCore.Dtos.Hrm;
using ERPCore.Infra.UoW;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using ERPCore.System.Errors;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services.Hrm;

/// <summary>Configurable marginal tax slabs (FR-HR-PAY-04, docs/13-BACKEND-HRM-API.md §6).</summary>
public sealed class TaxSlabService : ITaxSlabService
{
    private readonly IRepository<TaxSlab> _slabs;
    private readonly IUnitOfWork _uow;

    public TaxSlabService(IRepository<TaxSlab> slabs, IUnitOfWork uow)
    {
        _slabs = slabs;
        _uow = uow;
    }

    public async Task<List<TaxSlabDto>> ListAsync(CancellationToken ct = default)
    {
        var rows = await _slabs.Query().AsNoTracking()
            .OrderByDescending(s => s.EffectiveFrom).ThenBy(s => s.LowerBound)
            .ToListAsync(ct);
        return rows.Select(Map).ToList();
    }

    public async Task<TaxSlabDto> CreateAsync(CreateTaxSlabRequest request, CancellationToken ct = default)
    {
        if (request.UpperBound is not null && request.UpperBound <= request.LowerBound)
            throw new DomainException(ErrorCodes.TaxSlabGapInvalid, "Upper bound must be greater than the lower bound.", 422);

        var overlapping = await _slabs.Query().AnyAsync(s =>
            s.EffectiveFrom.Date == request.EffectiveFrom.Date &&
            s.LowerBound < (request.UpperBound ?? decimal.MaxValue) &&
            (s.UpperBound ?? decimal.MaxValue) > request.LowerBound, ct);
        if (overlapping)
            throw new DomainException(ErrorCodes.TaxSlabGapInvalid, "This slab overlaps an existing slab for the same effective date.", 422);

        var slab = new TaxSlab
        {
            EffectiveFrom = request.EffectiveFrom.Date,
            LowerBound = request.LowerBound,
            UpperBound = request.UpperBound,
            Rate = request.Rate,
            CreatedAt = DateTime.UtcNow
        };

        await _slabs.AddAsync(slab, ct);
        await _uow.SaveChangesAsync(ct);

        return Map(slab);
    }

    public async Task<List<TaxSlab>> GetEffectiveSlabsAsync(DateTime asOf, CancellationToken ct = default)
    {
        return await _slabs.Query().AsNoTracking()
            .Where(s => s.EffectiveFrom <= asOf && (s.EffectiveTo == null || s.EffectiveTo >= asOf))
            .OrderBy(s => s.LowerBound)
            .ToListAsync(ct);
    }

    private static TaxSlabDto Map(TaxSlab s) => new(s.TaxSlabId, s.EffectiveFrom, s.EffectiveTo, s.LowerBound, s.UpperBound, s.Rate);
}
