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

namespace SolutionErp.Infrastructure.Tests.Application;

// Coverage gap #2 (MAJOR — S34 backlog, closed S45 2026-06-01) — test-AFTER shipped code.
// EmployeeSatelliteFeatures.cs — FK invariant + soft-delete + cascade semantics cho 5 satellite
// (WorkHistory/Education/FamilyRelation/Skill/Document). Cookie-cutter 5× cùng guard.
//
// CHỐT theo CODE (single source of truth):
//   - Create*: guard `AnyAsync(EmployeeProfileId == req.Id && !IsDeleted)` → NotFoundException("EmployeeProfile", id)
//     khi parent không tồn tại HOẶC parent soft-deleted.
//   - Delete*: soft (IsDeleted=true + DeletedAt + DeletedBy=currentUser.UserId). Re-find sau xoá → NotFound.
//   - DeleteEmployeeProfile (EmployeeFeatures.cs:427-443) CHỈ soft-delete parent — KHÔNG loop children.
//     → satellite vẫn IsDeleted=false sau khi parent soft-deleted (behavior hiện tại, KHÔNG cascade ở app-layer).
//
// FK note: satellite → EmployeeProfile DeleteBehavior.Cascade (EF config), nhưng cascade chỉ fire ở
//   HARD delete. App dùng soft-delete (set flag) → EF cascade KHÔNG kích hoạt. Case #5 lock behavior này.
//   EmployeeProfile → User Cascade + UserId UNIQUE → seed parent PHẢI có User thật (qua IdentityFixture).
//
// AuditingInterceptor KHÔNG wire trong test fixture → Delete handler set IsDeleted thủ công trong code
//   handler (không qua interceptor) → vẫn soft-delete đúng (handler tự set flag, không gọi db.Remove).
public class EmployeeSatelliteTests
{
    private static readonly DateTime FixedNow = new(2026, 6, 1, 8, 0, 0, DateTimeKind.Utc);

    // Seed 1 EmployeeProfile (+ User vì FK Cascade Users + UNIQUE UserId). Trả entity để dùng Id.
    private static async Task<EmployeeProfile> SeedProfileAsync(
        IdentityFixture fix, TestApplicationDbContext db, string email, string code, bool isDeleted = false)
    {
        var user = await fix.CreateUserAsync(email, "NV " + code, departmentId: null, roles: Array.Empty<string>());
        var profile = new EmployeeProfile
        {
            Id = Guid.NewGuid(),
            UserId = user.Id,
            EmployeeCode = code,
            EmployeeStatus = EmployeeStatus.Active,
            IsDeleted = isDeleted,
        };
        db.EmployeeProfiles.Add(profile);
        await db.SaveChangesAsync(CancellationToken.None);
        return profile;
    }

    private static TestCurrentUser Actor(Guid userId)
        => new() { UserId = userId, FullName = "Người xoá" };

    // ===================================================================
    // Case 1: FK invariant ALL 5 Create — parent không tồn tại → NotFoundException("EmployeeProfile")
    // Cookie-cutter 5× — mỗi satellite y hệt guard.
    // ===================================================================

    [Fact]
    public async Task CreateWorkHistory_NonexistentParent_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var ghost = Guid.NewGuid();

        var act = async () => await new CreateEmployeeWorkHistoryCommandHandler(db)
            .Handle(new CreateEmployeeWorkHistoryCommand(ghost, "Công ty X"), CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>().WithMessage($"*EmployeeProfile*{ghost}*không tồn tại*");
        (await db.EmployeeWorkHistories.CountAsync()).Should().Be(0);
    }

    [Fact]
    public async Task CreateEducation_NonexistentParent_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var ghost = Guid.NewGuid();

        var act = async () => await new CreateEmployeeEducationCommandHandler(db)
            .Handle(new CreateEmployeeEducationCommand(ghost, "Đại học X"), CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>().WithMessage($"*EmployeeProfile*{ghost}*không tồn tại*");
        (await db.EmployeeEducations.CountAsync()).Should().Be(0);
    }

    [Fact]
    public async Task CreateFamilyRelation_NonexistentParent_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var ghost = Guid.NewGuid();

        var act = async () => await new CreateEmployeeFamilyRelationCommandHandler(db)
            .Handle(new CreateEmployeeFamilyRelationCommand(ghost, "Nguyễn Văn Cha", FamilyRelationKind.Father),
                CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>().WithMessage($"*EmployeeProfile*{ghost}*không tồn tại*");
        (await db.EmployeeFamilyRelations.CountAsync()).Should().Be(0);
    }

    [Fact]
    public async Task CreateSkill_NonexistentParent_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var ghost = Guid.NewGuid();

        var act = async () => await new CreateEmployeeSkillCommandHandler(db)
            .Handle(new CreateEmployeeSkillCommand(ghost, SkillKind.Computer, "AutoCAD"), CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>().WithMessage($"*EmployeeProfile*{ghost}*không tồn tại*");
        (await db.EmployeeSkills.CountAsync()).Should().Be(0);
    }

    [Fact]
    public async Task CreateDocument_NonexistentParent_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var ghost = Guid.NewGuid();

        var act = async () => await new CreateEmployeeDocumentCommandHandler(db)
            .Handle(new CreateEmployeeDocumentCommand(ghost, EmployeeDocumentType.IdCard, "cccd.pdf",
                "store/cccd.pdf", 1024, "application/pdf"), CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>().WithMessage($"*EmployeeProfile*{ghost}*không tồn tại*");
        (await db.EmployeeDocuments.CountAsync()).Should().Be(0);
    }

    // ===================================================================
    // Case 2: Create với parent SOFT-DELETED → NotFoundException (guard !IsDeleted)
    // 1 representative (WorkHistory) đủ — cùng guard.
    // ===================================================================

    [Fact]
    public async Task CreateWorkHistory_SoftDeletedParent_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var parent = await SeedProfileAsync(fix, db, "softdel@test.local", "NV/2026/8001", isDeleted: true);

        var act = async () => await new CreateEmployeeWorkHistoryCommandHandler(db)
            .Handle(new CreateEmployeeWorkHistoryCommand(parent.Id, "Công ty X"), CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>()
            .WithMessage($"*EmployeeProfile*{parent.Id}*không tồn tại*");
        (await db.EmployeeWorkHistories.CountAsync()).Should().Be(0,
            "parent soft-deleted bị loại bởi !IsDeleted → coi như không tồn tại");
    }

    // ===================================================================
    // Case 3: Create happy path — persisted, EmployeeProfileId đúng. 1 representative.
    // ===================================================================

    [Fact]
    public async Task CreateWorkHistory_ValidParent_Persists_WithCorrectFk()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var parent = await SeedProfileAsync(fix, db, "ok@test.local", "NV/2026/8002");

        var id = await new CreateEmployeeWorkHistoryCommandHandler(db)
            .Handle(new CreateEmployeeWorkHistoryCommand(parent.Id, "Công ty TNHH ABC",
                JobTitle: "Kỹ sư"), CancellationToken.None);

        id.Should().NotBeEmpty();
        var entity = await db.EmployeeWorkHistories.AsNoTracking().SingleAsync(x => x.Id == id);
        entity.EmployeeProfileId.Should().Be(parent.Id);
        entity.CompanyName.Should().Be("Công ty TNHH ABC");
        entity.JobTitle.Should().Be("Kỹ sư");
        entity.IsDeleted.Should().BeFalse();
    }

    // ===================================================================
    // Case 4: Delete satellite → soft (IsDeleted=true + DeletedBy). Update/Delete lại → NotFound.
    // 1 representative (WorkHistory).
    // ===================================================================

    [Fact]
    public async Task DeleteWorkHistory_SoftDeletes_SetsDeletedBy_ThenExcludedFromReoperate()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var parent = await SeedProfileAsync(fix, db, "del@test.local", "NV/2026/8003");
        var actor = await fix.CreateUserAsync("admin-del@test.local", "Quản trị", null, new[] { "Admin" });

        var satId = await new CreateEmployeeWorkHistoryCommandHandler(db)
            .Handle(new CreateEmployeeWorkHistoryCommand(parent.Id, "Công ty cũ"), CancellationToken.None);

        // Delete → soft.
        await new DeleteEmployeeWorkHistoryCommandHandler(db, Actor(actor.Id))
            .Handle(new DeleteEmployeeWorkHistoryCommand(satId), CancellationToken.None);

        var soft = await db.EmployeeWorkHistories.AsNoTracking().SingleAsync(x => x.Id == satId);
        soft.IsDeleted.Should().BeTrue("soft-delete set flag, KHÔNG physical remove");
        soft.DeletedBy.Should().Be(actor.Id, "DeletedBy = currentUser.UserId");
        soft.DeletedAt.Should().NotBeNull();

        // Update lại Id đã soft-delete → NotFound (excluded bởi !IsDeleted).
        var updateAct = async () => await new UpdateEmployeeWorkHistoryCommandHandler(db)
            .Handle(new UpdateEmployeeWorkHistoryCommand(satId, "Đổi tên", null, null, null, null, null, null, null),
                CancellationToken.None);
        await updateAct.Should().ThrowAsync<NotFoundException>()
            .WithMessage($"*EmployeeWorkHistory*{satId}*không tồn tại*");

        // Delete lại → NotFound.
        var deleteAgain = async () => await new DeleteEmployeeWorkHistoryCommandHandler(db, Actor(actor.Id))
            .Handle(new DeleteEmployeeWorkHistoryCommand(satId), CancellationToken.None);
        await deleteAgain.Should().ThrowAsync<NotFoundException>()
            .WithMessage($"*EmployeeWorkHistory*{satId}*không tồn tại*");
    }

    // ===================================================================
    // Case 5 ⭐: DeleteEmployeeProfile (soft) KHÔNG cascade satellite.
    // Behavior HIỆN TẠI: handler chỉ soft-delete parent, KHÔNG loop children.
    // EF FK Cascade chỉ fire ở HARD delete; app dùng soft-delete → child KHÔNG bị set IsDeleted.
    // (Nếu yêu cầu business là cascade → đây là gap cần REPORT, KHÔNG sửa trong test.)
    // ===================================================================

    [Fact]
    public async Task DeleteEmployeeProfile_SoftDelete_DoesNotCascadeSoftDeleteSatellites()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var parent = await SeedProfileAsync(fix, db, "parent-cascade@test.local", "NV/2026/8004");
        var actor = await fix.CreateUserAsync("admin-cascade@test.local", "Quản trị", null, new[] { "Admin" });

        var satId = await new CreateEmployeeWorkHistoryCommandHandler(db)
            .Handle(new CreateEmployeeWorkHistoryCommand(parent.Id, "Công ty của NV"), CancellationToken.None);

        // Soft-delete parent qua handler thật.
        await new DeleteEmployeeProfileCommandHandler(db, Actor(actor.Id))
            .Handle(new DeleteEmployeeProfileCommand(parent.Id), CancellationToken.None);

        var parentReloaded = await db.EmployeeProfiles.AsNoTracking().SingleAsync(x => x.Id == parent.Id);
        parentReloaded.IsDeleted.Should().BeTrue("parent đã soft-delete");

        // Satellite VẪN active — app-layer KHÔNG cascade soft-delete.
        var sat = await db.EmployeeWorkHistories.AsNoTracking().SingleAsync(x => x.Id == satId);
        sat.IsDeleted.Should().BeFalse(
            "behavior hiện tại: soft-delete parent KHÔNG loop children — EF cascade chỉ fire ở hard-delete");
    }

    // ===================================================================
    // Case 6: EF model cascade config assertion — lock schema intent (Pattern-10-style cho EF model).
    // ===================================================================

    [Fact]
    public void EmployeeWorkHistory_ForeignKeyToProfile_IsCascadeDeleteBehavior()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        var fk = db.Model.FindEntityType(typeof(EmployeeWorkHistory))!
            .GetForeignKeys()
            .Single(f => f.PrincipalEntityType.ClrType == typeof(EmployeeProfile));

        fk.DeleteBehavior.Should().Be(DeleteBehavior.Cascade,
            "schema intent: hard-delete EmployeeProfile cascade xoá satellite (EmployeeWorkHistoryConfiguration). " +
            "Lưu ý app-layer dùng soft-delete nên cascade này KHÔNG fire runtime (xem Case 5).");
    }
}
