using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
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>
/// Effective-dated salary structure service (FR-HR-PAY-02) — creating a new
/// structure supersedes the previous open-ended one, preserving history for audits
/// (docs/12-BACKEND-HRM.md §13) rather than overwriting it.
/// </summary>
public sealed class EmployeeSalaryStructureService : IEmployeeSalaryStructureService
{
    private readonly IRepository<EmployeeSalaryStructure> _structures;
    private readonly IRepository<Employee> _employees;
    private readonly IRepository<SalaryComponent> _components;
    private readonly IUnitOfWork _uow;

    public EmployeeSalaryStructureService(
        IRepository<EmployeeSalaryStructure> structures, IRepository<Employee> employees,
        IRepository<SalaryComponent> components, IUnitOfWork uow)
    {
        _structures = structures;
        _employees = employees;
        _components = components;
        _uow = uow;
    }

    public async Task<List<EmployeeSalaryStructureDto>> ListHistoryAsync(int employeeId, CancellationToken ct = default)
    {
        var rows = await _structures.Query().AsNoTracking()
            .Include(s => s.Lines).ThenInclude(l => l.SalaryComponent)
            .Where(s => s.EmployeeId == employeeId)
            .OrderByDescending(s => s.EffectiveFrom)
            .ToListAsync(ct);

        return rows.Select(Map).ToList();
    }

    public async Task<EmployeeSalaryStructureDto?> GetCurrentAsync(int employeeId, CancellationToken ct = default)
    {
        var current = await _structures.Query().AsNoTracking()
            .Include(s => s.Lines).ThenInclude(l => l.SalaryComponent)
            .Where(s => s.EmployeeId == employeeId && s.EffectiveTo == null)
            .FirstOrDefaultAsync(ct);
        return current is null ? null : Map(current);
    }

    public async Task<EmployeeSalaryStructureDto> CreateAsync(
        int employeeId, CreateSalaryStructureRequest request, int actorUserId, CancellationToken ct = default)
    {
        if (!await _employees.Query().AnyAsync(e => e.EmployeeId == employeeId, ct))
            throw new NotFoundException($"Employee {employeeId} was not found.");

        var current = await _structures.Query()
            .FirstOrDefaultAsync(s => s.EmployeeId == employeeId && s.EffectiveTo == null, ct);
        if (current is not null)
        {
            if (request.EffectiveFrom.Date <= current.EffectiveFrom.Date)
                throw new DomainException(ErrorCodes.SalaryStructureOverlap,
                    "The new effective date must be after the current structure's effective date.", 409);

            current.EffectiveTo = request.EffectiveFrom.Date.AddDays(-1);
            current.Status = SalaryStructureStatus.Superseded;
        }

        var componentIds = request.Lines.Select(l => l.SalaryComponentId).ToList();
        var validComponentCount = await _components.Query().CountAsync(c => componentIds.Contains(c.SalaryComponentId), ct);
        if (validComponentCount != componentIds.Distinct().Count())
            throw new DomainException(ErrorCodes.Validation, "One or more salary components were not found.", 422);

        var structure = new EmployeeSalaryStructure
        {
            EmployeeId = employeeId,
            EffectiveFrom = request.EffectiveFrom.Date,
            BasicSalary = request.BasicSalary,
            Status = SalaryStructureStatus.Active,
            ApprovedBy = actorUserId,
            ApprovedAt = DateTime.UtcNow,
            CreatedBy = actorUserId,
            CreatedAt = DateTime.UtcNow,
            Lines = request.Lines.Select(l => new EmployeeSalaryStructureLine
            {
                SalaryComponentId = l.SalaryComponentId,
                Amount = l.Amount
            }).ToList()
        };

        await _structures.AddAsync(structure, ct);
        await _uow.SaveChangesAsync(ct);

        return await GetCurrentAsync(employeeId, ct) ?? Map(structure);
    }

    private static EmployeeSalaryStructureDto Map(EmployeeSalaryStructure s) => new(
        s.EmployeeSalaryStructureId, s.EmployeeId, s.EffectiveFrom, s.EffectiveTo, s.BasicSalary, s.Currency, s.Status,
        s.Lines.Select(l => new EmployeeSalaryStructureLineDto(l.SalaryComponentId, l.SalaryComponent?.Name, l.Amount)).ToList(),
        s.CreatedAt);
}
