using FluentValidation;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Master;
using SolutionErp.Domain.Master.Catalogs;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Services;
using SolutionErp.Infrastructure.Tests.Common;
using SolutionErp.Infrastructure.Tests.Services;   // NoOpNotificationService (reuse internal helper)

namespace SolutionErp.Infrastructure.Tests.Application;

// [Mig 49 S57bis] PE gắn WorkItemId (hạng mục công việc) — loose-Guid scalar (KHÔNG
// FK vật lý, KHÔNG navigation — convention PE giống ProjectId/SelectedSupplierId).
// Test guard 3 trục theo CODE đã land (single source of truth):
//   1. CreatePurchaseEvaluationCommandValidator — RuleFor(WorkItemId).NotEmpty()
//   2. CreatePurchaseEvaluationCommandHandler — FK-invariant guard
//      db.WorkItems.AnyAsync(w.Id==x && w.IsActive) → ConflictException
//   3. UpdatePurchaseEvaluationDraftCommandHandler — null-safe partial update
//      (request.WorkItemId is null → GIỮ entity.WorkItemId; bug-class S42 picker
//      null-hoá từ client cũ / inline-edit không gửi field).
//
// Create handler 4 dep (db + ICurrentUser + workflow svc + codeGen) instantiate
// thật trên SQLite (codeGen Serializable-tx = non-issue SQLite — proven S52).
// UpdateDraft handler chỉ 2 dep (db + ICurrentUser) → nhẹ.
public class PeWorkItemGuardTests
{
    private sealed class FakeCurrentUser : ICurrentUser
    {
        public Guid? UserId { get; init; } = Guid.NewGuid();
        public string? Email { get; init; } = "drafter@test.local";
        public string? FullName { get; init; } = "Drafter Test";
        public IReadOnlyList<string> Roles { get; init; } = new[] { AppRoles.Drafter };
        public bool IsAuthenticated => UserId is not null;
    }

    // Build full Create handler stack từ IdentityFixture (db + um cho workflow svc).
    private static CreatePurchaseEvaluationCommandHandler BuildCreateHandler(
        TestApplicationDbContext db, UserManager<User> um, ICurrentUser currentUser)
    {
        var dt = new FixedDateTime(new DateTime(2026, 6, 11, 0, 0, 0, DateTimeKind.Utc));
        var notify = new NoOpNotificationService();
        var workflow = new PurchaseEvaluationWorkflowService(db, dt, notify, um);
        var codeGen = new PurchaseEvaluationCodeGenerator(db, dt);
        return new CreatePurchaseEvaluationCommandHandler(db, currentUser, workflow, codeGen);
    }

    private static async Task<Project> SeedProjectAsync(TestApplicationDbContext db)
    {
        var p = new Project { Id = Guid.NewGuid(), Code = "PRJ-WI", Name = "Dự án test WorkItem" };
        db.Projects.Add(p);
        await db.SaveChangesAsync(CancellationToken.None);
        return p;
    }

    private static async Task<WorkItem> SeedWorkItemAsync(
        TestApplicationDbContext db, string code, bool isActive = true)
    {
        var wi = new WorkItem
        {
            Id = Guid.NewGuid(),
            Code = code,
            Name = "Hạng mục " + code,
            IsActive = isActive,
        };
        db.WorkItems.Add(wi);
        await db.SaveChangesAsync(CancellationToken.None);
        return wi;
    }

    private static CreatePurchaseEvaluationCommand BuildCreateCommand(
        Guid projectId, Guid? workItemId, Guid? approvalWorkflowId = null)
        => new(
            Type: PurchaseEvaluationType.DuyetNcc,
            TenGoiThau: "Gói thầu test",
            ProjectId: projectId,
            DepartmentId: null,
            DiaDiem: null,
            MoTa: null,
            PaymentTerms: null,
            BudgetPeriodAmount: null,
            ApprovalWorkflowId: approvalWorkflowId,
            WorkItemId: workItemId);

    // ============================================================
    // 1. VALIDATOR — RuleFor(WorkItemId).NotEmpty()
    // ============================================================

    [Fact]
    public void Validator_WorkItemIdNull_IsInvalid_WithErrorOnWorkItemId()
    {
        var validator = new CreatePurchaseEvaluationCommandValidator();
        var cmd = BuildCreateCommand(Guid.NewGuid(), workItemId: null);

        var result = validator.Validate(cmd);

        result.IsValid.Should().BeFalse();
        result.Errors.Should().Contain(e => e.PropertyName == nameof(CreatePurchaseEvaluationCommand.WorkItemId));
    }

    [Fact]
    public void Validator_WorkItemIdEmptyGuid_PassesValidator_ButHandlerFkGuardCatches()
    {
        // ⚠️ SPEC-DRIFT NOTE (test theo CODE — S34 rule): WorkItemId là `Guid?`
        // (nullable). FluentValidation 7.2 `NotEmpty()` trên nullable-Guid chỉ check
        // != default(Guid?) == null — KHÔNG check Guid.Empty. Nên Guid.Empty (non-null)
        // PASS validator. Đây KHÔNG phải lỗ hổng thực tế: create handler FK-guard
        // `db.WorkItems.AnyAsync(w.Id==Guid.Empty && w.IsActive)` luôn false → throw
        // ConflictException. Defense-in-depth: validator chặn null, handler chặn
        // bogus/empty/inactive. Test này LOCK behavior hiện tại để nếu ai đổi sang
        // non-nullable Guid hoặc thêm GuidExtensions.NotEmpty → đỏ → review chủ đích.
        var validator = new CreatePurchaseEvaluationCommandValidator();
        var cmd = BuildCreateCommand(Guid.NewGuid(), workItemId: Guid.Empty);

        var result = validator.Validate(cmd);

        result.Errors.Should().NotContain(
            e => e.PropertyName == nameof(CreatePurchaseEvaluationCommand.WorkItemId),
            "NotEmpty() trên Guid? chỉ bắt null, KHÔNG bắt Guid.Empty — handler FK-guard mới chặn");
    }

    [Fact]
    public void Validator_WorkItemIdPresent_NoErrorOnWorkItemId()
    {
        // Chỉ assert rule WorkItemId pass — command còn lại đã hợp lệ ở BuildCreateCommand.
        // [S83] +ApprovalWorkflowId (cũng NotEmpty từ S83) để command FULL-valid → IsValid=true.
        var validator = new CreatePurchaseEvaluationCommandValidator();
        var cmd = BuildCreateCommand(Guid.NewGuid(), workItemId: Guid.NewGuid(), approvalWorkflowId: Guid.NewGuid());

        var result = validator.Validate(cmd);

        result.Errors.Should().NotContain(e => e.PropertyName == nameof(CreatePurchaseEvaluationCommand.WorkItemId));
        result.IsValid.Should().BeTrue();
    }

    // ============================================================
    // 1b. VALIDATOR — RuleFor(ApprovalWorkflowId).NotEmpty() [S83 bug fix]
    // ============================================================
    // Đóng lỗ hổng validate FE-only: phiếu null-workflow lọt vào ChoDuyet = kẹt nhánh
    // V1-legacy ("quy trình cũ", không Bước/Cấp, không route duyệt). FE canSubmit bắt
    // buộc chọn quy trình lúc create → BE validator phải mirror (defense-in-depth).

    [Fact]
    public void Validator_ApprovalWorkflowIdNull_IsInvalid_WithErrorOnWorkflow()
    {
        var validator = new CreatePurchaseEvaluationCommandValidator();
        var cmd = BuildCreateCommand(Guid.NewGuid(), workItemId: Guid.NewGuid(), approvalWorkflowId: null);

        var result = validator.Validate(cmd);

        result.IsValid.Should().BeFalse();
        result.Errors.Should().Contain(
            e => e.PropertyName == nameof(CreatePurchaseEvaluationCommand.ApprovalWorkflowId));
    }

    [Fact]
    public void Validator_ApprovalWorkflowIdPresent_NoErrorOnWorkflow()
    {
        var validator = new CreatePurchaseEvaluationCommandValidator();
        var cmd = BuildCreateCommand(Guid.NewGuid(), workItemId: Guid.NewGuid(), approvalWorkflowId: Guid.NewGuid());

        var result = validator.Validate(cmd);

        result.Errors.Should().NotContain(
            e => e.PropertyName == nameof(CreatePurchaseEvaluationCommand.ApprovalWorkflowId));
    }

    // ============================================================
    // 2. CREATE HANDLER — FK-invariant guard
    // ============================================================

    [Fact]
    public async Task Create_WorkItemBogusGuid_ThrowsConflict()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var project = await SeedProjectAsync(db);
        var handler = BuildCreateHandler(db, um, new FakeCurrentUser());

        // WorkItemId không tồn tại trong WorkItems → guard fail.
        var cmd = BuildCreateCommand(project.Id, workItemId: Guid.NewGuid());

        var act = async () => await handler.Handle(cmd, CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Hạng mục công việc không tồn tại hoặc ngưng hoạt động*");
    }

    [Fact]
    public async Task Create_WorkItemEmptyGuid_ThrowsConflict_HandlerDefenseInDepth()
    {
        // Chứng minh claim ở Validator_WorkItemIdEmptyGuid_PassesValidator: validator
        // cho Guid.Empty qua, nhưng handler FK-guard (`is Guid wiId` true cho Empty +
        // AnyAsync false) chặn → ConflictException. Defense-in-depth thực sự hoạt động.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var project = await SeedProjectAsync(db);
        var handler = BuildCreateHandler(db, um, new FakeCurrentUser());

        var cmd = BuildCreateCommand(project.Id, workItemId: Guid.Empty);

        var act = async () => await handler.Handle(cmd, CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Hạng mục công việc không tồn tại hoặc ngưng hoạt động*");
    }

    [Fact]
    public async Task Create_WorkItemInactive_ThrowsConflict()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var project = await SeedProjectAsync(db);
        var inactive = await SeedWorkItemAsync(db, "WI-INACTIVE", isActive: false);
        var handler = BuildCreateHandler(db, um, new FakeCurrentUser());

        // WorkItem tồn tại nhưng IsActive=false → guard (w.IsActive) loại → Conflict.
        var cmd = BuildCreateCommand(project.Id, workItemId: inactive.Id);

        var act = async () => await handler.Handle(cmd, CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Hạng mục công việc không tồn tại hoặc ngưng hoạt động*");
    }

    [Fact]
    public async Task Create_WorkItemActive_Succeeds_AndPersistsWorkItemId()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var project = await SeedProjectAsync(db);
        var active = await SeedWorkItemAsync(db, "WI-ACTIVE", isActive: true);
        var handler = BuildCreateHandler(db, um, new FakeCurrentUser());

        var cmd = BuildCreateCommand(project.Id, workItemId: active.Id);

        var newId = await handler.Handle(cmd, CancellationToken.None);

        var saved = await db.PurchaseEvaluations.FindAsync(newId);
        saved.Should().NotBeNull();
        saved!.WorkItemId.Should().Be(active.Id, "create persist đúng WorkItemId đã chọn");
        saved.Phase.Should().Be(PurchaseEvaluationPhase.DangSoanThao);
    }

    // ============================================================
    // 3. UPDATE DRAFT — null-safe partial update (bug-class S42 picker)
    // ============================================================

    private static UpdatePurchaseEvaluationDraftCommand BuildUpdateCommand(
        Guid peId, Guid? workItemId)
        => new(
            Id: peId,
            TenGoiThau: "Gói thầu sửa",
            DiaDiem: null,
            MoTa: null,
            PaymentTerms: null,
            BudgetPeriodAmount: null,
            ApprovalWorkflowId: null,
            WorkItemId: workItemId);

    private static UpdatePurchaseEvaluationDraftCommandHandler BuildUpdateHandler(
        TestApplicationDbContext db, ICurrentUser currentUser)
        => new(db, currentUser);

    // Phiếu Nháp có sẵn WorkItemId = w1 (state sau Create).
    private static async Task<PurchaseEvaluation> SeedDraftPeAsync(
        TestApplicationDbContext db, Guid projectId, Guid workItemId, string code = "PE-WI-UPD")
    {
        var pe = new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.DangSoanThao,
            MaPhieu = code,
            TenGoiThau = "Gói thầu gốc",
            ProjectId = projectId,
            WorkItemId = workItemId,
            DrafterUserId = Guid.NewGuid(),
        };
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);
        return pe;
    }

    [Fact]
    public async Task UpdateDraft_WorkItemIdNull_KeepsExistingW1_NotNullified()
    {
        // ⭐ Trục QUAN TRỌNG NHẤT (bug-class S42): client cũ / inline-edit KHÔNG gửi
        // WorkItemId → request.WorkItemId=null → handler GIỮ NGUYÊN w1, KHÔNG null-hoá.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var project = await SeedProjectAsync(db);
        var w1 = await SeedWorkItemAsync(db, "WI-1");
        var pe = await SeedDraftPeAsync(db, project.Id, w1.Id);
        var handler = BuildUpdateHandler(db, new FakeCurrentUser());

        var cmd = BuildUpdateCommand(pe.Id, workItemId: null);
        await handler.Handle(cmd, CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.FindAsync(pe.Id);
        reloaded!.WorkItemId.Should().Be(w1.Id, "null request KHÔNG được null-hoá hạng mục đã chọn");
        reloaded.TenGoiThau.Should().Be("Gói thầu sửa", "field khác vẫn update bình thường");
    }

    [Fact]
    public async Task UpdateDraft_WorkItemIdNewActiveW2_ChangesToW2()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var project = await SeedProjectAsync(db);
        var w1 = await SeedWorkItemAsync(db, "WI-1");
        var w2 = await SeedWorkItemAsync(db, "WI-2");
        var pe = await SeedDraftPeAsync(db, project.Id, w1.Id);
        var handler = BuildUpdateHandler(db, new FakeCurrentUser());

        var cmd = BuildUpdateCommand(pe.Id, workItemId: w2.Id);
        await handler.Handle(cmd, CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.FindAsync(pe.Id);
        reloaded!.WorkItemId.Should().Be(w2.Id, "đổi sang hạng mục W2 active hợp lệ");
    }

    [Fact]
    public async Task UpdateDraft_WorkItemIdBogus_ThrowsConflict_AndKeepsW1()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var project = await SeedProjectAsync(db);
        var w1 = await SeedWorkItemAsync(db, "WI-1");
        var pe = await SeedDraftPeAsync(db, project.Id, w1.Id);
        var handler = BuildUpdateHandler(db, new FakeCurrentUser());

        // Đổi sang Guid không tồn tại → guard (wiId != entity.WorkItemId) fail → Conflict.
        var cmd = BuildUpdateCommand(pe.Id, workItemId: Guid.NewGuid());

        var act = async () => await handler.Handle(cmd, CancellationToken.None);
        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Hạng mục công việc không tồn tại hoặc ngưng hoạt động*");

        // Side-effect assert: throw TRƯỚC SaveChanges → entity row giữ nguyên w1.
        // Re-read AsNoTracking để tránh đọc instance đã bị mutate trong tracker
        // (defensive — guard throw trước khi gán nên thực tế chưa mutate, nhưng
        // AsNoTracking đảm bảo verify DB-truth không phải change-tracker state).
        var reloaded = await db.PurchaseEvaluations.AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == pe.Id);
        reloaded!.WorkItemId.Should().Be(w1.Id, "bogus WorkItemId không được persist, giữ w1");
    }

    [Fact]
    public async Task UpdateDraft_WorkItemIdInactive_ThrowsConflict()
    {
        // WorkItem tồn tại nhưng IsActive=false (vd master bị ngưng sau khi pick) → đổi
        // sang nó cũng bị guard (w.IsActive) chặn.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var project = await SeedProjectAsync(db);
        var w1 = await SeedWorkItemAsync(db, "WI-1");
        var inactive = await SeedWorkItemAsync(db, "WI-INACTIVE-UPD", isActive: false);
        var pe = await SeedDraftPeAsync(db, project.Id, w1.Id);
        var handler = BuildUpdateHandler(db, new FakeCurrentUser());

        var cmd = BuildUpdateCommand(pe.Id, workItemId: inactive.Id);

        var act = async () => await handler.Handle(cmd, CancellationToken.None);
        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Hạng mục công việc không tồn tại hoặc ngưng hoạt động*");
    }

    [Fact]
    public async Task UpdateDraft_WorkItemIdSameAsExisting_NoGuardLookup_Succeeds()
    {
        // request.WorkItemId == entity.WorkItemId (w1) → guard (wiId != entity.WorkItemId)
        // SKIP lookup → vẫn success kể cả nếu w1 sau đó bị inactive (no re-validate khi
        // không đổi). Giữ w1.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var project = await SeedProjectAsync(db);
        var w1 = await SeedWorkItemAsync(db, "WI-1");
        var pe = await SeedDraftPeAsync(db, project.Id, w1.Id);
        var handler = BuildUpdateHandler(db, new FakeCurrentUser());

        var cmd = BuildUpdateCommand(pe.Id, workItemId: w1.Id);
        await handler.Handle(cmd, CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.FindAsync(pe.Id);
        reloaded!.WorkItemId.Should().Be(w1.Id);
    }
}
