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>
/// WorkShift master service (FR-HR-MD-01) — the attendance baseline Late/Early/OT
/// figures are computed against (docs/12-BACKEND-HRM.md A.3).
/// </summary>
public sealed class WorkShiftService : IWorkShiftService
{
    private readonly IRepository<WorkShift> _shifts;
    private readonly IUnitOfWork _uow;

    public WorkShiftService(IRepository<WorkShift> shifts, IUnitOfWork uow)
    {
        _shifts = shifts;
        _uow = uow;
    }

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

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

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

    public async Task<ETagged<WorkShiftDto>?> GetAsync(int workShiftId, CancellationToken ct = default)
    {
        var shift = await _shifts.Query().AsNoTracking().FirstOrDefaultAsync(w => w.WorkShiftId == workShiftId, ct);
        return shift is null ? null : new ETagged<WorkShiftDto>(Map(shift), shift.RowVersion);
    }

    public async Task<ETagged<WorkShiftDto>> CreateAsync(CreateWorkShiftRequest request, CancellationToken ct = default)
    {
        var code = request.Code.Trim();
        if (await _shifts.Query().AnyAsync(w => w.Code.ToLower() == code.ToLower(), ct))
            throw new ConflictException($"A work shift with code '{code}' already exists.");

        var shift = new WorkShift
        {
            Code = code,
            Name = request.Name.Trim(),
            StartTime = request.StartTime,
            EndTime = request.EndTime,
            IsOvernight = request.IsOvernight,
            GraceMinutes = request.GraceMinutes,
            BreakMinutes = request.BreakMinutes,
            StandardWorkingMinutes = request.StandardWorkingMinutes,
            OtMultiplier = request.OtMultiplier,
            WorkingDaysMask = request.WorkingDaysMask,
            Status = EntityStatus.Active,
            CreatedAt = DateTime.UtcNow
        };

        await _shifts.AddAsync(shift, ct);
        await _uow.SaveChangesAsync(ct);

        return new ETagged<WorkShiftDto>(Map(shift), shift.RowVersion);
    }

    public async Task<ETagged<WorkShiftDto>> UpdateAsync(int workShiftId, UpdateWorkShiftRequest request, uint expectedRowVersion, CancellationToken ct = default)
    {
        var shift = await _shifts.GetByIdAsync(workShiftId, ct)
            ?? throw new NotFoundException($"Work shift {workShiftId} was not found.");

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

        shift.Name = request.Name.Trim();
        shift.StartTime = request.StartTime;
        shift.EndTime = request.EndTime;
        shift.IsOvernight = request.IsOvernight;
        shift.GraceMinutes = request.GraceMinutes;
        shift.BreakMinutes = request.BreakMinutes;
        shift.StandardWorkingMinutes = request.StandardWorkingMinutes;
        shift.OtMultiplier = request.OtMultiplier;
        shift.WorkingDaysMask = request.WorkingDaysMask;
        shift.UpdatedAt = DateTime.UtcNow;

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

        return new ETagged<WorkShiftDto>(Map(shift), shift.RowVersion);
    }

    public async Task SetStatusAsync(int workShiftId, EntityStatus status, CancellationToken ct = default)
    {
        var shift = await _shifts.GetByIdAsync(workShiftId, ct)
            ?? throw new NotFoundException($"Work shift {workShiftId} was not found.");

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

    private static WorkShiftDto Map(WorkShift w) => new(
        w.WorkShiftId, w.Code, w.Name, w.StartTime, w.EndTime, w.IsOvernight,
        w.GraceMinutes, w.BreakMinutes, w.StandardWorkingMinutes, w.OtMultiplier, w.WorkingDaysMask,
        w.Status, w.CreatedAt, w.UpdatedAt);
}
