using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Notifications;
using SolutionErp.Domain.Common;
using SolutionErp.Domain.Contracts;
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;

// Tests cho 2-stage department approval logic ở PurchaseEvaluationWorkflowService.
// Cover bug fix anh Kiệt: NV.PRO duyệt phase ChoPurchasing → BLOCK transition.
// TPB.PRO confirm → ALLOW transition.
//
// Pattern: dùng IdentityFixture (Identity stack + DbContext SQLite) để
// test thật end-to-end service thay vì mock.
public class PeTwoStageApprovalTests : IClassFixture<IdentityFixture>
{
    private readonly IdentityFixture _fx;
    private readonly TestApplicationDbContext _db;
    private readonly UserManager<User> _userManager;
    private readonly PurchaseEvaluationWorkflowService _service;
    private readonly Guid _deptPro;
    private readonly Guid _deptCcm;

    public PeTwoStageApprovalTests(IdentityFixture fx)
    {
        _fx = fx;
        _db = fx.Services.GetRequiredService<TestApplicationDbContext>();
        _userManager = fx.Services.GetRequiredService<UserManager<User>>();

        // Seed 2 departments (idempotent — check trước khi insert vì fixture
        // shared across tests trong class).
        _deptPro = SeedDept("PRO", "Phòng Cung ứng");
        _deptCcm = SeedDept("CCM", "Phòng Kiểm soát chi phí");

        var clock = new FixedDateTime(new DateTime(2026, 5, 4, 10, 0, 0, DateTimeKind.Utc));
        var fakeNotifications = new FakeNotificationService();

        _service = new PurchaseEvaluationWorkflowService(
            _db,
            clock,
            fakeNotifications,
            _userManager);
    }

    private Guid SeedDept(string code, string name)
    {
        var existing = _db.Departments.FirstOrDefault(d => d.Code == code);
        if (existing is not null) return existing.Id;
        var d = new SolutionErp.Domain.Master.Department { Id = Guid.NewGuid(), Code = code, Name = name };
        _db.Departments.Add(d);
        _db.SaveChanges();
        return d.Id;
    }

    private async Task<PurchaseEvaluation> SeedPeAsync(PurchaseEvaluationPhase phase, Guid? projectId = null)
    {
        // Project required by FK constraint.
        var pid = projectId ?? Guid.NewGuid();
        if (!_db.Projects.Any(p => p.Id == pid))
        {
            _db.Projects.Add(new SolutionErp.Domain.Master.Project
            {
                Id = pid,
                Code = $"PRJ-{Random.Shared.Next(10000):D4}",
                Name = "Test project",
            });
        }

        var pe = new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = phase,
            TenGoiThau = "Test gói thầu",
            ProjectId = pid,
        };
        _db.PurchaseEvaluations.Add(pe);
        await _db.SaveChangesAsync();
        return pe;
    }

    [Fact]
    public async Task NV_Review_Blocks_Phase_Transition()
    {
        // Arrange: NV.PRO (role Procurement, dept PRO, NOT DeptManager).
        var nv = await _fx.CreateUserAsync(
            $"nv-{Guid.NewGuid():N}@test", "NV PRO", _deptPro, ["Procurement"]);
        var pe = await SeedPeAsync(PurchaseEvaluationPhase.ChoPurchasing);

        // Act: NV approve to ChoCCM.
        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.ChoCCM, nv.Id, ["Procurement"],
            ApprovalDecision.Approve, "review");

        // Assert: phase KHÔNG đổi, có 1 row Stage=Review.
        var fresh = await _db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        fresh.Phase.Should().Be(PurchaseEvaluationPhase.ChoPurchasing);

        var deptApprovals = await _db.PurchaseEvaluationDepartmentApprovals
            .Where(a => a.PurchaseEvaluationId == pe.Id).ToListAsync();
        deptApprovals.Should().HaveCount(1);
        deptApprovals[0].Stage.Should().Be(ApprovalStage.Review);
        deptApprovals[0].DepartmentId.Should().Be(_deptPro);
        deptApprovals[0].IsBypassed.Should().BeFalse();

        var approvals = await _db.PurchaseEvaluationApprovals
            .Where(a => a.PurchaseEvaluationId == pe.Id).ToListAsync();
        approvals.Should().HaveCount(1);
        approvals[0].Comment.Should().StartWith("[Review NV]");
    }

    [Fact]
    public async Task TPB_Confirm_After_NV_Review_Allows_Transition()
    {
        // Arrange: NV review trước, sau đó TPB confirm.
        var nv = await _fx.CreateUserAsync(
            $"nv-{Guid.NewGuid():N}@test", "NV PRO", _deptPro, ["Procurement"]);
        var tpb = await _fx.CreateUserAsync(
            $"tpb-{Guid.NewGuid():N}@test", "TPB PRO", _deptPro, ["DeptManager", "Procurement"]);
        var pe = await SeedPeAsync(PurchaseEvaluationPhase.ChoPurchasing);

        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.ChoCCM, nv.Id, ["Procurement"],
            ApprovalDecision.Approve, "review NV");

        // Re-fetch tracked entity (service modifies state ở Phase prior).
        pe = await _db.PurchaseEvaluations.FirstAsync(x => x.Id == pe.Id);

        // Act: TPB confirm.
        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.ChoCCM, tpb.Id, ["DeptManager", "Procurement"],
            ApprovalDecision.Approve, "confirm TPB");

        // Assert: phase đổi, có 2 row (Review + Confirm).
        var fresh = await _db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        fresh.Phase.Should().Be(PurchaseEvaluationPhase.ChoCCM);

        var deptApprovals = await _db.PurchaseEvaluationDepartmentApprovals
            .Where(a => a.PurchaseEvaluationId == pe.Id).ToListAsync();
        deptApprovals.Should().HaveCount(2);
        deptApprovals.Should().Contain(a => a.Stage == ApprovalStage.Review && a.ApproverUserId == nv.Id);
        deptApprovals.Should().Contain(a => a.Stage == ApprovalStage.Confirm && a.ApproverUserId == tpb.Id && !a.IsBypassed);
    }

    [Fact]
    public async Task NV_With_BypassReview_Allows_Transition_With_IsBypassed_True()
    {
        // Arrange: NV CanBypassReview=true.
        var nv = await _fx.CreateUserAsync(
            $"nv-bypass-{Guid.NewGuid():N}@test", "NV PRO bypass",
            _deptPro, ["Procurement"], canBypassReview: true);
        var pe = await SeedPeAsync(PurchaseEvaluationPhase.ChoPurchasing);

        // Act: bypass user approve → đẩy thẳng Stage=Confirm.
        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.ChoCCM, nv.Id, ["Procurement"],
            ApprovalDecision.Approve, "bypass approve");

        // Assert: phase đổi, có 1 row Stage=Confirm + IsBypassed=true.
        var fresh = await _db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        fresh.Phase.Should().Be(PurchaseEvaluationPhase.ChoCCM);

        var deptApprovals = await _db.PurchaseEvaluationDepartmentApprovals
            .Where(a => a.PurchaseEvaluationId == pe.Id).ToListAsync();
        deptApprovals.Should().HaveCount(1);
        deptApprovals[0].Stage.Should().Be(ApprovalStage.Confirm);
        deptApprovals[0].IsBypassed.Should().BeTrue();
        deptApprovals[0].ApproverRoleSnapshot.Should().Be("NV(bypass)");
    }

    [Fact]
    public async Task Admin_Skips_TwoStage_Logic_Entirely()
    {
        // Arrange: Admin role.
        var admin = await _fx.CreateUserAsync(
            $"admin-{Guid.NewGuid():N}@test", "Admin user", null, ["Admin"]);
        var pe = await SeedPeAsync(PurchaseEvaluationPhase.ChoPurchasing);

        // Act: Admin approve.
        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.ChoCCM, admin.Id, ["Admin"],
            ApprovalDecision.Approve, "admin force");

        // Assert: phase đổi, KHÔNG có DepartmentApproval row.
        var fresh = await _db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        fresh.Phase.Should().Be(PurchaseEvaluationPhase.ChoCCM);

        var deptApprovals = await _db.PurchaseEvaluationDepartmentApprovals
            .Where(a => a.PurchaseEvaluationId == pe.Id).ToListAsync();
        deptApprovals.Should().BeEmpty();
    }

    [Fact]
    public async Task Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao()
    {
        // Arrange: PE phase=ChoCCM. Drafter reject.
        var actor = await _fx.CreateUserAsync(
            $"ccm-{Guid.NewGuid():N}@test", "CCM TPB", _deptCcm, ["DeptManager", "CostControl"]);
        var pe = await SeedPeAsync(PurchaseEvaluationPhase.ChoCCM);

        // Act: reject (target irrelevant — service forces về DangSoanThao).
        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.TuChoi, actor.Id, ["DeptManager", "CostControl"],
            ApprovalDecision.Reject, "không phù hợp");

        // Assert.
        var fresh = await _db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        fresh.Phase.Should().Be(PurchaseEvaluationPhase.DangSoanThao);
        fresh.RejectedFromPhase.Should().Be(PurchaseEvaluationPhase.ChoCCM);
    }

    [Fact]
    public async Task Resume_After_Reject_Jumps_Back_To_RejectedPhase()
    {
        // Arrange: PE rejected từ ChoCCM, đang ở DangSoanThao + RejectedFromPhase=ChoCCM.
        var drafter = await _fx.CreateUserAsync(
            $"drafter-{Guid.NewGuid():N}@test", "Drafter", _deptPro, ["Drafter"]);
        var pe = await SeedPeAsync(PurchaseEvaluationPhase.DangSoanThao);
        pe.RejectedFromPhase = PurchaseEvaluationPhase.ChoCCM;
        await _db.SaveChangesAsync();

        // Act: drafter trình lại từ DangSoanThao → ChoPurchasing (target không
        // quan trọng vì resume sẽ override = RejectedFromPhase). Note: service
        // jump tới ChoCCM, nhưng nếu actor có dept thì sẽ hit 2-stage logic.
        // Simpler: dùng admin để bypass 2-stage gate khi resume cũng OK.
        var admin = await _fx.CreateUserAsync(
            $"admin-resume-{Guid.NewGuid():N}@test", "Admin resume", null, ["Admin"]);
        await _service.TransitionAsync(
            pe, PurchaseEvaluationPhase.ChoPurchasing, admin.Id, ["Admin"],
            ApprovalDecision.Approve, "drafter resume");

        // Assert: phase jump tới ChoCCM (không phải ChoPurchasing target),
        // RejectedFromPhase=null.
        var fresh = await _db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        fresh.Phase.Should().Be(PurchaseEvaluationPhase.ChoCCM);
        fresh.RejectedFromPhase.Should().BeNull();
    }
}

// Stub notification service — tests không cần verify notification path
// (best effort try/catch ở service đã cover fail case).
internal class FakeNotificationService : 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;
}
