using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Master.Suppliers.Commands.PublishSupplier;
using SolutionErp.Application.Master.Suppliers.Queries.GetSupplier;
using SolutionErp.Application.Master.Suppliers.Queries.ListSuppliers;
using SolutionErp.Domain.Master;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Application;

// ============================================================================
// Supplier import v2 (S113) — PublishSupplierCommand + ListSuppliers/GetSupplier IsPublic.
// Publish-gate = SECURITY/correctness (cổng công bố NCC) → test-before-merge (docs/rules.md §7).
// Test theo CODE trên đĩa (S34) — handlers chỉ cần IApplicationDbContext → new trực tiếp với fix.Db.
//
// 🔑 PublishSupplierCommand (KHÔNG dùng UpdateSupplier, tránh #73 clobber):
//   - MaNcc gửi (non-null): set Code=Trim; unique-check `x.Code == newCode && x.Id != id` (khi len>0
//     & khác Code cũ) → ConflictException nếu trùng.
//   - Gate D2: Publish=true ∧ Code rỗng → ConflictException "Cần Mã NCC...".
//   - IsPublic = Publish.
//
// ⚠️ Unique-check publish dùng `x.Code == newCode` (DB collation), KHÁC import service
//   (OrdinalIgnoreCase in-mem). SQLite BINARY → chỉ bắt trùng ĐÚNG hoa/thường. Prod SQL Server
//   CI-collation mới bắt CI-dup. Test T9b dùng trùng ĐÚNG-case (deterministic trên SQLite).
//   (Observation, KHÔNG phải bug — prod collation CI xử lý; flag để biết asymmetry.)
// ============================================================================
public class SupplierPublishAndListTests
{
    private static Supplier NewSupplier(string code, string name, bool isPublic,
        SupplierType type = SupplierType.NhaCungCap) =>
        new() { Id = Guid.NewGuid(), Code = code, Name = name, Type = type, IsPublic = isPublic };

    // ---- T9 — publish có Mã NCC → set Code + IsPublic=true ----
    [Fact]
    public async Task PublishSupplier_WithMaNcc_SetsCode_AndIsPublicTrue()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;
        var draft = NewSupplier("", "Draft NCC", isPublic: false);   // nháp, chưa có Mã NCC
        db.Suppliers.Add(draft);
        await db.SaveChangesAsync(CancellationToken.None);

        await new PublishSupplierCommandHandler(db)
            .Handle(new PublishSupplierCommand(draft.Id, MaNcc: "NEW01", Publish: true), CancellationToken.None);

        var reloaded = await db.Suppliers.SingleAsync(x => x.Id == draft.Id);
        reloaded.Code.Should().Be("NEW01", "MaNcc gửi → set Code");
        reloaded.IsPublic.Should().BeTrue("Publish=true + có Code → công bố");
    }

    // ---- T9b — publish với Mã NCC trùng (đúng hoa/thường) NCC khác → ConflictException ----
    [Fact]
    public async Task PublishSupplier_MaNccCollidesExactCaseWithOther_ThrowsConflict_NoChange()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;
        var other = NewSupplier("TAKEN", "Other", isPublic: true);
        var draft = NewSupplier("", "Draft", isPublic: false);
        db.Suppliers.AddRange(other, draft);
        await db.SaveChangesAsync(CancellationToken.None);

        var act = async () => await new PublishSupplierCommandHandler(db)
            .Handle(new PublishSupplierCommand(draft.Id, MaNcc: "TAKEN", Publish: true), CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*đã tồn tại*", "Mã NCC trùng NCC khác → chặn (unique-check)");

        var reloaded = await db.Suppliers.SingleAsync(x => x.Id == draft.Id);
        reloaded.Code.Should().Be("", "throw trước khi gán → draft KHÔNG đổi");
        reloaded.IsPublic.Should().BeFalse();
    }

    // ---- T10 — publish khi Code rỗng (không gửi MaNcc) → ConflictException "Cần Mã NCC" ----
    [Fact]
    public async Task PublishSupplier_PublishTrue_ButCodeEmpty_ThrowsConflict_CanMaNcc()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;
        var draft = NewSupplier("", "Draft NCC", isPublic: false);
        db.Suppliers.Add(draft);
        await db.SaveChangesAsync(CancellationToken.None);

        var act = async () => await new PublishSupplierCommandHandler(db)
            .Handle(new PublishSupplierCommand(draft.Id, MaNcc: null, Publish: true), CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Cần Mã NCC*", "gate D2: công bố bắt buộc có Mã NCC");

        (await db.Suppliers.SingleAsync(x => x.Id == draft.Id)).IsPublic
            .Should().BeFalse("gate chặn → vẫn nháp");
    }

    // ---- T11 — unpublish (Publish=false) LUÔN cho, IsPublic=false, giữ Code ----
    [Fact]
    public async Task PublishSupplier_Unpublish_AlwaysAllowed_SetsIsPublicFalse_KeepsCode()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;
        var pub = NewSupplier("KEEP", "Public NCC", isPublic: true);
        db.Suppliers.Add(pub);
        await db.SaveChangesAsync(CancellationToken.None);

        await new PublishSupplierCommandHandler(db)
            .Handle(new PublishSupplierCommand(pub.Id, MaNcc: null, Publish: false), CancellationToken.None);

        var reloaded = await db.Suppliers.SingleAsync(x => x.Id == pub.Id);
        reloaded.IsPublic.Should().BeFalse("ẩn luôn được, KHÔNG cần Mã NCC");
        reloaded.Code.Should().Be("KEEP", "unpublish KHÔNG đụng Code");
    }

    // ---- T12 — ListSuppliers Published filter: true / false / null=all ----
    [Fact]
    public async Task ListSuppliers_PublishedFilter_FiltersByIsPublic_NullReturnsAll()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;
        db.Suppliers.AddRange(
            NewSupplier("P1", "Pub 1", isPublic: true),
            NewSupplier("P2", "Pub 2", isPublic: true),
            NewSupplier("", "Draft 1", isPublic: false));
        await db.SaveChangesAsync(CancellationToken.None);

        var handler = new ListSuppliersQueryHandler(db);

        var published = await handler.Handle(new ListSuppliersQuery(Published: true) { PageSize = 100 }, CancellationToken.None);
        published.Total.Should().Be(2, "Published=true → chỉ NCC đã công bố");
        published.Items.Should().OnlyContain(x => x.IsPublic);

        var drafts = await handler.Handle(new ListSuppliersQuery(Published: false) { PageSize = 100 }, CancellationToken.None);
        drafts.Total.Should().Be(1, "Published=false → chỉ nháp");
        drafts.Items.Should().OnlyContain(x => !x.IsPublic);

        var all = await handler.Handle(new ListSuppliersQuery(Published: null) { PageSize = 100 }, CancellationToken.None);
        all.Total.Should().Be(3, "null → tất cả (admin quản lý thấy cả nháp)");
    }

    // ---- T13 — SupplierDto.IsPublic project đúng (GetSupplier single-item projection) ----
    [Fact]
    public async Task GetSupplier_ProjectsIsPublic_ForBothPublicAndDraft()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;
        var pub = NewSupplier("G1", "Public", isPublic: true);
        var draft = NewSupplier("", "Draft", isPublic: false);
        db.Suppliers.AddRange(pub, draft);
        await db.SaveChangesAsync(CancellationToken.None);

        var handler = new GetSupplierQueryHandler(db);

        (await handler.Handle(new GetSupplierQuery(pub.Id), CancellationToken.None)).IsPublic
            .Should().BeTrue("DTO project IsPublic=true cho NCC công bố");
        (await handler.Handle(new GetSupplierQuery(draft.Id), CancellationToken.None)).IsPublic
            .Should().BeFalse("DTO project IsPublic=false cho nháp");
    }
}
