using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Office;
using SolutionErp.Domain.Hrm;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Office;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Application;

// Phase 11 P11-E (S52 2026-06-08) — test-after GetAttendanceReportHandler aggregate.
// CORE logic = day-type classification (weekday / weekend Sat-Sun / holiday∈set) IN-MEMORY
// + OT quy đổi (weighted) theo OtPolicy active multipliers.
//
// Handler (AttendanceReportFeatures.cs):
//   otWeighted = otWeekday*mWd + otWeekend*mWe + otHoliday*mHol
//   day-type: holidaySet.Contains(DateOnly.FromDateTime(d)) → Holiday
//             else d.DayOfWeek is Sat/Sun → Weekend
//             else → Weekday
//   DaysPresent = Count(CheckInAt != null); TotalWorkHours = Sum(WorkHours ?? 0); OtRaw = Sum(OtHours ?? 0)
//
// ⚠️ Holiday OVERRIDE day-of-week: 2026-06-01 là thứ Hai (weekday) NHƯNG ∈ holidaySet
// → phân Holiday (holiday check chạy TRƯỚC weekend/weekday). Đây là điểm test then chốt.
// Holiday.Date = DateOnly (KHÔNG DateTime) — handler so qua DateOnly.FromDateTime.
public class AttendanceReportTests
{
    private static IdentityFixture NewFix() => new();

    // 2026-06-01 Monday (nhưng là ngày lễ) / 2026-06-02 Tuesday (weekday) / 2026-06-06 Saturday (weekend).
    private static readonly DateTime Holiday0601 = new(2026, 6, 1);
    private static readonly DateTime Weekday0602 = new(2026, 6, 2);
    private static readonly DateTime Weekend0606 = new(2026, 6, 6);

    private static OtPolicy BuildPolicy()
        => new()
        {
            Id = Guid.NewGuid(),
            Code = "STANDARD",
            Name = "OT chuẩn",
            MultiplierWeekday = 1.5m,
            MultiplierWeekend = 2.0m,
            MultiplierHoliday = 3.0m,
            MaxHoursPerDay = 4,
            MaxHoursPerMonth = 40,
            MaxHoursPerYear = 200,
            IsActive = true,
        };

    private static Holiday BuildHoliday(int year, DateTime date)
        => new()
        {
            Id = Guid.NewGuid(),
            Year = year,
            Date = DateOnly.FromDateTime(date),
            Name = "Ngày lễ test",
            IsActive = true,
        };

    private static Attendance BuildAttendance(Guid userId, string fullName, DateTime date,
        decimal otHours, decimal workHours, bool checkedIn = true)
        => new()
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            UserFullName = fullName,
            AttendanceDate = date.Date,
            CheckInAt = checkedIn ? date.Date.AddHours(8) : null,
            CheckOutAt = checkedIn ? date.Date.AddHours(17) : null,
            SourceIn = AttendanceSource.Web,
            SourceOut = AttendanceSource.Web,
            OtHours = otHours,
            WorkHours = workHours,
        };

    // ============ Case 1: Aggregate đầy đủ 1 user — day-type + weighted ============

    [Fact]
    public async Task GetReport_OneUser_ClassifiesDayTypesAndComputesWeightedOt()
    {
        var fix = NewFix();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
            var u = await fix.CreateUserAsync("u-rep1@test.local", "Nguyễn Văn A", null, Array.Empty<string>());

            db.OtPolicies.Add(BuildPolicy());
            db.Holidays.Add(BuildHoliday(2026, Holiday0601));
            db.Attendances.AddRange(
                BuildAttendance(u.Id, u.FullName, Weekday0602, otHours: 2m, workHours: 8m),  // weekday
                BuildAttendance(u.Id, u.FullName, Weekend0606, otHours: 3m, workHours: 8m),  // weekend (Sat)
                BuildAttendance(u.Id, u.FullName, Holiday0601, otHours: 1m, workHours: 8m)); // holiday (Mon nhưng lễ)
            await db.SaveChangesAsync(CancellationToken.None);

            var handler = new GetAttendanceReportHandler(db);
            var report = await handler.Handle(new GetAttendanceReportQuery(2026, 6, null), CancellationToken.None);

            report.Year.Should().Be(2026);
            report.Month.Should().Be(6);
            report.Rows.Should().HaveCount(1);

            var row = report.Rows[0];
            row.UserId.Should().Be(u.Id);
            row.OtWeekday.Should().Be(2m, "2026-06-02 Tuesday = weekday");
            row.OtWeekend.Should().Be(3m, "2026-06-06 Saturday = weekend");
            row.OtHoliday.Should().Be(1m, "2026-06-01 thứ Hai NHƯNG ∈ holidaySet → Holiday (override day-of-week)");
            row.OtRaw.Should().Be(6m, "tổng raw 2+3+1");
            row.OtWeighted.Should().Be(12.0m, "2×1.5 + 3×2.0 + 1×3.0 = 3+6+3");
            row.TotalWorkHours.Should().Be(24m, "8+8+8");
            row.DaysPresent.Should().Be(3, "cả 3 ngày có CheckInAt");

            report.GrandTotalOtWeighted.Should().Be(12.0m);
            report.GrandTotalWorkHours.Should().Be(24m);
        }
    }

    // ============ Case 2: DepartmentId filter — chỉ user đúng phòng ============

    [Fact]
    public async Task GetReport_WithDepartmentFilter_ReturnsOnlyUsersInDept()
    {
        var fix = NewFix();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

            var deptA = new Domain.Master.Department { Id = Guid.NewGuid(), Code = "DA", Name = "Phòng A" };
            var deptB = new Domain.Master.Department { Id = Guid.NewGuid(), Code = "DB", Name = "Phòng B" };
            db.Departments.AddRange(deptA, deptB);
            await db.SaveChangesAsync(CancellationToken.None);

            var inDept = await fix.CreateUserAsync("u-deptA@test.local", "User Phòng A", deptA.Id, Array.Empty<string>());
            var outDept = await fix.CreateUserAsync("u-deptB@test.local", "User Phòng B", deptB.Id, Array.Empty<string>());

            db.OtPolicies.Add(BuildPolicy());
            db.Holidays.Add(BuildHoliday(2026, Holiday0601));
            db.Attendances.AddRange(
                BuildAttendance(inDept.Id, inDept.FullName, Weekday0602, otHours: 2m, workHours: 8m),
                BuildAttendance(outDept.Id, outDept.FullName, Weekday0602, otHours: 5m, workHours: 8m));
            await db.SaveChangesAsync(CancellationToken.None);

            var handler = new GetAttendanceReportHandler(db);
            var report = await handler.Handle(new GetAttendanceReportQuery(2026, 6, deptA.Id), CancellationToken.None);

            report.Rows.Should().HaveCount(1, "chỉ user phòng A khớp filter");
            report.Rows[0].UserId.Should().Be(inDept.Id);
            report.Rows[0].DepartmentName.Should().Be("Phòng A");
            report.Rows[0].OtWeekday.Should().Be(2m);
            report.GrandTotalOtWeighted.Should().Be(3.0m, "chỉ 2h weekday × 1.5 = 3.0 (loại trừ user phòng B)");
        }
    }
}
