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>SalaryComponent master service (FR-HR-PAY-01, docs/13-BACKEND-HRM-API.md §6).</summary>
public sealed class SalaryComponentService : ISalaryComponentService
{
    private readonly IRepository<SalaryComponent> _components;
    private readonly IUnitOfWork _uow;

    public SalaryComponentService(IRepository<SalaryComponent> components, IUnitOfWork uow)
    {
        _components = components;
        _uow = uow;
    }

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

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

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

    public async Task<ETagged<SalaryComponentDto>?> GetAsync(int salaryComponentId, CancellationToken ct = default)
    {
        var component = await _components.Query().AsNoTracking().FirstOrDefaultAsync(c => c.SalaryComponentId == salaryComponentId, ct);
        return component is null ? null : new ETagged<SalaryComponentDto>(Map(component), component.RowVersion);
    }

    public async Task<ETagged<SalaryComponentDto>> CreateAsync(CreateSalaryComponentRequest request, CancellationToken ct = default)
    {
        var code = request.Code.Trim();
        if (await _components.Query().AnyAsync(c => c.Code.ToLower() == code.ToLower(), ct))
            throw new ConflictException($"A salary component with code '{code}' already exists.");

        var component = new SalaryComponent
        {
            Code = code,
            Name = request.Name.Trim(),
            ComponentType = request.ComponentType,
            IsTaxable = request.IsTaxable,
            IsEpfEtfApplicable = request.IsEpfEtfApplicable,
            Status = EntityStatus.Active,
            CreatedAt = DateTime.UtcNow
        };

        await _components.AddAsync(component, ct);
        await _uow.SaveChangesAsync(ct);

        return new ETagged<SalaryComponentDto>(Map(component), component.RowVersion);
    }

    public async Task<ETagged<SalaryComponentDto>> UpdateAsync(
        int salaryComponentId, UpdateSalaryComponentRequest request, uint expectedRowVersion, CancellationToken ct = default)
    {
        var component = await _components.GetByIdAsync(salaryComponentId, ct)
            ?? throw new NotFoundException($"Salary component {salaryComponentId} was not found.");

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

        component.Name = request.Name.Trim();
        component.IsTaxable = request.IsTaxable;
        component.IsEpfEtfApplicable = request.IsEpfEtfApplicable;
        component.UpdatedAt = DateTime.UtcNow;

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

        return new ETagged<SalaryComponentDto>(Map(component), component.RowVersion);
    }

    public async Task SetStatusAsync(int salaryComponentId, EntityStatus status, CancellationToken ct = default)
    {
        var component = await _components.GetByIdAsync(salaryComponentId, ct)
            ?? throw new NotFoundException($"Salary component {salaryComponentId} was not found.");

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

    private static SalaryComponentDto Map(SalaryComponent c) => new(
        c.SalaryComponentId, c.Code, c.Name, c.ComponentType, c.IsTaxable, c.IsEpfEtfApplicable, c.Status, c.CreatedAt, c.UpdatedAt);
}
