using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations;
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Application;

// Plan C task 3 catch-up cho S21 t4-t5 helper `EnsureEditableForDetailsAsync`:
// F3 (Mig 28 → Mig 29) — gating Section 2 (Detail + NCC + Báo giá) edit access.
// 3 scenario decision branch:
//   1. Drafter scope: Phase ∈ {DangSoanThao, TraLai} → accept any caller
//   2. F3 Approver scope: Phase=ChoDuyet + level.AllowApproverEditDetails=true
//      + actor.Id == level.ApproverUserId → accept
//   3. Admin bypass: Phase=ChoDuyet + role contains Admin → accept (skip flag check)
// Otherwise throw ConflictException / ForbiddenException.
public class PurchaseEvaluationDraftGuardTests
{
    private sealed class FakeCurrentUser : ICurrentUser
    {
        public Guid? UserId { get; init; }
        public string? Email { get; init; }
        public string? FullName { get; init; }
        public IReadOnlyList<string> Roles { get; init; } = Array.Empty<string>();
        public bool IsAuthenticated => UserId is not null;
    }

    private static async Task<(ApprovalWorkflow wf, ApprovalWorkflowStep step, ApprovalWorkflowLevel level)>
        SeedWorkflowAsync(TestApplicationDbContext db, Guid approverUserId, bool allowApproverEdit = false)
    {
        var wf = new ApprovalWorkflow
        {
            Id = Guid.NewGuid(),
            Code = "QT-GUARD-001",
            Version = 1,
            Name = "Guard test workflow",
            ApplicableType = ApprovalWorkflowApplicableType.DuyetNcc,
            IsActive = true,
            IsUserSelectable = true,
        };
        var step = new ApprovalWorkflowStep
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowId = wf.Id,
            Order = 1,
            DepartmentId = null,
            Name = "Bước 1",
        };
        var level = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = approverUserId,
            AllowApproverEditDetails = allowApproverEdit,
        };
        db.ApprovalWorkflows.Add(wf);
        db.ApprovalWorkflowSteps.Add(step);
        db.ApprovalWorkflowLevels.Add(level);
        await db.SaveChangesAsync(CancellationToken.None);
        return (wf, step, level);
    }

    private static PurchaseEvaluation BuildPe(
        PurchaseEvaluationPhase phase,
        Guid? workflowId = null,
        int? stepIdx = null,
        int? levelOrder = null,
        string code = "PE-G-001")
    {
        return new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = phase,
            MaPhieu = code,
            TenGoiThau = "Test guard",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = Guid.NewGuid(),
            ApprovalWorkflowId = workflowId,
            CurrentWorkflowStepIndex = stepIdx,
            CurrentApprovalLevelOrder = levelOrder,
        };
    }

    // ===== Scenario 1: Drafter scope =====

    [Fact]
    public async Task DraftScope_DangSoanThao_AnyCaller_ReturnsPe()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var pe = BuildPe(PurchaseEvaluationPhase.DangSoanThao);
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var anyCaller = new FakeCurrentUser { UserId = Guid.NewGuid(), Roles = new[] { AppRoles.Drafter } };

        var result = await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, anyCaller, CancellationToken.None);

        result.Id.Should().Be(pe.Id);
    }

    [Fact]
    public async Task DraftScope_TraLai_AnyCaller_ReturnsPe()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var pe = BuildPe(PurchaseEvaluationPhase.TraLai, code: "PE-G-002");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var anyCaller = new FakeCurrentUser { UserId = Guid.NewGuid(), Roles = new[] { AppRoles.Drafter } };

        var result = await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, anyCaller, CancellationToken.None);

        result.Id.Should().Be(pe.Id);
    }

    // ===== Scenario 2: F3 Approver scope =====

    [Fact]
    public async Task ApproverScope_ChoDuyet_FlagOn_ActorMatches_ReturnsPe()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var approverUser = await fix.CreateUserAsync("approver-g1@test.local", "Approver G1", departmentId: null, roles: new[] { AppRoles.CostControl });
        var (wf, _, _) = await SeedWorkflowAsync(db, approverUser.Id, allowApproverEdit: true);
        var pe = BuildPe(PurchaseEvaluationPhase.ChoDuyet, wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-G-003");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var approver = new FakeCurrentUser { UserId = approverUser.Id, Roles = new[] { AppRoles.CostControl } };

        var result = await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, approver, CancellationToken.None);

        result.Id.Should().Be(pe.Id);
    }

    [Fact]
    public async Task ApproverScope_ChoDuyet_FlagOff_NonAdmin_Throws()
    {
        // F3 disabled trên Level slot → throw ConflictException.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var approverUser = await fix.CreateUserAsync("approver-g2@test.local", "Approver G2", departmentId: null, roles: new[] { AppRoles.CostControl });
        var (wf, _, _) = await SeedWorkflowAsync(db, approverUser.Id, allowApproverEdit: false);
        var pe = BuildPe(PurchaseEvaluationPhase.ChoDuyet, wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-G-004");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var approver = new FakeCurrentUser { UserId = approverUser.Id, Roles = new[] { AppRoles.CostControl } };

        var act = async () => await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, approver, CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*không được cấp quyền chỉnh sửa Section 2*");
    }

    [Fact]
    public async Task ApproverScope_ChoDuyet_FlagOn_ActorMismatch_ThrowsForbidden()
    {
        // Khác user gọi (không phải slot ApproverUserId) → Forbidden.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var approverUser = await fix.CreateUserAsync("approver-g3@test.local", "Approver G3", departmentId: null, roles: new[] { AppRoles.CostControl });
        var (wf, _, _) = await SeedWorkflowAsync(db, approverUser.Id, allowApproverEdit: true);
        var pe = BuildPe(PurchaseEvaluationPhase.ChoDuyet, wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-G-005");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var someoneElse = new FakeCurrentUser { UserId = Guid.NewGuid(), Roles = new[] { AppRoles.CostControl } };

        var act = async () => await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, someoneElse, CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*Chỉ NV phụ trách*");
    }

    // ===== Scenario 3: Admin bypass =====

    [Fact]
    public async Task AdminBypass_ChoDuyet_FlagOff_ReturnsPe()
    {
        // Admin role bypass Allow* flag check + actor match — vẫn được edit.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var approverUser = await fix.CreateUserAsync("approver-g4@test.local", "Approver G4", departmentId: null, roles: new[] { AppRoles.CostControl });
        var (wf, _, _) = await SeedWorkflowAsync(db, approverUser.Id, allowApproverEdit: false);
        var pe = BuildPe(PurchaseEvaluationPhase.ChoDuyet, wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-G-006");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var admin = new FakeCurrentUser { UserId = Guid.NewGuid(), Roles = new[] { AppRoles.Admin } };

        var result = await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, admin, CancellationToken.None);

        result.Id.Should().Be(pe.Id);
    }

    // ===== Other phase locked =====

    [Fact]
    public async Task DaDuyet_AnyCaller_Throws()
    {
        // Phase terminal (DaDuyet/TuChoi/DaPhatHanh) — không ai edit được, kể cả admin trừ khi
        // tạo helper bypass riêng. Hiện EnsureEditable không có admin bypass cho non-ChoDuyet.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var pe = BuildPe(PurchaseEvaluationPhase.DaDuyet, code: "PE-G-007");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var admin = new FakeCurrentUser { UserId = Guid.NewGuid(), Roles = new[] { AppRoles.Admin } };

        var act = async () => await PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync(
            db, pe.Id, admin, CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Phase=DaDuyet*");
    }
}
