using ERPCore.Common.Http;
using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Dtos.Common;
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>Leave type master service (FR-HR-LV-01, docs/13-BACKEND-HRM-API.md §5).</summary>
public sealed class LeaveTypeService : ILeaveTypeService
{
    private readonly IRepository<LeaveType> _types;
    private readonly IUnitOfWork _uow;

    public LeaveTypeService(IRepository<LeaveType> types, IUnitOfWork uow)
    {
        _types = types;
        _uow = uow;
    }

    public async Task<PagedResponse<LeaveTypeDto>> ListAsync(PageQuery query, EntityStatus? status, CancellationToken ct = default)
    {
        var q = _types.Query().AsNoTracking();
        if (!string.IsNullOrWhiteSpace(query.Q))
        {
            var term = query.Q.Trim();
            q = q.Where(t => EF.Functions.ILike(t.Name, $"%{term}%") || EF.Functions.ILike(t.Code, $"%{term}%"));
        }
        if (status is not null) q = q.Where(t => t.Status == status);

        var total = await q.CountAsync(ct);
        var rows = await q.OrderBy(t => t.Name)
            .Skip(query.Skip).Take(query.PageSize)
            .Select(t => Map(t))
            .ToListAsync(ct);

        return PagedResponse<LeaveTypeDto>.Create(rows, query.Page, query.PageSize, total);
    }

    public async Task<ETagged<LeaveTypeDto>?> GetAsync(int leaveTypeId, CancellationToken ct = default)
    {
        var type = await _types.Query().AsNoTracking().FirstOrDefaultAsync(t => t.LeaveTypeId == leaveTypeId, ct);
        return type is null ? null : new ETagged<LeaveTypeDto>(Map(type), type.RowVersion);
    }

    public async Task<ETagged<LeaveTypeDto>> CreateAsync(CreateLeaveTypeRequest request, CancellationToken ct = default)
    {
        var code = request.Code.Trim();
        if (await _types.Query().AnyAsync(t => t.Code.ToLower() == code.ToLower(), ct))
            throw new ConflictException($"A leave type with code '{code}' already exists.");

        var type = new LeaveType
        {
            Code = code,
            Name = request.Name.Trim(),
            IsPaid = request.IsPaid,
            CountsAsNoPay = request.CountsAsNoPay,
            AccrualPerYear = request.AccrualPerYear,
            CarryForwardAllowed = request.CarryForwardAllowed,
            MaxCarryForwardDays = request.MaxCarryForwardDays,
            RequiresApproval = request.RequiresApproval,
            Status = EntityStatus.Active,
            CreatedAt = DateTime.UtcNow
        };

        await _types.AddAsync(type, ct);
        await _uow.SaveChangesAsync(ct);

        return new ETagged<LeaveTypeDto>(Map(type), type.RowVersion);
    }

    public async Task<ETagged<LeaveTypeDto>> UpdateAsync(int leaveTypeId, UpdateLeaveTypeRequest request, uint expectedRowVersion, CancellationToken ct = default)
    {
        var type = await _types.GetByIdAsync(leaveTypeId, ct)
            ?? throw new NotFoundException($"Leave type {leaveTypeId} was not found.");

        if (type.RowVersion != expectedRowVersion)
            throw new DomainException(ErrorCodes.ConcurrencyConflict, "The leave type was modified by another request.", 412);

        type.Name = request.Name.Trim();
        type.IsPaid = request.IsPaid;
        type.CountsAsNoPay = request.CountsAsNoPay;
        type.AccrualPerYear = request.AccrualPerYear;
        type.CarryForwardAllowed = request.CarryForwardAllowed;
        type.MaxCarryForwardDays = request.MaxCarryForwardDays;
        type.RequiresApproval = request.RequiresApproval;
        type.UpdatedAt = DateTime.UtcNow;

        try
        {
            await _uow.SaveChangesAsync(ct);
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new DomainException(ErrorCodes.ConcurrencyConflict, "The leave type was modified by another request.", 412);
        }

        return new ETagged<LeaveTypeDto>(Map(type), type.RowVersion);
    }

    public async Task SetStatusAsync(int leaveTypeId, EntityStatus status, CancellationToken ct = default)
    {
        var type = await _types.GetByIdAsync(leaveTypeId, ct)
            ?? throw new NotFoundException($"Leave type {leaveTypeId} was not found.");

        type.Status = status;
        type.UpdatedAt = DateTime.UtcNow;
        await _uow.SaveChangesAsync(ct);
    }

    private static LeaveTypeDto Map(LeaveType t) => new(
        t.LeaveTypeId, t.Code, t.Name, t.IsPaid, t.CountsAsNoPay, t.AccrualPerYear,
        t.CarryForwardAllowed, t.MaxCarryForwardDays, t.RequiresApproval, t.Status, t.CreatedAt, t.UpdatedAt);
}
