using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Dtos.Hrm;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services.Hrm;

/// <inheritdoc cref="IHrReportService"/>
public sealed class HrReportService : IHrReportService
{
    private readonly IRepository<AttendanceRecord> _attendance;
    private readonly IRepository<PayrollLine> _payrollLines;
    private readonly IRepository<EmployeeSalaryStructure> _salaryStructures;
    private readonly IRepository<LeaveBalance> _leaveBalances;
    private readonly IRepository<EmployeeDocument> _documents;

    public HrReportService(
        IRepository<AttendanceRecord> attendance, IRepository<PayrollLine> payrollLines,
        IRepository<EmployeeSalaryStructure> salaryStructures, IRepository<LeaveBalance> leaveBalances,
        IRepository<EmployeeDocument> documents)
    {
        _attendance = attendance;
        _payrollLines = payrollLines;
        _salaryStructures = salaryStructures;
        _leaveBalances = leaveBalances;
        _documents = documents;
    }

    public async Task<List<AttendanceSummaryRowDto>> AttendanceSummaryAsync(
        int periodYear, int periodMonth, int? departmentId, CancellationToken ct = default)
    {
        var periodStart = new DateTime(periodYear, periodMonth, 1);
        var periodEnd = periodStart.AddMonths(1).AddDays(-1);

        var q = _attendance.Query().AsNoTracking()
            .Include(r => r.Employee).ThenInclude(e => e!.Department)
            .Where(r => r.AttendanceDate >= periodStart && r.AttendanceDate <= periodEnd);
        if (departmentId is not null) q = q.Where(r => r.Employee!.DepartmentId == departmentId);

        var rows = await q.ToListAsync(ct);

        return rows.GroupBy(r => r.EmployeeId)
            .Select(g => new AttendanceSummaryRowDto(
                g.Key, g.First().Employee!.EmployeeCode, g.First().Employee!.FullName, g.First().Employee!.Department?.Name,
                g.Count(r => r.AttendanceStatus == AttendanceStatus.Present),
                g.Count(r => r.AttendanceStatus == AttendanceStatus.Absent),
                g.Count(r => r.AttendanceStatus == AttendanceStatus.OnLeave),
                g.Count(r => r.AttendanceStatus == AttendanceStatus.HalfDay),
                g.Sum(r => r.OvertimeMinutes),
                g.Sum(r => r.LateMinutes)))
            .OrderBy(r => r.EmployeeName)
            .ToList();
    }

    public async Task<List<OvertimeReportRowDto>> OvertimeReportAsync(int periodYear, int periodMonth, CancellationToken ct = default)
    {
        var periodStart = new DateTime(periodYear, periodMonth, 1);
        var periodEnd = periodStart.AddMonths(1).AddDays(-1);

        return await _attendance.Query().AsNoTracking()
            .Include(r => r.Employee)
            .Where(r => r.AttendanceDate >= periodStart && r.AttendanceDate <= periodEnd && r.OvertimeMinutes > 0)
            .OrderByDescending(r => r.OvertimeMinutes)
            .Select(r => new OvertimeReportRowDto(r.EmployeeId, r.Employee!.EmployeeCode, r.Employee!.FullName, r.AttendanceDate, r.OvertimeMinutes))
            .ToListAsync(ct);
    }

    public async Task<List<LateArrivalReportRowDto>> LateArrivalReportAsync(int periodYear, int periodMonth, CancellationToken ct = default)
    {
        var periodStart = new DateTime(periodYear, periodMonth, 1);
        var periodEnd = periodStart.AddMonths(1).AddDays(-1);

        return await _attendance.Query().AsNoTracking()
            .Include(r => r.Employee)
            .Where(r => r.AttendanceDate >= periodStart && r.AttendanceDate <= periodEnd && r.LateMinutes > 0)
            .OrderByDescending(r => r.LateMinutes)
            .Select(r => new LateArrivalReportRowDto(r.EmployeeId, r.Employee!.EmployeeCode, r.Employee!.FullName, r.AttendanceDate, r.LateMinutes))
            .ToListAsync(ct);
    }

    public async Task<List<PayrollRegisterRowDto>> PayrollRegisterAsync(int payrollRunId, CancellationToken ct = default)
    {
        return await _payrollLines.Query().AsNoTracking()
            .Include(l => l.Employee)
            .Where(l => l.PayrollRunId == payrollRunId)
            .OrderBy(l => l.Employee!.FullName)
            .Select(l => new PayrollRegisterRowDto(
                l.PayrollLineId, l.EmployeeId, l.Employee!.EmployeeCode, l.Employee!.FullName,
                l.GrossSalary, l.GrossSalary - l.NetSalary, l.NetSalary))
            .ToListAsync(ct);
    }

    public async Task<List<SalaryHistoryRowDto>> SalaryHistoryAsync(int employeeId, CancellationToken ct = default)
    {
        return await _salaryStructures.Query().AsNoTracking()
            .Where(s => s.EmployeeId == employeeId)
            .OrderByDescending(s => s.EffectiveFrom)
            .Select(s => new SalaryHistoryRowDto(s.EmployeeSalaryStructureId, s.EffectiveFrom, s.EffectiveTo, s.BasicSalary, s.Status.ToString()))
            .ToListAsync(ct);
    }

    public async Task<List<LeaveBalanceReportRowDto>> LeaveBalanceReportAsync(int year, CancellationToken ct = default)
    {
        return await _leaveBalances.Query().AsNoTracking()
            .Include(b => b.Employee)
            .Include(b => b.LeaveType)
            .Where(b => b.Year == year)
            .OrderBy(b => b.Employee!.FullName)
            .Select(b => new LeaveBalanceReportRowDto(
                b.EmployeeId, b.Employee!.EmployeeCode, b.Employee!.FullName, b.LeaveType!.Name,
                b.EntitledDays, b.TakenDays, b.EntitledDays + b.CarriedForwardDays + b.AdjustmentDays - b.TakenDays))
            .ToListAsync(ct);
    }

    public async Task<List<DocumentExpiryReportRowDto>> DocumentExpiryReportAsync(int withinDays, CancellationToken ct = default)
    {
        var cutoff = DateTime.UtcNow.Date.AddDays(withinDays);
        var today = DateTime.UtcNow.Date;

        var rows = await _documents.Query().AsNoTracking()
            .Include(d => d.Employee)
            .Include(d => d.HrDocumentType)
            .Where(d => d.ExpiryDate != null && d.ExpiryDate <= cutoff)
            .OrderBy(d => d.ExpiryDate)
            .ToListAsync(ct);

        return rows.Select(d => new DocumentExpiryReportRowDto(
            d.EmployeeDocumentId, d.EmployeeId, d.Employee!.EmployeeCode, d.Employee!.FullName,
            d.HrDocumentType!.Name, d.ExpiryDate!.Value, (d.ExpiryDate.Value.Date - today).Days)).ToList();
    }
}
