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

namespace SolutionErp.Infrastructure.Tests.Application;

// [PE D2 — Mig 58 multi-winner] test-after cho CreateContractFromEvaluationCommand
// SAU khi đổi single-winner (SelectedSupplierId) → multi-winner (Suppliers.IsWinner)
// trả List<Guid> (1 HĐ per đơn vị IsWinner). KHÔNG có test cũ cho handler này.
//
// Code under test: CreateContractFromEvaluationFeatures.cs:45-152.
// Lock các invariant anh Kiệt UAT con số:
//   - GiaTri: 1 winner → SUM details.ThanhTienNganSach (HÀNH VI CŨ giữ); ≥2 →
//     mỗi HĐ = SUM ThanhTien báo giá của ĐÚNG winner đó (quote.PESupplierId == winner-row.Id).
//   - 1 Contract per winner, distinct MaHopDong (codegen per supplier), Phase=DangSoanThao,
//     SupplierId = supplier của winner đó.
//   - Idempotency: pe.ContractId = HĐ ĐẦU; re-call → ConflictException "đã tạo HĐ rồi".
//   - 0 winner → ConflictException "chưa chọn NCC/TP trúng thầu".
//   - Phase != DaDuyet → ConflictException.
//
// Wiring mirror CreateContractCommandApplicableTypeTests (S33): real
// ContractCodeGenerator(db, clock) + real ContractWorkflowService (BeginTransactionAsync
// SERIALIZABLE chạy SẠCH trên SQLite — proven WorkflowAppApproveV2Tests / ContractCodeGeneratorTests,
// KHÔNG try/skip). IdentityFixture cần cho ChangelogService → UserManager (dù changelog
// chỉ stash UserId).
//
// ⚠️ Seed-FK: PurchaseEvaluationQuote có HARD FK PurchaseEvaluationSupplierId → Supplier
// nav (Restrict, PurchaseEvaluationConfiguration:135) → MỖI Quote PHẢI ref 1
// PurchaseEvaluationSupplier-row thật (random Guid → SQLite Error 19). Contract.SupplierId/
// ProjectId KHÔNG FK vật lý (chỉ index) nhưng handler load Project (NotFound) + build
// supplierMap từ Suppliers thật → vẫn seed real Supplier + Project rows.
public class CreateContractFromEvaluationMultiWinnerTests
{
    private static readonly DateTime Clock = new(2026, 6, 24, 0, 0, 0, DateTimeKind.Utc);

    // Build handler với deps thật (mirror prod ContractFeatures wiring).
    private static CreateContractFromEvaluationCommandHandler BuildHandler(
        TestApplicationDbContext db, IServiceProvider sp, User actor)
    {
        var um = sp.GetRequiredService<UserManager<User>>();
        var dt = new FixedDateTime(Clock);
        var notify = new NoOpNotificationServiceApp();
        var currentUser = new TestCurrentUser { UserId = actor.Id, Roles = new[] { AppRoles.Drafter } };
        var changelog = new ChangelogService(db, currentUser, um);
        var codeGen = new ContractCodeGenerator(db, dt);
        var workflowSvc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);
        return new CreateContractFromEvaluationCommandHandler(db, currentUser, workflowSvc, codeGen);
    }

    private sealed record Seeded(
        PurchaseEvaluation Pe,
        Project Project,
        List<(Supplier supplier, PurchaseEvaluationSupplier row)> Suppliers,
        User Actor);

    // Seed 1 PE (default DaDuyet) + N supplier rows; winnersByIndex marks which supplier-rows
    // are IsWinner. detailQuotes[i][j] = ThanhTien báo giá của supplier j cho detail i.
    // detailBudgets[i] = ThanhTienNganSach (ngân sách) của detail i.
    // [multi-NCC 2026-07-12 cardinality-change] GiaTri mỗi HĐ NAY = SUM ThanhTien quote ĐƯỢC CHỌN
    // (Quote.IsSelected) của winner đó — CreateContractFromEvaluationFeatures.cs:88-90
    // `q.PurchaseEvaluationSupplierId == w.Id && q.IsSelected`. Nên quote của winner PHẢI
    // IsSelected=true mới cộng vào GiaTri. detailQuoteSelected[i][j] override IsSelected per
    // (detail,supplier); null → mặc định winner's-all-selected (IsSelected = winnerIndexes.Contains(j)).
    private static async Task<Seeded> SeedAsync(
        IdentityFixture fix,
        TestApplicationDbContext db,
        int supplierCount,
        int[] winnerIndexes,
        decimal[] detailBudgets,
        decimal[][] detailQuotes,
        PurchaseEvaluationPhase phase = PurchaseEvaluationPhase.DaDuyet,
        bool[][]? detailQuoteSelected = null)
    {
        var actor = await fix.CreateUserAsync("drafter-d2@test.local", "Drafter D2",
            departmentId: null, roles: new[] { AppRoles.Drafter });

        var project = new Project { Id = Guid.NewGuid(), Code = "FLOCK01", Name = "Dự án FLOCK 01" };
        db.Projects.Add(project);

        var suppliers = new List<(Supplier, PurchaseEvaluationSupplier)>();
        var peId = Guid.NewGuid();
        for (var i = 0; i < supplierCount; i++)
        {
            var supplier = new Supplier
            {
                Id = Guid.NewGuid(),
                Code = $"NCC{i + 1}",
                Name = $"Nhà cung cấp {i + 1}",
                Type = SupplierType.NhaThauPhu,
            };
            db.Suppliers.Add(supplier);
            var row = new PurchaseEvaluationSupplier
            {
                Id = Guid.NewGuid(),
                PurchaseEvaluationId = peId,
                SupplierId = supplier.Id,
                Order = i,
                IsWinner = winnerIndexes.Contains(i),
            };
            suppliers.Add((supplier, row));
        }

        var pe = new PurchaseEvaluation
        {
            Id = peId,
            MaPhieu = "PE-2026-001",
            Type = PurchaseEvaluationType.DuyetNccPhuongAn,
            Phase = phase,
            TenGoiThau = "Cung cấp bê tông",
            ProjectId = project.Id,
            DepartmentId = null,
            DrafterUserId = actor.Id,
            MoTa = "Mô tả gói thầu",
            PaymentTerms = "{\"tamUng\":30}",
            BudgetPeriodAmount = 999_000_000m,
            // SelectedSupplierId: với ≥2 winner = null (multi); single = winner duy nhất —
            // handler KHÔNG đọc field này nữa (đọc Suppliers.IsWinner) nên để null an toàn.
            SelectedSupplierId = null,
        };
        pe.Suppliers.AddRange(suppliers.Select(s => s.Item2));

        for (var i = 0; i < detailBudgets.Length; i++)
        {
            var detail = new PurchaseEvaluationDetail
            {
                Id = Guid.NewGuid(),
                PurchaseEvaluationId = peId,
                GroupCode = "A.I",
                GroupName = "Bê tông",
                NoiDung = $"Hạng mục {i + 1}",
                Order = i,
                ThanhTienNganSach = detailBudgets[i],
            };
            // Quote per supplier cho detail này — FK PESupplierId → supplier-row.Id.
            for (var j = 0; j < supplierCount; j++)
            {
                var isSelected = detailQuoteSelected is not null
                    ? detailQuoteSelected[i][j]
                    : winnerIndexes.Contains(j);   // mặc định: quote của winner = được chọn
                detail.Quotes.Add(new PurchaseEvaluationQuote
                {
                    Id = Guid.NewGuid(),
                    PurchaseEvaluationDetailId = detail.Id,
                    PurchaseEvaluationSupplierId = suppliers[j].Item2.Id,
                    ThanhTien = detailQuotes[i][j],
                    IsSelected = isSelected,
                });
            }
            pe.Details.Add(detail);
        }

        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);
        return new Seeded(pe, project,
            suppliers.Select(s => (s.Item1, s.Item2)).ToList(), actor);
    }

    // ===== (a) ≥2 winners → N HĐ, mỗi GiaTri = winner's quote total =====

    [Fact]
    public async Task Create_TwoWinners_CreatesTwoContracts_EachGiaTriIsThatWinnerQuoteTotal()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        // 2 winner (idx 0 + 1), 1 non-winner (idx 2). 2 details.
        // Quote totals: winner0 = 100 + 40 = 140; winner1 = 200 + 60 = 260; loser2 = 999.
        // detailBudgets (NGÂN SÁCH) khác hẳn quote → chứng minh ≥2 KHÔNG dùng budget sum.
        var seeded = await SeedAsync(fix, db,
            supplierCount: 3,
            winnerIndexes: new[] { 0, 1 },
            detailBudgets: new[] { 500m, 300m },                 // sum = 800 (PHẢI bị bỏ qua khi ≥2)
            detailQuotes: new[]
            {
                new[] { 100m, 200m, 7m },                        // detail 0: w0=100, w1=200
                new[] { 40m, 60m, 9m },                          // detail 1: w0=40,  w1=60
            });
        var handler = BuildHandler(db, fix.Services, seeded.Actor);

        var ids = await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, "HĐ liên-danh"),
            CancellationToken.None);

        ids.Should().HaveCount(2);

        var contracts = await db.Contracts.AsNoTracking()
            .Where(c => ids.Contains(c.Id)).ToListAsync();
        contracts.Should().HaveCount(2);
        contracts.Should().OnlyContain(c => c.Phase == ContractPhase.DangSoanThao);
        contracts.Should().OnlyContain(c => c.Type == ContractType.HopDongThauPhu);

        // Mỗi Contract.SupplierId khớp 1 winner; KHÔNG có loser.
        var winnerSupplierIds = new[] { seeded.Suppliers[0].supplier.Id, seeded.Suppliers[1].supplier.Id };
        contracts.Select(c => c.SupplierId).Should().BeEquivalentTo(winnerSupplierIds);
        contracts.Should().NotContain(c => c.SupplierId == seeded.Suppliers[2].supplier.Id);

        // GiaTri per-winner = SUM ThanhTien báo giá của ĐÚNG winner đó.
        var bySupplier = contracts.ToDictionary(c => c.SupplierId, c => c.GiaTri);
        bySupplier[seeded.Suppliers[0].supplier.Id].Should().Be(140m);   // 100 + 40
        bySupplier[seeded.Suppliers[1].supplier.Id].Should().Be(260m);   // 200 + 60

        // 2 mã HĐ DISTINCT (codegen per supplier).
        contracts.Select(c => c.MaHopDong).Should().OnlyHaveUniqueItems();
        contracts.Should().OnlyContain(c => c.MaHopDong != null);

        // Idempotency anchor: pe.ContractId = HĐ ĐẦU (contracts[0].Id trong list trả về).
        var peReloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(p => p.Id == seeded.Pe.Id);
        peReloaded.ContractId.Should().Be(ids[0]);
    }

    // ===== single winner → 1 HĐ, GiaTri = SUM báo giá ĐƯỢC CHỌN của winner (D4) =====
    // [multi-NCC 2026-07-12 — SPEC CHANGE D4] BỎ shortcut detailsSum (budget) cũ cho single.
    // NAY single = SUM(Quote.IsSelected của winner), giống multi. Trước đây test này assert
    // GiaTri = 800 (budget sum); giờ = 140 (100+40 = quote ĐƯỢC CHỌN của winner0).

    [Fact]
    public async Task Create_SingleWinner_GiaTriIsSelectedQuoteTotal_NotBudgetSum()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        // 1 winner (idx 0). detailBudgets sum = 500 + 300 = 800 (KHÔNG dùng nữa). winner0 quote
        // ĐƯỢC CHỌN = 100 + 40 = 140. Single-winner NAY = 140 (quote IsSelected), KHÔNG = 800 (budget).
        var seeded = await SeedAsync(fix, db,
            supplierCount: 2,
            winnerIndexes: new[] { 0 },
            detailBudgets: new[] { 500m, 300m },
            detailQuotes: new[]
            {
                new[] { 100m, 7m },
                new[] { 40m, 9m },
            });
        var handler = BuildHandler(db, fix.Services, seeded.Actor);

        var ids = await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, null),
            CancellationToken.None);

        ids.Should().HaveCount(1);

        var contract = await db.Contracts.AsNoTracking().FirstAsync(c => c.Id == ids[0]);
        contract.SupplierId.Should().Be(seeded.Suppliers[0].supplier.Id);
        contract.GiaTri.Should().Be(140m, "D4: single-winner = SUM báo giá ĐƯỢC CHỌN (100+40), KHÔNG phải budget 800");
        contract.Phase.Should().Be(ContractPhase.DangSoanThao);
        contract.MaHopDong.Should().NotBeNull();
        // TenHopDong null trong request → fallback pe.TenGoiThau.
        contract.TenHopDong.Should().Be("Cung cấp bê tông");

        var peReloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(p => p.Id == seeded.Pe.Id);
        peReloaded.ContractId.Should().Be(ids[0]);
    }

    // ===== (b) [multi-NCC 2026-07-12 — NEW] single-winner thắng SUBSET hạng mục =====
    // Winner thắng CHỈ 1 trong 2 hạng mục (detail0 IsSelected=true, detail1 IsSelected=false)
    // → GiaTri = SUM(selected quotes) = quote detail0 THÔI, KHÁC SUM-mọi-quote-của-winner
    // (chống bug detailsSum/all-quote cũ). Chứng minh handler lọc theo Quote.IsSelected per hạng mục.
    [Fact]
    public async Task Create_SingleWinner_WinsSubsetOfDetails_GiaTriSumsOnlySelectedQuotes()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        // Winner = supplier0. detail0 quote 300 ĐƯỢC CHỌN, detail1 quote 500 KHÔNG chọn.
        // GiaTri = 300 (chỉ selected); SUM-mọi-quote-winner = 800; budget-sum = 2000. Cả 3 khác nhau.
        var seeded = await SeedAsync(fix, db,
            supplierCount: 2,
            winnerIndexes: new[] { 0 },
            detailBudgets: new[] { 1000m, 1000m },              // sum 2000 — KHÔNG dùng
            detailQuotes: new[]
            {
                new[] { 300m, 11m },                            // detail0: winner0=300
                new[] { 500m, 13m },                            // detail1: winner0=500 (KHÔNG chọn)
            },
            detailQuoteSelected: new[]
            {
                new[] { true, false },                          // detail0: winner0 ĐƯỢC CHỌN
                new[] { false, false },                         // detail1: winner0 KHÔNG chọn
            });
        var handler = BuildHandler(db, fix.Services, seeded.Actor);

        var ids = await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, null),
            CancellationToken.None);

        ids.Should().HaveCount(1);
        var contract = await db.Contracts.AsNoTracking().FirstAsync(c => c.Id == ids[0]);
        contract.SupplierId.Should().Be(seeded.Suppliers[0].supplier.Id);
        contract.GiaTri.Should().Be(300m,
            "SUM chỉ quote ĐƯỢC CHỌN (detail0=300); KHÁC SUM-mọi-quote-winner (800) + budget-sum (2000)");
    }

    // ===== (c) idempotency → re-call throws ConflictException =====

    [Fact]
    public async Task Create_Again_AfterSuccess_ThrowsConflict_DaTaoHopDongRoi()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        var seeded = await SeedAsync(fix, db,
            supplierCount: 2,
            winnerIndexes: new[] { 0, 1 },
            detailBudgets: new[] { 500m },
            detailQuotes: new[] { new[] { 100m, 200m } });
        var handler = BuildHandler(db, fix.Services, seeded.Actor);

        var firstIds = await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, null),
            CancellationToken.None);
        firstIds.Should().HaveCount(2);

        // Re-call cùng phiếu (pe.ContractId đã set) → block.
        var act = async () => await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, null),
            CancellationToken.None);
        await act.Should().ThrowAsync<ConflictException>().WithMessage("*đã tạo HĐ rồi*");

        // Không tạo thêm HĐ nào (vẫn đúng 2).
        (await db.Contracts.AsNoTracking().CountAsync()).Should().Be(2);
    }

    // ===== (d) 0 winner → ConflictException (chưa chọn NCC/TP trúng thầu) =====

    [Fact]
    public async Task Create_NoWinner_ThrowsConflict_ChuaChonTrungThau()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        // supplier rows tồn tại nhưng KHÔNG có IsWinner.
        var seeded = await SeedAsync(fix, db,
            supplierCount: 2,
            winnerIndexes: Array.Empty<int>(),
            detailBudgets: new[] { 500m },
            detailQuotes: new[] { new[] { 100m, 200m } });
        var handler = BuildHandler(db, fix.Services, seeded.Actor);

        var act = async () => await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, null),
            CancellationToken.None);
        await act.Should().ThrowAsync<ConflictException>().WithMessage("*chưa chọn NCC/TP trúng thầu*");

        (await db.Contracts.AsNoTracking().CountAsync()).Should().Be(0);
    }

    // ===== (e) not DaDuyet phase → ConflictException =====

    [Fact]
    public async Task Create_NotDaDuyet_ThrowsConflict()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        // Phiếu còn ChoDuyet (chưa duyệt xong) dù có winner → chặn.
        var seeded = await SeedAsync(fix, db,
            supplierCount: 2,
            winnerIndexes: new[] { 0 },
            detailBudgets: new[] { 500m },
            detailQuotes: new[] { new[] { 100m, 200m } },
            phase: PurchaseEvaluationPhase.ChoDuyet);
        var handler = BuildHandler(db, fix.Services, seeded.Actor);

        var act = async () => await handler.Handle(
            new CreateContractFromEvaluationCommand(seeded.Pe.Id, ContractType.HopDongThauPhu, null),
            CancellationToken.None);
        await act.Should().ThrowAsync<ConflictException>().WithMessage("*DaDuyet*");

        (await db.Contracts.AsNoTracking().CountAsync()).Should().Be(0);
    }
}
