using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Notifications;
using SolutionErp.Domain.Common;
using SolutionErp.Domain.Contracts;   // ApprovalDecision enum (shared HĐ/PE)
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Notifications;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Services;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Services;

// Regression test for Session 21 turn 3 bug — gotcha #45:
// FE button "← Trả lại" trong PeWorkflowPanel gửi `decision: 1` (Approve) thay
// vì `2` (Reject) khi target = TraLai (98). Root: `isReject` local variable
// trong FE thiếu nhánh TraLai → payload mismatch giữa button label hiển thị
// "Trả lại" và decision gửi BE.
//
// Hiệu ứng cũ trước fix: BE TransitionAsync nhận decision=Approve → skip Reject
// branch (L51) → enter APPROVE STEP branch (L97) → ApproveV2Async UPSERT
// opinion đánh dấu "đã duyệt" cho NV đang nhấn nút → tiến qua Cấp tiếp theo.
// User UAT thấy: "Trả về nhưng hệ thống vẫn duyệt".
//
// Fix BE defense-in-depth: guard early throw ConflictException khi targetPhase
// ∈ {TraLai, TuChoi} mà decision != Reject — chặn FE inconsistency tại
// boundary BE thay vì depend FE đúng.
//
// FE fix song song trong fe-admin + fe-user PeWorkflowPanel.tsx (rule §3.9
// mirror 2 app) — sync `isReject` + dialog `isSendBack` include TraLai.
public class PurchaseEvaluationWorkflowServiceGuardTests
{
    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, 12, 0, 0, 0, DateTimeKind.Utc));
        var notify = new NoOpNotificationService();
        var svc = new PurchaseEvaluationWorkflowService(db, dt, notify, um);
        return (svc, fix, db);
    }

    private static PurchaseEvaluation BuildPeInChoDuyet(string code = "PE-GUARD-001")
    {
        return new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = code,
            TenGoiThau = "Test guard bug Trả lại",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = Guid.NewGuid(),
            CurrentApprovalLevelOrder = 1,
            CurrentWorkflowStepIndex = 0,
        };
    }

    [Fact]
    public async Task TransitionAsync_TargetTraLai_WithApproveDecision_Throws_AndDoesNotMutateState()
    {
        // Arrange: phiếu PE ở ChoDuyet (typical intermediate state khi approver duyệt)
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var pe = BuildPeInChoDuyet();
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            // Act: simulate FE bug payload — button "← Trả lại" gửi decision=Approve
            // thay vì Reject (gotcha #45 root cause).
            var act = async () => await svc.TransitionAsync(
                evaluation: pe,
                targetPhase: PurchaseEvaluationPhase.TraLai,
                actorUserId: Guid.NewGuid(),
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "test guard mismatch",
                ct: CancellationToken.None);

            // Assert: BE chặn payload mismatch sớm + state phiếu KHÔNG đổi
            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*TraLai*Reject*");

            pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet,
                "Guard chặn trước khi mutate phase");
            pe.CurrentApprovalLevelOrder.Should().Be(1,
                "Guard chặn trước khi advance level pointer");
        }
    }

    [Fact]
    public async Task TransitionAsync_TargetTuChoi_WithApproveDecision_Throws_AndDoesNotMutateState()
    {
        // Tương tự TraLai — TuChoi cũng BẮT BUỘC decision=Reject. Defense
        // double-cover invariant (FE chỉ bug TraLai branch nhưng guard nên cover
        // luôn TuChoi cho consistency).
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var pe = BuildPeInChoDuyet("PE-GUARD-002");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                evaluation: pe,
                targetPhase: PurchaseEvaluationPhase.TuChoi,
                actorUserId: Guid.NewGuid(),
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "test guard tu choi",
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*TuChoi*Reject*");

            pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet);
        }
    }

    [Fact]
    public async Task TransitionAsync_TargetTuChoi_WithRejectDecision_Throws_TuChoiRemoved_S60()
    {
        // UAT S60 (anh Kiệt 14:14): "bỏ luôn nút Từ chối — Duyệt hoặc Trả về
        // thôi". Payload TuChoi + decision=Reject (hợp lệ THEO SPEC CŨ) giờ bị
        // guard S60 chặn hẳn — TuChoi hết đường vào từ transition API (guard
        // #45 decision-mismatch vẫn đứng trước cover nhánh TuChoi+Approve).
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var pe = BuildPeInChoDuyet("PE-GUARD-004");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                evaluation: pe,
                targetPhase: PurchaseEvaluationPhase.TuChoi,
                actorUserId: Guid.NewGuid(),
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "test tu choi removed S60",
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*Từ chối*đã được gỡ*");

            pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet,
                "Guard chặn trước khi mutate — phiếu giữ nguyên ChoDuyet");
        }
    }

    [Fact]
    public async Task TransitionAsync_TargetTraLai_WithRejectDecision_SetsPhaseTraLai()
    {
        // Happy path control test: decision=Reject + target=TraLai → BE đi vào
        // Reject branch (L51), set Phase=TraLai, clear pointer. Verify fix
        // KHÔNG break flow Trả lại đúng.
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var pe = BuildPeInChoDuyet("PE-GUARD-003");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            await svc.TransitionAsync(
                evaluation: pe,
                targetPhase: PurchaseEvaluationPhase.TraLai,
                actorUserId: Guid.NewGuid(),
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "trả lại sửa lại đi",
                ct: CancellationToken.None);

            pe.Phase.Should().Be(PurchaseEvaluationPhase.TraLai,
                "Reject branch set Phase=TraLai");
            pe.CurrentApprovalLevelOrder.Should().BeNull("Trả lại clear level pointer");
            pe.CurrentWorkflowStepIndex.Should().BeNull("Trả lại clear step pointer");
            pe.SlaDeadline.Should().BeNull("Trả lại clear SLA");
        }
    }
}

// Stub: not assert side effects of notify (out-of-scope cho guard test).
// Pattern reuse cho future PE service tests.
internal sealed class NoOpNotificationService : INotificationService
{
    public Task NotifyAsync(
        Guid userId,
        NotificationType type,
        string title,
        string? description = null,
        string? href = null,
        Guid? refId = null,
        CancellationToken ct = default) => Task.CompletedTask;

    public Task NotifyManyAsync(
        IEnumerable<Guid> userIds,
        NotificationType type,
        string title,
        string? description = null,
        string? href = null,
        Guid? refId = null,
        CancellationToken ct = default) => Task.CompletedTask;
}
