using FluentValidation;
using MediatR;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Contracts.Services;
// [S187 W4] Alias 1 lớp thay vì mở trọn `SolutionErp.Application.Contracts`: file này đã mở
// `SolutionErp.Domain.Contracts` (Contract, ContractType, ContractChangelog…) — hai namespace
// cùng tên đuôi mà kéo cả hai vào là mời va-chạm tên về sau.
using ContractWorkflowGuards = SolutionErp.Application.Contracts.ContractWorkflowGuards;
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.ContractSigningPlans;
using SolutionErp.Domain.Contracts;

namespace SolutionErp.Application.ContractSigningPlans;

// =====================================================================================
// [K7 — S167 2026-08-01] CẦU GĐ2 → GĐ3: Kế hoạch ký kết (KHKK) đã duyệt → Hợp đồng draft.
//
// Khuôn = bridge PE→HĐ cũ `CreateContractFromEvaluationFeatures.cs` + guard workflow V2 của
// `ContractFeatures.cs:74-87`. Luật spec-4gd §①: GIỐNG NHAU — CHỈ KHÁC FORM — ĐỪNG CHẾ THÊM.
//
// 🔴 KHÁC bridge PE cũ ở 3 điểm CÓ CHỦ ĐÍCH:
//   1. Hạt là DÒNG (`ContractSigningPlanLine`), không phải phiếu: 1 call = 1 HĐ cho ĐÚNG tập
//      `LineIds` gửi lên. Đây là write-site ĐẦU TIÊN toàn codebase của `Line.ContractId`
//      (đo @S164: 0 assignment) ⇒ cây 4-folder có nguồn THẬT thay vì suy từ `pe.ContractId` đơn.
//   2. Pin V2 `ApprovalWorkflowId` (bridge cũ chỉ pin V1) — `ApprovalWorkflow.ApplicableType`
//      = Contract(3) là "HĐ general (any ContractType)" ⇒ KHÔNG suy được từ ContractType,
//      người bấm phải chọn trong dialog.
//   3. Trả 1 HĐ (`Guid` + mã) thay vì `List<Guid>` — liên-danh N NCC = N lần bấm (OG-1 UI-1-1).
//
// 🔴 4 VÁ của spec-cụm-3 §② nằm TRONG file này (đừng gỡ khi refactor):
//   vá-1  authz HỘI 2 khoá — ở controller (`KeHoachKyKet.Read` class ∧ `Contracts.Create` action).
//   vá-3  guard workflow pin phải `IsActive` ∧ `IsUserSelectable` — KHÔNG chỉ tồn-tại+đúng-type
//         (khuôn `ContractFeatures.cs:79-87` thiếu 2 vế này ⇒ forge POST pin được quy trình đã
//         retired; dropdown FE lọc 2 cờ `ContractCreatePage.tsx:334-341` nhưng FE không phải rào).
//   vá-4a `line.ContractId` ghi LUÔN (nguồn thật) + `pe.ContractId ??=` giữ vai FALLBACK hiển thị.
//   vá-6  nạp `Project.Code` + `Supplier.Code` (2 guard NotFound) cho codegen RG-001 · field-map
//         += `SlaDeadline` · pin `WorkflowDefinitionId` như 2 tiền lệ (`ContractFeatures.cs:105-109`
//         + bridge cũ `:108`) — để null là rơi về policy hardcode `ContractFeatures.cs:647-662`.
// =====================================================================================

/// Kết quả bridge — trả CẢ mã HĐ vì mã gen NGAY lúc tạo (FE toast + link không phải gọi lại).
public record CreateContractFromSigningPlanResult(Guid ContractId, string? MaHopDong);

/// <param name="LineIds">
/// Tập dòng đưa vào HĐ này. Máy nhận N (schema GỘP: N dòng cùng NCC → 1 HĐ); UI hướng-1
/// truyền đúng 1 phần tử. `GiaTri` = Σ `ApprovedAmount` của ĐÚNG tập này — không hơn không kém.
/// </param>
/// <param name="ApprovalWorkflowId">
/// Quy trình duyệt HĐ (V2, `ApplicableType = Contract`).
/// [S187 W4] Từ NULLABLE: bỏ trống ⇒ máy tự pin quy trình của đúng loại HĐ theo họ `HD-*`
/// (86 khoá cứng + 81 PA-A). Trước W4 field này BẮT BUỘC vì `ApplicableType=Contract` là
/// "HĐ general (any ContractType)" nên không suy được từ `ContractType` — họ mã `HD-*` (W3)
/// vừa dựng xong chính là thứ khiến việc suy trở nên có thật.
/// </param>
public record CreateContractFromSigningPlanCommand(
    Guid PlanId,
    List<Guid> LineIds,
    ContractType ContractType,
    Guid? ApprovalWorkflowId = null,
    string? TenHopDong = null) : IRequest<CreateContractFromSigningPlanResult>;

public class CreateContractFromSigningPlanCommandValidator
    : AbstractValidator<CreateContractFromSigningPlanCommand>
{
    public CreateContractFromSigningPlanCommandValidator()
    {
        RuleFor(x => x.PlanId).NotEmpty();
        // [S187 W4] `ApprovalWorkflowId` KHÔNG còn `NotEmpty()`: bỏ trống nay là một lựa chọn hợp
        // lệ (máy tự pin theo loại). `NotEmpty()` trên `Guid?` chặn CẢ `null` ⇒ giữ lại thì nhánh
        // auto-resolve chết trước khi handler chạy. Ca "chọn nhưng chọn bậy" do handler bắt bằng
        // câu tiếng Việt nêu đích danh mã quy trình — validator chỉ nói được "must not be empty".
        RuleFor(x => x.ContractType).IsInEnum();
        RuleFor(x => x.LineIds)
            .NotEmpty().WithMessage("Chọn ít nhất 1 dòng của kế hoạch để đưa vào Hợp đồng.");
        RuleFor(x => x.LineIds)
            .Must(ids => ids.Distinct().Count() == ids.Count)
            .When(x => x.LineIds is { Count: > 0 })
            .WithMessage("Danh sách dòng bị trùng — mỗi dòng chỉ được chọn 1 lần.");
        // MaxLength MATCH EF config (`ContractConfiguration` TenHopDong = 500) — EF là source
        // of truth, KHÔNG lấy số từ spec (bài S35).
        RuleFor(x => x.TenHopDong).MaximumLength(500);
    }
}

public class CreateContractFromSigningPlanCommandHandler(
    IApplicationDbContext db,
    ICurrentUser currentUser,
    IContractWorkflowService workflow,
    IContractCodeGenerator codeGenerator)
    : IRequestHandler<CreateContractFromSigningPlanCommand, CreateContractFromSigningPlanResult>
{
    public async Task<CreateContractFromSigningPlanResult> Handle(
        CreateContractFromSigningPlanCommand request, CancellationToken ct)
    {
        if (!currentUser.IsAuthenticated || currentUser.UserId is null)
            throw new UnauthorizedException();
        var actorId = currentUser.UserId.Value;

        // ---- Rào (1) PHIẾU + PHASE — mirror rào (i) `ContractSigningPlanFeatures.cs:381-382`.
        var plan = await db.ContractSigningPlans
            .Include(p => p.Lines)
            .FirstOrDefaultAsync(p => p.Id == request.PlanId, ct)
            ?? throw new NotFoundException("ContractSigningPlan", request.PlanId);

        if (plan.Phase != ContractSigningPlanPhase.DaDuyet)
            throw new ConflictException(
                "Chỉ tạo Hợp đồng từ Kế hoạch ký kết đã duyệt xong (Đã duyệt).");

        // ---- Rào (2) DÒNG ∈ PHIẾU. Đếm sau `Distinct` — id lạ/của phiếu khác đều rơi vào đây.
        var lineIds = request.LineIds.Distinct().ToList();
        var lines = plan.Lines
            .Where(l => lineIds.Contains(l.Id))
            .OrderBy(l => l.CreatedAt).ThenBy(l => l.Id)
            .ToList();
        if (lines.Count != lineIds.Count)
            throw new ConflictException(
                "Có dòng không thuộc kế hoạch này (đã bị xoá hoặc chọn nhầm phiếu) — tải lại trang rồi chọn lại.");

        // ---- Rào (3) 1 HĐ = 1 NCC (`Contract.SupplierId` đơn). Liên-danh N NCC = N lần bấm.
        var supplierIds = lines.Select(l => l.SupplierId).Distinct().ToList();
        if (supplierIds.Count > 1)
            throw new ConflictException(
                "Các dòng chọn thuộc nhiều NCC khác nhau — mỗi Hợp đồng chỉ 1 NCC, tạo lần lượt.");

        // ---- Rào (4)+(5) per-dòng: có giá CHỐT + chưa bridge. Message NÊU TÊN hạng mục để người
        //      bấm biết dòng nào — danh sách dòng trong dialog không có mã, chỉ có tên.
        foreach (var l in lines)
        {
            var ten = l.TenHangMuc ?? "(chưa gán hạng mục)";
            // KHÔNG fallback `ProposedAmount`: đó là số ĐỀ XUẤT chưa qua choke-point finalize
            // (`ContractSigningPlanWorkflowService.cs:348` `ApprovedAmount ??=`). Lấy nhầm =
            // HĐ mang số chưa ai chốt.
            if (l.ApprovedAmount is null)
                throw new ConflictException(
                    $"Dòng '{ten}' chưa có giá duyệt chốt — phiếu phải đi qua bước duyệt kết thúc trước khi đưa vào Hợp đồng.");
            if (l.ContractId is not null)
                throw new ConflictException($"Dòng '{ten}' đã có Hợp đồng.");
        }

        // ---- Rào (6) WORKFLOW PIN — [S187 W4] CỬA 2/4, nay gọi NHÀ CHUNG
        //      `ContractWorkflowGuards.EnsureValidWorkflowForContractAsync`.
        //
        // 4 vế inline trước đây (chính file này là bản MẪU mà nhà chung chép ra) nay + 1 vế thứ 5:
        // KHOÁ CỨNG LOẠI (86) — quy trình mang mã họ `HD-*` phải là mã của ĐÚNG `ContractType`
        // đang tạo, gắn chéo ⇒ 409. Mã ngoài họ `HD-*` vẫn cho qua CÓ CHỦ ĐÍCH (xem chú thích
        // đầu `ContractWorkflowGuards`): quy trình admin tự đặt tên không mang thông tin loại.
        //
        // 🔴 Message giữ NGUYÊN VĂN 3 chuỗi lưới T7 đang đo (`không khớp với Hợp đồng` ·
        //    `ngừng hoạt động` · `chưa được ghim`) + `NotFoundException` cho id rác (T7-a):
        //    gộp về một nhà nói CÙNG câu, không phải câu na ná.
        //
        // 🔴 KHÔNG chọn quy trình (`null`/`Guid.Empty`) ⇒ máy TỰ tìm quy trình của đúng loại HĐ
        //    (PA-A, `ResolveDefaultWorkflowAsync`). Không tìm được ⇒ 409 chứ KHÔNG lẳng lặng để
        //    null: pin V2 là LÝ DO cầu này tồn tại (chú thích đầu file, điểm 2). HĐ không pin V2
        //    thì `TransitionAsync:97` để `CurrentApprovalLevelOrder = null` ⇒ hộp thư của người
        //    phải duyệt RỖNG trong khi HĐ vẫn "đã trình" — đúng thế-giới-hỏng mà T8 dựng để bắt.
        //    (Hành vi cũ khi client không gửi field: `NotEmpty()` của validator ném 400. Nay 409
        //    kèm câu chỉ-đường; cả hai đều là "cầu từ chối tạo HĐ không có quy trình".)
        var pinnedWorkflowId = request.ApprovalWorkflowId is Guid reqAwId && reqAwId != Guid.Empty
            ? reqAwId
            : (Guid?)null;
        if (pinnedWorkflowId is Guid awId)
        {
            await ContractWorkflowGuards.EnsureValidWorkflowForContractAsync(
                db, awId, request.ContractType, ct);
        }
        else
        {
            pinnedWorkflowId =
                await ContractWorkflowGuards.ResolveDefaultWorkflowAsync(db, request.ContractType, ct)
                ?? throw new ConflictException(
                    $"Chưa chọn quy trình duyệt, và loại HĐ '{request.ContractType}' cũng chưa có quy trình " +
                    "mặc định (đang hoạt động + đã ghim cho người dùng chọn) — chọn quy trình trong hộp thoại, " +
                    "hoặc nhờ Admin ghim quy trình cho loại HĐ này.");
        }

        // ---- [vá-6] Nạp Project + Supplier ĐỦ (cần `Code` cho codegen RG-001) — 2 guard NotFound
        //      theo khuôn bridge cũ `CreateContractFromEvaluationFeatures.cs:63-66/:85-86`.
        //      loose-Guid ⇒ không có FK vật lý đỡ lưng, thiếu guard = NRE lúc gen mã.
        var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == plan.ProjectId, ct)
            ?? throw new NotFoundException("Project", plan.ProjectId);
        var supplierId = supplierIds[0];
        var supplier = await db.Suppliers.FirstOrDefaultAsync(s => s.Id == supplierId, ct)
            ?? throw new NotFoundException("Supplier", supplierId);

        // ---- [vá-6] Pin V1 `WorkflowDefinitionId` NGANG bridge cũ `:108` + `ContractFeatures.cs:105`.
        //      Cả 2 cùng set là ĐÚNG ("Cả 2 set ok" `:107-108`): `ContractWorkflowService` dispatch
        //      V2 trước. Để null thì HĐ rơi về policy hardcode `ContractFeatures.cs:647-662` khi
        //      ai đó gỡ pin V2 — mất luôn đường về quy trình đã cấu hình cho loại HĐ đó.
        var activeWfId = await db.WorkflowDefinitions.AsNoTracking()
            .Where(w => w.ContractType == request.ContractType && w.IsActive)
            .Select(w => (Guid?)w.Id)
            .FirstOrDefaultAsync(ct);

        // PE nguồn: TRACKED (cần ghi `ContractId` fallback bên dưới) + lấy tên gói cho tên HĐ mặc
        // định. loose-Guid ⇒ có thể null (PE bị xoá cứng) — mọi chỗ dùng đều null-safe.
        var pe = await db.PurchaseEvaluations
            .FirstOrDefaultAsync(e => e.Id == plan.PurchaseEvaluationId, ct);

        var giaTri = lines.Sum(l => l.ApprovedAmount!.Value);

        var tenHangMuc = string.Join(", ", lines
            .Select(l => l.TenHangMuc)
            .Where(t => !string.IsNullOrWhiteSpace(t))
            .Distinct());
        var tenMacDinh = string.IsNullOrWhiteSpace(tenHangMuc)
            ? (pe?.TenGoiThau ?? plan.MaKeHoach ?? "Hợp đồng")
            : $"{pe?.TenGoiThau ?? plan.MaKeHoach} — {tenHangMuc}";
        var tenHopDong = request.TenHopDong ?? tenMacDinh;
        // Clamp theo EF max length (500) — tên tự ghép từ N hạng mục có thể vượt.
        if (tenHopDong.Length > 500) tenHopDong = tenHopDong[..500];

        var contract = new Contract
        {
            Type = request.ContractType,
            Phase = ContractPhase.DangSoanThao,
            SupplierId = supplierId,
            ProjectId = plan.ProjectId,
            DepartmentId = plan.DepartmentId,
            DrafterUserId = actorId,
            GiaTri = giaTri,
            TenHopDong = tenHopDong,
            NoiDung = pe?.MoTa,
            // NS tham chiếu: Σ số PE (snapshot lúc lập kế hoạch), KHÔNG phải số đã chốt — cột
            // "ngân sách nhập tay" của HĐ là mốc so sánh, giá trị HĐ nằm ở `GiaTri`.
            BudgetManualName = plan.MaKeHoach is null ? null : $"NS tham chiếu KHKK {plan.MaKeHoach}",
            BudgetManualAmount = lines.Sum(l => l.PeReferenceAmount),
            WorkflowDefinitionId = activeWfId,                       // [vá-6]
            // [S187 W4] pin V2 — giá trị đã qua cửa 2/4: hoặc người bấm chọn (đã validate đủ 5 vế),
            // hoặc máy tự tìm theo loại. Không bao giờ null tới đây (nhánh else đã ném 409).
            ApprovalWorkflowId = pinnedWorkflowId,
            // [vá-6] SlaDeadline — thiếu thì HĐ sinh từ cầu này là HĐ DUY NHẤT trong hệ không có
            // hạn phase (cả 2 tiền lệ `ContractFeatures.cs:110` + bridge cũ `:109-110` đều set).
            SlaDeadline = DateTime.UtcNow.Add(
                workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)),
        };

        // Codegen TỰ-COMMIT (SERIALIZABLE + `SaveChangesAsync` nội bộ — `ContractCodeGenerator`):
        // gọi TRƯỚC `Add` để lượt save nội bộ của nó không flush HĐ dở (bài S88, đúng thứ tự
        // bridge cũ `:112-113` và `CreateContractSigningPlanCommandHandler:426-429`).
        contract.MaHopDong = await codeGenerator.GenerateAsync(contract, project.Code, supplier.Code, ct);

        db.Contracts.Add(contract);

        db.ContractChangelogs.Add(new ContractChangelog
        {
            ContractId = contract.Id,
            EntityType = ChangelogEntityType.Contract,
            Action = ChangelogAction.Insert,
            PhaseAtChange = contract.Phase,
            UserId = actorId,
            Summary = $"Tạo HĐ {contract.MaHopDong} từ Kế hoạch ký kết {plan.MaKeHoach ?? plan.Id.ToString()}",
            ContextNote = $"ContractSigningPlan {plan.Id} — {lines.Count} dòng",
        });
        db.ContractSigningPlanChangelogs.Add(new ContractSigningPlanChangelog
        {
            ContractSigningPlanId = plan.Id,
            EntityType = ContractSigningPlanEntityType.Line,
            Action = ChangelogAction.Update,
            PhaseAtChange = plan.Phase,
            UserId = actorId,
            Summary = $"Đưa {lines.Count} dòng vào HĐ {contract.MaHopDong}",
            ContextNote = $"Contract {contract.Id}",
        });

        // ---- [vá-4a] WRITE-SITE `Line.ContractId` — NGUỒN THẬT của đường KHKK → HĐ.
        foreach (var l in lines) l.ContractId = contract.Id;

        // ---- [vá-4a] `pe.ContractId` giữ vai FALLBACK HIỂN THỊ (cây 4-folder đọc ô đơn này).
        //      `??=` CÓ CHIỀU: chỉ điền khi còn trống ⇒ HĐ đầu tiên của gói thắng, các HĐ sau
        //      KHÔNG ghi đè (chúng vẫn nhìn thấy được qua `Line.ContractId` — đó là lý do vá-4a
        //      tồn tại). Bridge PE cũ gán thẳng `= contracts[0].Id` sau khi đã chặn ở `:59-60`,
        //      nên ở ĐÂY phải là `??=`: 2 cầu cùng chạy trên 1 PE thì cầu chạy sau không được
        //      cướp ô của cầu chạy trước.
        if (pe is not null) pe.ContractId ??= contract.Id;

        await db.SaveChangesAsync(ct);
        return new CreateContractFromSigningPlanResult(contract.Id, contract.MaHopDong);
    }
}
