using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.PurchaseEvaluations.Services;
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.Contracts;  // ApprovalDecision
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Services;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Services;

// Plan O S23 t5 (2026-05-15) — Regression test cho 4 BE lookup sites cùng
// bug pattern Plan N: Schema Mig 29 OR-of-N (N rows cùng Order) — handler
// FirstOrDefault(Order==X) không discriminate ApproverUserId → lấy row đầu DB.
//
// Bro UAT 2026-05-15 phát hiện sau Plan N deploy: Actor non-row-đầu click
// "Trả lại" → toast "Không phải lượt bạn — chỉ NV Cấp duyệt hiện tại mới
// được Trả lại / Từ chối phiếu" mặc dù actor TRONG slot.
//
// 4 sites fixed Plan O:
//   1. PurchaseEvaluationWorkflowService.cs:201 — EnsureCanRejectV2Async (bug bro UAT 1)
//   2. PurchaseEvaluationWorkflowService.cs:248 — ApplyReturnModeAsync (cùng bug)
//   3. PurchaseEvaluationDetailFeatures.cs:72 — F3 EnsureEditableForDetailsAsync
//   4. PurchaseEvaluationFeatures.cs:311 — F4 AdjustBudgetCommand
//
// Test cover 2 site Service.cs qua TransitionAsync entry point.
// 2 site Features.cs đã có test existing (PurchaseEvaluationDraftGuardTests
// + implicit qua command handler) — không add thêm.
public class PurchaseEvaluationPerNvLookupRegressionTests
{
    private static (PurchaseEvaluationWorkflowService svc, IdentityFixture fix, TestApplicationDbContext db)
        CreateService()
    {
        var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var dt = new FixedDateTime(new DateTime(2026, 5, 15, 12, 0, 0, DateTimeKind.Utc));
        var notify = new NoOpNotificationService();
        var svc = new PurchaseEvaluationWorkflowService(db, dt, notify, um);
        return (svc, fix, db);
    }

    private static async Task<(ApprovalWorkflow wf, Guid lAId, Guid lBId, Guid lCId, Guid lDId)>
        Seed1StepOrOfNAsync(TestApplicationDbContext db,
            Guid uA, Guid uB, Guid uC, Guid uD,
            bool aAllowReturn, bool bAllowReturn, bool cAllowReturn, bool dAllowReturn)
    {
        var wf = new ApprovalWorkflow
        {
            Id = Guid.NewGuid(),
            Code = "QT-O-001",
            Version = 1,
            Name = "Plan O OR-of-N regression",
            ApplicableType = ApprovalWorkflowApplicableType.DuyetNcc,
            IsActive = true,
        };
        var step = new ApprovalWorkflowStep
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowId = wf.Id,
            Order = 1,
            Name = "Bước 1 (4 NV OR-of-N cùng Cấp)",
        };
        var lA = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = uA,
            AllowReturnOneLevel = aAllowReturn,
            AllowReturnToDrafter = true,
        };
        var lB = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = uB,
            AllowReturnOneLevel = bAllowReturn,
            AllowReturnToDrafter = true,
        };
        var lC = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = uC,
            AllowReturnOneLevel = cAllowReturn,
            AllowReturnToDrafter = true,
        };
        var lD = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = uD,
            AllowReturnOneLevel = dAllowReturn,
            AllowReturnToDrafter = true,
        };

        db.ApprovalWorkflows.Add(wf);
        db.ApprovalWorkflowSteps.Add(step);
        db.ApprovalWorkflowLevels.AddRange(lA, lB, lC, lD);
        await db.SaveChangesAsync(CancellationToken.None);
        return (wf, lA.Id, lB.Id, lC.Id, lD.Id);
    }

    private static async Task<PurchaseEvaluation> SeedPeChoDuyetAsync(
        TestApplicationDbContext db, Guid drafterId, Guid workflowId)
    {
        var pe = new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = $"PE-O-{Guid.NewGuid().ToString("N").Substring(0, 6)}",
            TenGoiThau = "Plan O regression",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = drafterId,
            ApprovalWorkflowId = workflowId,
            CurrentWorkflowStepIndex = 0,
            CurrentApprovalLevelOrder = 1,
        };
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);
        return pe;
    }

    // ============================================================
    // Site #1: EnsureCanRejectV2Async (Service.cs:201) — bug bro UAT 1
    // Pre-fix: handler check `currentLevel?.ApproverUserId != actorId` where
    // currentLevel = first row by Order. Non-first-row actor luôn fail check.
    // ============================================================

    [Fact]
    public async Task TransitionReject_ActorD_LastInSlot_AllowsRejectViaDrafterMode()
    {
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var drafter = await fix.CreateUserAsync("drafter-o1@test.local", "Drafter", null, new[] { AppRoles.Drafter });
            var a = await fix.CreateUserAsync("a-o1@test.local", "A", null, new[] { AppRoles.CostControl });
            var b = await fix.CreateUserAsync("b-o1@test.local", "B", null, new[] { AppRoles.CostControl });
            var c = await fix.CreateUserAsync("c-o1@test.local", "C", null, new[] { AppRoles.CostControl });
            var d = await fix.CreateUserAsync("d-o1@test.local", "D", null, new[] { AppRoles.CostControl });
            // All 4 slot allow Drafter mode (default Trả lại)
            var (wf, _, _, _, _) = await Seed1StepOrOfNAsync(db, a.Id, b.Id, c.Id, d.Id, false, false, false, false);
            var pe = await SeedPeChoDuyetAsync(db, drafter.Id, wf.Id);

            // Actor D = NON-first-row in OR-of-N slot.
            // Pre-Plan O bug: handler chỉ check Levels[0]=A → ApproverUserId(A) != actorId(D) → throw "Không phải lượt bạn"
            // Post-Plan O fix: handler filter `Order==X && ApproverUserId==actorId` → match D row → allow.
            var act = async () => await svc.TransitionAsync(
                pe, PurchaseEvaluationPhase.TraLai, d.Id, new[] { AppRoles.CostControl },
                ApprovalDecision.Reject, "Test Plan O Actor D Trả lại");

            await act.Should().NotThrowAsync(
                "Plan O O1+O2 fix: actor D match slot → cho phép Trả lại Drafter mode. " +
                "Pre-fix bug: throw 'Không phải lượt bạn' vì handler chỉ check row đầu DB.");
        }
    }

    [Fact]
    public async Task TransitionReject_Outsider_NotInSlot_ThrowsForbidden()
    {
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var drafter = await fix.CreateUserAsync("drafter-o2@test.local", "Drafter", null, new[] { AppRoles.Drafter });
            var a = await fix.CreateUserAsync("a-o2@test.local", "A", null, new[] { AppRoles.CostControl });
            var b = await fix.CreateUserAsync("b-o2@test.local", "B", null, new[] { AppRoles.CostControl });
            var c = await fix.CreateUserAsync("c-o2@test.local", "C", null, new[] { AppRoles.CostControl });
            var d = await fix.CreateUserAsync("d-o2@test.local", "D", null, new[] { AppRoles.CostControl });
            var outsider = await fix.CreateUserAsync("outsider@test.local", "Outsider", null, new[] { AppRoles.CostControl });
            var (wf, _, _, _, _) = await Seed1StepOrOfNAsync(db, a.Id, b.Id, c.Id, d.Id, false, false, false, false);
            var pe = await SeedPeChoDuyetAsync(db, drafter.Id, wf.Id);

            // Outsider không trong slot OR-of-N → throw đúng intent
            var act = async () => await svc.TransitionAsync(
                pe, PurchaseEvaluationPhase.TraLai, outsider.Id, new[] { AppRoles.CostControl },
                ApprovalDecision.Reject, "Outsider test");

            await act.Should().ThrowAsync<ForbiddenException>()
                .WithMessage("*Không phải lượt bạn*",
                    "Plan O fix: outsider không có trong slot OR-of-N → throw đúng nghiệp vụ. " +
                    "Verify fix KHÔNG over-permissive (cho phép outsider).");
        }
    }

    // ============================================================
    // Site #2: ApplyReturnModeAsync (Service.cs:248) — read Allow flag từ Level slot
    // Pre-fix: currentLevel = first row → flag check dùng row đầu thay vì actor row.
    // ============================================================

    [Fact]
    public async Task TransitionRejectOneLevel_ActorC_HasFlagWhileOthersDont_AllowsMode()
    {
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var drafter = await fix.CreateUserAsync("drafter-o3@test.local", "Drafter", null, new[] { AppRoles.Drafter });
            var a = await fix.CreateUserAsync("a-o3@test.local", "A", null, new[] { AppRoles.CostControl });
            var b = await fix.CreateUserAsync("b-o3@test.local", "B", null, new[] { AppRoles.CostControl });
            var c = await fix.CreateUserAsync("c-o3@test.local", "C", null, new[] { AppRoles.CostControl });
            var d = await fix.CreateUserAsync("d-o3@test.local", "D", null, new[] { AppRoles.CostControl });
            // Only Actor C tick AllowReturnOneLevel — 3 NV khác KHÔNG tick.
            // Bug pre-fix: handler read flag từ row đầu (Actor A KHÔNG tick)
            // → throw ConflictException "Cấp Approver hiện tại không bật mode OneLevel"
            // mặc dù actor C TICK đúng.
            var (wf, _, _, _, _) = await Seed1StepOrOfNAsync(db, a.Id, b.Id, c.Id, d.Id, false, false, true, false);
            var pe = await SeedPeChoDuyetAsync(db, drafter.Id, wf.Id);
            // Set pointer Step 0 Cấp 1 (slot OR-of-N — actor C có flag)
            // Đang ở Cấp 1 Bước 1 → fallback Plan M reset (0,1) giữ ChoDuyet — no throw
            var act = async () => await svc.TransitionAsync(
                pe, PurchaseEvaluationPhase.ChoDuyet, c.Id, new[] { AppRoles.CostControl },
                ApprovalDecision.Reject, "Actor C Trả lại 1 Cấp",
                returnMode: WorkflowReturnMode.OneLevel);

            await act.Should().NotThrowAsync(
                "Plan O O2 fix: actor C match slot + có flag OneLevel → mode allowed. " +
                "Pre-fix bug: read flag từ row đầu (A=false) → throw 'không bật mode OneLevel'.");
        }
    }
}
