using FluentValidation;
using MediatR;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Domain.Hrm;

namespace SolutionErp.Application.Hrm;

// Phase 11 P11-B Wave 1 (Mig 42 — S43 2026-05-30) — Số dư phép theo năm.
// Query lazy: list mọi LeaveType IsActive LEFT JOIN LeaveBalance — chưa có row thì
// synthesize default (Entitled=DaysPerYear, Used=0, Adjustment=0). Admin upsert qua
// AdjustLeaveBalanceCommand. Trừ phép tự động ở ApproveLeaveRequestHandler (terminal).
//
// RemainingDays = EntitledDays + AdjustmentDays − UsedDays (COMPUTED, KHÔNG store).
// HRM entities KHÔNG có global HasQueryFilter → query phải Where(!IsDeleted) thủ công.

// ===== DTO =====

public record LeaveBalanceDto(
    Guid LeaveTypeId,
    string Code,
    string Name,
    int Year,
    decimal EntitledDays,
    decimal UsedDays,
    decimal AdjustmentDays,
    decimal RemainingDays,
    decimal DaysPerYear,
    bool IsPaid);

// ===== Shared builder: merge active LeaveTypes + balances cho 1 user/năm =====

internal static class LeaveBalanceProjection
{
    public static async Task<List<LeaveBalanceDto>> BuildAsync(
        IApplicationDbContext db, Guid userId, int year, CancellationToken ct)
    {
        var types = await db.LeaveTypes.AsNoTracking()
            .Where(t => !t.IsDeleted && t.IsActive)
            .OrderBy(t => t.Code)
            .Select(t => new { t.Id, t.Code, t.Name, t.DaysPerYear, t.IsPaid })
            .ToListAsync(ct);

        var balances = await db.LeaveBalances.AsNoTracking()
            .Where(b => !b.IsDeleted && b.UserId == userId && b.Year == year)
            .Select(b => new { b.LeaveTypeId, b.EntitledDays, b.UsedDays, b.AdjustmentDays })
            .ToListAsync(ct);
        var byType = balances.ToDictionary(b => b.LeaveTypeId);

        return types.Select(t =>
        {
            byType.TryGetValue(t.Id, out var b);
            var entitled = b?.EntitledDays ?? t.DaysPerYear;
            var used = b?.UsedDays ?? 0m;
            var adjustment = b?.AdjustmentDays ?? 0m;
            return new LeaveBalanceDto(
                t.Id, t.Code, t.Name, year,
                entitled, used, adjustment,
                entitled + adjustment - used,
                t.DaysPerYear, t.IsPaid);
        }).ToList();
    }
}

// ===== Query: số dư phép của chính mình =====

public record GetMyLeaveBalancesQuery(int Year) : IRequest<List<LeaveBalanceDto>>;

public class GetMyLeaveBalancesHandler(IApplicationDbContext db, ICurrentUser cu)
    : IRequestHandler<GetMyLeaveBalancesQuery, List<LeaveBalanceDto>>
{
    public async Task<List<LeaveBalanceDto>> Handle(GetMyLeaveBalancesQuery req, CancellationToken ct)
    {
        if (cu.UserId is null) throw new UnauthorizedException();
        return await LeaveBalanceProjection.BuildAsync(db, cu.UserId.Value, req.Year, ct);
    }
}

// ===== Query: số dư phép của 1 user (admin) =====

public record GetUserLeaveBalancesQuery(Guid UserId, int Year) : IRequest<List<LeaveBalanceDto>>;

public class GetUserLeaveBalancesHandler(IApplicationDbContext db)
    : IRequestHandler<GetUserLeaveBalancesQuery, List<LeaveBalanceDto>>
{
    public async Task<List<LeaveBalanceDto>> Handle(GetUserLeaveBalancesQuery req, CancellationToken ct)
        => await LeaveBalanceProjection.BuildAsync(db, req.UserId, req.Year, ct);
}

// ===== Command: admin upsert (carry-over / điều chỉnh) =====

public record AdjustLeaveBalanceCommand(
    Guid UserId,
    Guid LeaveTypeId,
    int Year,
    decimal? EntitledDays,
    decimal? AdjustmentDays) : IRequest;

public class AdjustLeaveBalanceValidator : AbstractValidator<AdjustLeaveBalanceCommand>
{
    public AdjustLeaveBalanceValidator()
    {
        RuleFor(x => x.UserId).NotEmpty();
        RuleFor(x => x.LeaveTypeId).NotEmpty();
        RuleFor(x => x.Year).InclusiveBetween(2000, 2100);
        RuleFor(x => x.EntitledDays).GreaterThanOrEqualTo(0).When(x => x.EntitledDays.HasValue);
    }
}

public class AdjustLeaveBalanceHandler(IApplicationDbContext db, ICurrentUser cu, IDateTime clock)
    : IRequestHandler<AdjustLeaveBalanceCommand>
{
    public async Task Handle(AdjustLeaveBalanceCommand req, CancellationToken ct)
    {
        var typeExists = await db.LeaveTypes.AsNoTracking()
            .AnyAsync(t => t.Id == req.LeaveTypeId && !t.IsDeleted, ct);
        if (!typeExists) throw new NotFoundException("LeaveType", req.LeaveTypeId);

        var bal = await db.LeaveBalances
            .FirstOrDefaultAsync(b => b.UserId == req.UserId && b.LeaveTypeId == req.LeaveTypeId
                && b.Year == req.Year && !b.IsDeleted, ct);

        if (bal is null)
        {
            var daysPerYear = await db.LeaveTypes.AsNoTracking()
                .Where(t => t.Id == req.LeaveTypeId).Select(t => t.DaysPerYear).FirstOrDefaultAsync(ct);
            bal = new LeaveBalance
            {
                UserId = req.UserId,
                LeaveTypeId = req.LeaveTypeId,
                Year = req.Year,
                EntitledDays = req.EntitledDays ?? daysPerYear,
                UsedDays = 0,
                AdjustmentDays = req.AdjustmentDays ?? 0,
                CreatedAt = clock.UtcNow,
                CreatedBy = cu.UserId,
            };
            db.LeaveBalances.Add(bal);
        }
        else
        {
            if (req.EntitledDays.HasValue) bal.EntitledDays = req.EntitledDays.Value;
            if (req.AdjustmentDays.HasValue) bal.AdjustmentDays = req.AdjustmentDays.Value;
            bal.UpdatedAt = clock.UtcNow;
            bal.UpdatedBy = cu.UserId;
        }
        await db.SaveChangesAsync(ct);
    }
}
