using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Application;

// S66 test-after (UAT mode — PE HoSoLink shipped S65 Mig 52).
// `PurchaseEvaluation.HoSoLink string?` — 1 hyperlink tới thư mục hồ sơ NAS, max 1000,
// nullable, KHÔNG entity con / index.
//
// [S88 SPEC CHANGE — bug-fix anh Kiệt FDC, bug-class S42/#73] UpdateDraft HoSoLink chuyển
// từ ABSOLUTE-SET → NULL-SAFE + CLEAR-VIA-EMPTY. CHỐT theo CODE (single source of truth,
// S34 rule) — đọc PurchaseEvaluationFeatures.cs:
//   - Create handler line ~140:  HoSoLink = request.HoSoLink   (absolute-set, default null)
//   - Update handler line 285-286 (MỚI):
//         if (request.HoSoLink is not null)
//             entity.HoSoLink = string.IsNullOrWhiteSpace(request.HoSoLink) ? null : request.HoSoLink;
//     ⇒ 3 nhánh:
//        • request.HoSoLink == null  (form Sửa header / InfoTab pencil OMIT field) → GIỮ link cũ
//          (PRESERVE) — đây là FIX: trước absolute-set null-hóa link mỗi lần Sửa header (mất dữ liệu).
//        • request.HoSoLink == ""/whitespace (HoSoLinkRow bấm xoá → FE gửi "") → CLEAR (set null).
//        • request.HoSoLink == "giá trị"                                          → set giá trị đó.
//   - Validator: RuleFor(HoSoLink).MaximumLength(1000) — chỉ field-level, no cross-table.
//
// LƯU Ý: HoSoLink nay CÙNG họ null-safe với ApprovalWorkflowId (S85) + BudgetPeriodAmount /
//   ExpectedRemainingAmount / WorkItemId (S61/S57bis) — tất cả guard `if (request.X is not
//   null)`. RIÊNG HoSoLink thêm "" = clear (vì FE HoSoLinkRow cần một cách chủ động xoá link
//   mà không có nút riêng — gửi chuỗi rỗng). Các field kia null-safe THUẦN (không clear-via-empty).
//
// UpdatePurchaseEvaluationDraftCommandHandler(IApplicationDbContext db, ICurrentUser cu)
// — 2 dep nhẹ. Phase guard: chỉ DangSoanThao / TraLai mới update được.
public class PeHoSoLinkTests
{
    private sealed class FakeCurrentUser : ICurrentUser
    {
        public Guid? UserId { get; init; } = Guid.NewGuid();
        public string? Email { get; init; }
        public string? FullName { get; init; } = "Người soạn";
        public IReadOnlyList<string> Roles { get; init; } = System.Array.Empty<string>();
        public bool IsAuthenticated => UserId is not null;
    }

    // Seed 1 PE Nháp với HoSoLink ban đầu (default null). Trả entity.
    private static PurchaseEvaluation BuildPe(
        PurchaseEvaluationPhase phase = PurchaseEvaluationPhase.DangSoanThao,
        string? hoSoLink = null,
        string code = "PE-HSL-001")
        => new()
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = phase,
            MaPhieu = code,
            TenGoiThau = "Gói thầu test",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = Guid.NewGuid(),
            HoSoLink = hoSoLink,
        };

    // Update command với chỉ HoSoLink đổi, giữ field text khác như cũ.
    private static UpdatePurchaseEvaluationDraftCommand UpdateCmd(Guid id, string? hoSoLink)
        => new(id, "Gói thầu test", DiaDiem: null, MoTa: null, PaymentTerms: null, HoSoLink: hoSoLink);

    // ============================================================
    // EF persistence round-trip — create-path field persist đúng
    // ============================================================

    [Fact]
    public async Task Persist_HoSoLinkSet_RoundTripsExactValue()
    {
        using var fix = new SqliteDbFixture();
        var db = fix.Db;

        const string link = @"\\nas.solutions.local\HoSo\GoiThau\BeTong-2026";
        var pe = BuildPe(hoSoLink: link);
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(link);
    }

    [Fact]
    public async Task Persist_HoSoLinkNull_AllowedOptional()
    {
        // HoSoLink optional (nullable) — phiếu không có link vẫn lưu OK.
        using var fix = new SqliteDbFixture();
        var db = fix.Db;

        var pe = BuildPe(hoSoLink: null, code: "PE-HSL-002");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().BeNull();
    }

    [Fact]
    public async Task Persist_HoSoLink1000Chars_RoundTripsAtMaxLength()
    {
        // MaxLength(1000) — boundary 1000 ký tự lưu nguyên (SQLite TEXT không cắt,
        // assert giá trị giữ đủ; SQL Server prod nvarchar(1000) cũng chứa đủ 1000).
        using var fix = new SqliteDbFixture();
        var db = fix.Db;

        var link = new string('x', 1000);
        var pe = BuildPe(hoSoLink: link, code: "PE-HSL-003");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().HaveLength(1000);
    }

    // ============================================================
    // UpdateDraft — null-safe PRESERVE + clear-via-empty + set (S88 spec change)
    // ============================================================

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

        var pe = BuildPe(hoSoLink: null, code: "PE-HSL-U1");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        const string newLink = @"\\nas\HoSo\PE-001";
        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        await handler.Handle(UpdateCmd(pe.Id, newLink), CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(newLink);
    }

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

        var pe = BuildPe(hoSoLink: @"\\nas\HoSo\OLD", code: "PE-HSL-U2");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        const string newLink = @"\\nas\HoSo\NEW-2026";
        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        await handler.Handle(UpdateCmd(pe.Id, newLink), CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(newLink);
    }

    // CASE (a) — bug-fix cốt lõi: form Sửa header OMIT hoSoLink (null) → GIỮ link cũ.
    // TRƯỚC FIX: absolute-set null → link bị null-hóa (mất dữ liệu mỗi lần Sửa header).
    // SAU FIX (S88): null-safe guard `if (request.HoSoLink is not null)` → PRESERVE.
    // Test này từng RED trên code cũ (đã chứng minh: pre-fix null=clear → fail "expected link").
    [Fact]
    public async Task UpdateDraft_HoSoLinkNull_PreservesExistingLink()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        const string existing = @"\\nas\HoSo\MUST-BE-PRESERVED";
        var pe = BuildPe(hoSoLink: existing, code: "PE-HSL-U3");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        // HoSoLink default null trong UpdateCmd (form Sửa header KHÔNG gửi field) → preserve.
        await handler.Handle(UpdateCmd(pe.Id, hoSoLink: null), CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(existing,
            "S88 null-safe: request.HoSoLink=null (form Sửa header OMIT) → GIỮ link cũ (KHÔNG null-hóa — root fix bug-class S42/#73)");
        // Sanity: handler vẫn chạy (field text khác vẫn được set), không phải no-op.
        reload.TenGoiThau.Should().Be("Gói thầu test");
    }

    // CASE (b) — HoSoLinkRow bấm xoá → FE gửi "" (chuỗi rỗng) → CLEAR (set null).
    // "" / whitespace là cách chủ động xoá link (khác null = không gửi). Theory phủ cả " ".
    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    public async Task UpdateDraft_HoSoLinkEmptyOrWhitespace_ClearsLink(string blank)
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        var pe = BuildPe(hoSoLink: @"\\nas\HoSo\WILL-BE-CLEARED", code: "PE-HSL-U3B");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        // FE HoSoLinkRow gửi "" khi user xoá link → clear.
        await handler.Handle(UpdateCmd(pe.Id, hoSoLink: blank), CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().BeNull(
            "S88 clear-via-empty: request.HoSoLink=\"\"/whitespace (HoSoLinkRow xoá) → CLEAR link (set null)");
    }

    // CASE (c) — gửi giá trị thật → set giá trị đó (overwrite link cũ).
    [Fact]
    public async Task UpdateDraft_HoSoLinkValue_SetsIt()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        var pe = BuildPe(hoSoLink: @"\\nas\HoSo\OLD", code: "PE-HSL-U3C");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        const string newLink = @"O:\new";
        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        await handler.Handle(UpdateCmd(pe.Id, hoSoLink: newLink), CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(newLink, "request.HoSoLink có giá trị → set giá trị đó");
    }

    [Fact]
    public async Task UpdateDraft_OnTraLaiPhase_AllowsHoSoLinkUpdate()
    {
        // Phase guard cho phép Nháp + Trả lại → update HoSoLink ở TraLai cũng OK.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        var pe = BuildPe(PurchaseEvaluationPhase.TraLai, hoSoLink: null, code: "PE-HSL-U4");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        const string link = @"\\nas\HoSo\TraLai-Update";
        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        await handler.Handle(UpdateCmd(pe.Id, link), CancellationToken.None);

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(link);
    }

    [Fact]
    public async Task UpdateDraft_OnChoDuyetPhase_ThrowsConflict_NoMutation()
    {
        // Phase != Nháp/Trả lại → ConflictException, HoSoLink KHÔNG bị đổi.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();

        var pe = BuildPe(PurchaseEvaluationPhase.ChoDuyet, hoSoLink: @"\\nas\HoSo\LOCKED", code: "PE-HSL-U5");
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);

        var handler = new UpdatePurchaseEvaluationDraftCommandHandler(db, new FakeCurrentUser());
        var act = async () => await handler.Handle(UpdateCmd(pe.Id, @"\\nas\HoSo\HACK"), CancellationToken.None);

        await act.Should().ThrowAsync<ConflictException>()
            .WithMessage("*Nháp hoặc Trả lại*");

        var reload = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reload.HoSoLink.Should().Be(@"\\nas\HoSo\LOCKED");
    }
}
