using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SolutionErp.Application.Common.Models;
using SolutionErp.Application.ContractSigningPlans;
using SolutionErp.Application.ContractSigningPlans.Services;
using SolutionErp.Domain.ContractSigningPlans;
using SolutionErp.Domain.Contracts;              // ContractType (cầu GĐ2 → GĐ3, K7)

namespace SolutionErp.Api.Controllers;

// [W2 — S161 2026-07-29] REST "Kế hoạch ký kết HĐ" (GĐ2).
//
// 🔴 AUTHZ 2 TẦNG (gotcha #82 — menu-flag ≠ API-authz, reviewer-adversarial bắt S118):
//   tầng 1 = class `[Authorize(Policy = "KeHoachKyKet.Read")]` (mọi endpoint tối thiểu Read)
//   tầng 2 = per-action override `KeHoachKyKet.{Create|Update|Delete}` — GHI ĐỦ TRÊN MỌI
//            action, kể cả action chỉ-đọc (viết lại Read cho tường minh): `[Authorize]` trần
//            hoặc để trống = lỗ hổng, và "ẩn menu" KHÔNG đóng được API.
//   4 policy `KeHoachKyKet.*` sinh runtime vì `KeHoachKyKet` ∈ `MenuKeys.All`
//   (`MenuKeys.cs:42,180`) × `MenuKeys.Actions` trong `Api/Program.cs`. Key KHÔNG nằm
//   trong `All` ⇒ 500 lúc chạy (policy không tồn tại), không phải 403.
//
// [W3 đợt-1 — S161] ĐÃ CÓ endpoint transition (đính chính dòng cũ "KHÔNG có ... đó là W3"):
// `POST {id}/transitions` mở ở cuối file. Đợt-1 mới là SKELETON — service ném
// NotImplementedException ⇒ endpoint trả 500 cho tới khi đợt-3 điền logic. CỐ Ý: test-before.
// GlobalExceptionMiddleware map exception → ProblemDetails ⇒ TUYỆT ĐỐI không try-catch ở đây.
[ApiController]
[Route("api/contract-signing-plans")]
[Authorize(Policy = "KeHoachKyKet.Read")]
public class ContractSigningPlansController(
    IMediator mediator,
    IContractSigningPlanWorkflowService workflow) : ControllerBase
{
    // ========================= Đọc =========================

    [HttpGet]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<ActionResult<PagedResult<ContractSigningPlanListItemDto>>> List(
        [FromQuery] ContractSigningPlanPhase? phase = null,
        [FromQuery] Guid? projectId = null,
        [FromQuery] Guid? purchaseEvaluationId = null,
        [FromQuery] bool pendingMe = false,
        [FromQuery(Name = "group")] int? group = null,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string? search = null,
        [FromQuery] bool sortDesc = true,
        CancellationToken ct = default)
        => Ok(await mediator.Send(
            new ListContractSigningPlansQuery(phase, projectId, purchaseEvaluationId, pendingMe, group)
            { Page = page, PageSize = pageSize, Search = search, SortDesc = sortDesc }, ct));

    /// Inbox "chờ tôi duyệt" — wire sẵn cho W3 (W2 trả rỗng vì chưa có đường trình duyệt).
    [HttpGet("inbox")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<ActionResult<List<ContractSigningPlanListItemDto>>> Inbox(CancellationToken ct)
        => Ok(await mediator.Send(new GetMyContractSigningPlanInboxQuery(), ct));

    /// Màn "Đã xoá" — CHỈ XEM. [K4b S166] +`group` mirror List `:42` (8 leaf Đã-xóa per-nhóm).
    [HttpGet("deleted")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<ActionResult<PagedResult<ContractSigningPlanListItemDto>>> ListDeleted(
        [FromQuery(Name = "group")] int? group = null,
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string? search = null,
        [FromQuery] bool sortDesc = true,
        CancellationToken ct = default)
        => Ok(await mediator.Send(new ListDeletedContractSigningPlansQuery(group)
        { Page = page, PageSize = pageSize, Search = search, SortDesc = sortDesc }, ct));

    /// Picker màn tạo: phiếu Duyệt NCC đã duyệt, có NCC trúng thầu, chưa có kế hoạch sống.
    [HttpGet("approved-pe-awaiting-plan")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<ActionResult<List<ApprovedPeAwaitingPlanDto>>> ListApprovedPeAwaitingPlan(
        CancellationToken ct)
        => Ok(await mediator.Send(new ListApprovedPeAwaitingPlanQuery(), ct));

    [HttpGet("{id:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<ActionResult<ContractSigningPlanDetailDto>> Get(Guid id, CancellationToken ct)
        => Ok(await mediator.Send(new GetContractSigningPlanQuery(id), ct));

    /// [S171 BE-5] "Lịch sử thay đổi" panel 3 mục 7.
    // 🔴 `KeHoachKyKet.Read` GHI TƯỜNG MINH đúng key của endpoint (gotcha #85 — KHÔNG OR key con).
    // ⚠️ KHÔNG chép khuôn PE: `PurchaseEvaluationsController.cs:15` `[Authorize]` TRẦN + `:281`
    //    endpoint lịch-sử KHÔNG policy nào ⇒ đăng nhập bất kỳ là đọc được lịch sử phiếu (#82
    //    đang treo bên đó). Handler còn bịt thêm tầng hàng bằng `EnsureCanViewAsync`.
    //    🔸 Comment này CỐ Ý không viết lại chuỗi route: thước B-5 đếm literal, chú-thích nhắc tên
    //       sẽ bơm tử-số (đúng lớp citation-trap mà B-6 vừa phải sửa).
    [HttpGet("{id:guid}/changelogs")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<ActionResult<List<ContractSigningPlanChangelogDto>>> ListChangelogs(
        Guid id, [FromQuery] int take = 200, CancellationToken ct = default)
        => Ok(await mediator.Send(new ListContractSigningPlanChangelogsQuery(id, take), ct));

    // ========================= Ghi header =========================

    [HttpPost]
    [Authorize(Policy = "KeHoachKyKet.Create")]
    public async Task<ActionResult<CreateContractSigningPlanResult>> Create(
        [FromBody] CreateContractSigningPlanCommand cmd, CancellationToken ct)
    {
        var result = await mediator.Send(cmd, ct);
        return CreatedAtAction(nameof(Get), new { id = result.Id }, result);
    }

    [HttpPut("{id:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<IActionResult> UpdateDraft(
        Guid id, [FromBody] UpdateContractSigningPlanDraftBody body, CancellationToken ct)
    {
        await mediator.Send(new UpdateContractSigningPlanDraftCommand(
            id, body.GhiChu, body.HoSoLink, body.ApprovalWorkflowId), ct);
        return NoContent();
    }

    // [S168 K8-blocker] Gán hạng mục SP-002 cho DÒNG — mảnh Lines-editor (comment auto-gen hẹn
    // K4b, chưa từng land; submit-guard vá-5 chặn null ⇒ thiếu route này phiếu mới kẹt Nháp).
    // Ghi vào phiếu ⇒ policy Update (khuôn dossier-items dưới — "tạo" ở module này = tạo PHIẾU).
    [HttpPut("{id:guid}/lines/{lineId:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<IActionResult> UpdateLine(
        Guid id, Guid lineId, [FromBody] UpdateSigningPlanLineBody body, CancellationToken ct)
    {
        await mediator.Send(new UpdateContractSigningPlanLineCommand(
            id, lineId, body.CatalogEntryId, body.ProposedAmount, body.Note), ct);
        return NoContent();
    }

    /// [S175] Thêm dòng hạng mục. 🔴 Policy `Update` chứ KHÔNG phải `Create`: "tạo" ở module này
    /// nghĩa là tạo PHIẾU (khuôn dossier-items `:142`); thêm dòng là SỬA phiếu đang soạn.
    /// Gotcha #85 — dùng ĐÚNG key của endpoint, không OR key con.
    [HttpPost("{id:guid}/lines")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<ActionResult<Guid>> AddLine(
        Guid id, [FromBody] AddSigningPlanLineBody body, CancellationToken ct)
    {
        var lineId = await mediator.Send(new AddContractSigningPlanLineCommand(
            id, body.SupplierId, body.CatalogEntryId, body.ProposedAmount, body.Note), ct);
        return Ok(lineId);
    }

    /// [S175] Xoá dòng hạng mục THÊM TAY (handler chặn dòng gốc sinh từ phiếu Duyệt NCC).
    [HttpDelete("{id:guid}/lines/{lineId:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<IActionResult> DeleteLine(Guid id, Guid lineId, CancellationToken ct)
    {
        await mediator.Send(new DeleteContractSigningPlanLineCommand(id, lineId), ct);
        return NoContent();
    }

    [HttpDelete("{id:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Delete")]
    public async Task<IActionResult> Delete(
        Guid id, [FromQuery] string? reason = null, CancellationToken ct = default)
    {
        await mediator.Send(new DeleteContractSigningPlanCommand(id, reason), ct);
        return NoContent();
    }

    // ================= Căn cứ b.8-9 (dossier items) =================
    // Ghi vào phiếu ⇒ policy Update (KHÔNG Create): "tạo" ở module này = tạo PHIẾU.

    [HttpPost("{id:guid}/dossier-items")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<ActionResult<object>> CreateDossierItem(
        Guid id, [FromBody] UpsertContractSigningPlanDossierItemCommand cmd, CancellationToken ct)
    {
        if (id != cmd.ContractSigningPlanId)
            return BadRequest(new { detail = "ID kế hoạch trên đường dẫn không khớp dữ liệu gửi lên." });
        var newId = await mediator.Send(cmd with { Id = null }, ct);
        return Ok(new { id = newId });
    }

    [HttpPut("{id:guid}/dossier-items/{itemId:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<ActionResult<object>> UpdateDossierItem(
        Guid id, Guid itemId,
        [FromBody] UpsertContractSigningPlanDossierItemCommand cmd, CancellationToken ct)
    {
        if (id != cmd.ContractSigningPlanId)
            return BadRequest(new { detail = "ID kế hoạch trên đường dẫn không khớp dữ liệu gửi lên." });
        var savedId = await mediator.Send(cmd with { Id = itemId }, ct);
        return Ok(new { id = savedId });
    }

    [HttpDelete("{id:guid}/dossier-items/{itemId:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<IActionResult> DeleteDossierItem(Guid id, Guid itemId, CancellationToken ct)
    {
        await mediator.Send(new DeleteContractSigningPlanDossierItemCommand(id, itemId), ct);
        return NoContent();
    }

    // ===================== Đính kèm (mở mọi phase) =====================

    [HttpPost("{id:guid}/attachments")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    [RequestSizeLimit(25_000_000)]
    public async Task<ActionResult<ContractSigningPlanAttachmentDto>> UploadAttachment(
        Guid id,
        IFormFile file,
        [FromForm] Guid? dossierItemId = null,
        [FromForm] ContractSigningPlanAttachmentPurpose purpose = ContractSigningPlanAttachmentPurpose.DossierScan,
        [FromForm] string? note = null,
        CancellationToken ct = default)
    {
        if (file is null || file.Length == 0)
            return BadRequest(new { detail = "Chưa chọn file." });

        await using var stream = file.OpenReadStream();
        var dto = await mediator.Send(new UploadContractSigningPlanAttachmentCommand(
            id, dossierItemId, file.FileName, file.ContentType, file.Length, stream, purpose, note), ct);
        return Ok(dto);
    }

    /// [S171 BE-7 — owner chốt 2026-08-04] File người DUYỆT đính kèm trong dialog Duyệt/Trả lại/
    /// Từ chối (panel 3 mục 5, Ảnh 2). Đường RIÊNG thay vì bắt FE tự truyền `purpose=3`:
    ///   • `Purpose` ép cứng `ApprovalAttachment` ở BE ⇒ FE không đặt nhầm mục đích, và khối
    ///     "📎 File đính kèm khi duyệt" đọc theo purpose không lẫn với file căn cứ b.8-9;
    ///   • `dossierItemId` KHÔNG nhận (file duyệt không treo vào căn cứ nào).
    // 🔴 Policy `KeHoachKyKet.Update` — đây là đường GHI (gotcha #85: đúng key của endpoint, KHÔNG
    //    hạ xuống Read cho tiện, KHÔNG OR key con). Người duyệt cần quyền Update để bấm Duyệt
    //    (`/transitions` cùng policy) ⇒ đính kèm lúc duyệt không đẻ thêm rào mới cho họ.
    [HttpPost("{id:guid}/approval-attachments")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    [RequestSizeLimit(25_000_000)]
    public async Task<ActionResult<ContractSigningPlanAttachmentDto>> UploadApprovalAttachment(
        Guid id,
        IFormFile file,
        [FromForm] string? note = null,
        CancellationToken ct = default)
    {
        if (file is null || file.Length == 0)
            return BadRequest(new { detail = "Chưa chọn file." });

        await using var stream = file.OpenReadStream();
        var dto = await mediator.Send(new UploadContractSigningPlanAttachmentCommand(
            id, DossierItemId: null, file.FileName, file.ContentType, file.Length, stream,
            ContractSigningPlanAttachmentPurpose.ApprovalAttachment, note), ct);
        return Ok(dto);
    }

    [HttpGet("{id:guid}/attachments/{attId:guid}/download")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<IActionResult> DownloadAttachment(Guid id, Guid attId, CancellationToken ct)
    {
        var f = await mediator.Send(new DownloadContractSigningPlanAttachmentQuery(id, attId), ct);
        return File(f.Content, f.ContentType, f.FileName);
    }

    /// Xem inline (PDF iframe / ảnh) — cùng handler download, khác Content-Disposition.
    [HttpGet("{id:guid}/attachments/{attId:guid}/view")]
    [Authorize(Policy = "KeHoachKyKet.Read")]
    public async Task<IActionResult> ViewAttachment(Guid id, Guid attId, CancellationToken ct)
    {
        var f = await mediator.Send(new DownloadContractSigningPlanAttachmentQuery(id, attId), ct);
        // [W2 S161 — reviewer F-11] KHÔNG nội suy FileName thô vào header: tên có dấu/ký tự `"`
        // làm header non-ASCII/vỡ cú pháp. ContentDispositionHeaderValue tự mã hoá RFC 6266
        // (filename*) — cùng cơ chế MVC dùng cho đường /download.
        var cd = new Microsoft.Net.Http.Headers.ContentDispositionHeaderValue("inline");
        cd.SetHttpFileName(f.FileName);
        Response.Headers.ContentDisposition = cd.ToString();
        return File(f.Content, f.ContentType);
    }

    [HttpDelete("{id:guid}/attachments/{attId:guid}")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<IActionResult> DeleteAttachment(Guid id, Guid attId, CancellationToken ct)
    {
        await mediator.Send(new DeleteContractSigningPlanAttachmentCommand(id, attId), ct);
        return NoContent();
    }

    // ===================== Duyệt 3 trạm (W3) =====================
    // 🔴 HỢP-ĐỒNG LITERAL (lead chốt @S161 — 2 lane BE/FE khớp từng tên field):
    //   body     { "action": "submit"|"approve"|"return"|"reject", "comment": string|null }
    //   response { "phase": int, "currentWorkflowStepIndex": int|null,
    //              "currentApprovalLevelOrder": int|null }
    // Policy `KeHoachKyKet.Update` (KHÔNG Create/Delete): duyệt = GHI vào phiếu đã có.
    // Đi THẲNG service (không qua MediatR) vì đây là 1 thao tác state-machine, không phải
    // CQRS command — mirror `ContractsController` gọi `IContractWorkflowService`.

    [HttpPost("{id:guid}/transitions")]
    [Authorize(Policy = "KeHoachKyKet.Update")]
    public async Task<ActionResult<ContractSigningPlanTransitionResult>> Transition(
        Guid id, [FromBody] ContractSigningPlanTransitionBody body, CancellationToken ct)
        => Ok(await workflow.TransitionAsync(id, body.Action, body.Comment,
            body.ApplyLevelFinalize ?? true, ct));

    // ===================== Cầu GĐ2 → GĐ3 (K7 — S167) =====================
    // 🔴 [vá-1 spec-cụm-3 §②-1] AUTHZ = HỘI 2 KHOÁ, cả 2 vế đều phải chặn được:
    //   vế 1 (class, dòng :28) `KeHoachKyKet.Read`  — không được xem kế hoạch thì không được
    //                                                  lấy dòng của nó đi tạo HĐ;
    //   vế 2 (action, ngay dưới) `Contracts.Create` — tài nguyên SINH RA là Contract ⇒ khoá phải
    //                                                  là khoá của thứ được tạo (gotcha #85:
    //                                                  gate bằng đúng key policy của endpoint).
    // ASP.NET Core cộng dồn [Authorize] class + action theo AND ⇒ thiếu 1 trong 2 = 403.
    // ⚠️ `Contracts.Create` trước S167 KHÔNG role nào có (seeder `SeedAllRolesReviewReadPermissionsAsync`
    // đặt `CanCreate = isPe` ⇒ false cho key `Contracts` — grep tên hàm, ĐỪNG neo số dòng: file này
    // dài ra là số thối [gate-K7 F-8 bắt neo cũ sai ngay lúc land]); `SeedContractCreateAccessAsync` cấp cho
    // Drafter + Procurement + Admin — grant đó ăn @restart, KHÔNG phải deploy FE.
    // ⚠️ KHÔNG động tới `POST /api/contracts` (`ContractsController` `[Authorize]` trần, #82 treo
    // chủ đích): siết ở đây là siết CỬA MỚI, không phải sửa cửa cũ.
    [HttpPost("{id:guid}/create-contract")]
    [Authorize(Policy = "Contracts.Create")]
    public async Task<ActionResult<CreateContractFromSigningPlanResult>> CreateContract(
        Guid id, [FromBody] CreateContractFromSigningPlanBody body, CancellationToken ct)
    {
        var result = await mediator.Send(new CreateContractFromSigningPlanCommand(
            id, body.LineIds ?? [], body.ContractType, body.ApprovalWorkflowId, body.TenHopDong), ct);
        return Created($"/api/contracts/{result.ContractId}", result);
    }

    // ========================= Body records =========================
    // PUT header: KHÔNG mang `Id` trong body (id lấy từ route) — tránh 2 nguồn sự thật.
    // Mọi field nullable = null-safe: client không gửi ⇒ giữ giá trị cũ (#73).
    public record UpdateContractSigningPlanDraftBody(
        string? GhiChu = null,
        string? HoSoLink = null,
        Guid? ApprovalWorkflowId = null);

    // Transition: `Action` BẮT BUỘC (thiếu ⇒ service ném 409 "Hành động không hợp lệ"),
    // `Comment` tuỳ chọn. Tên property PascalCase bind đúng JSON "action"/"comment" —
    // ASP.NET Core mặc định camelCase + đọc không phân biệt hoa/thường
    // (`Api/Program.cs:33-34` chỉ thêm converter DateTime, KHÔNG đổi naming policy).
    // [K3 S165] `ApplyLevelFinalize` — OPT-OUT khuôn PE S96: null/true = auto kết-thúc-tại-cấp
    // khi Cấp có `AllowApproverFinalize`; false = người duyệt BỎ tick ⇒ trình tiếp CEO.
    public record ContractSigningPlanTransitionBody(
        string Action,
        string? Comment = null,
        bool? ApplyLevelFinalize = null);

    // [S168 K8-blocker] Body gán hạng mục cho dòng — PlanId/LineId lấy từ route (1 nguồn sự thật).
    // [S175] +2 field null-safe: `null` = client không gửi ⇒ giữ nguyên. `CatalogEntryId` đổi
    // sang nullable ⇒ call-site FE cũ (gửi đúng 1 field) KHÔNG phải sửa.
    public record UpdateSigningPlanLineBody(
        Guid? CatalogEntryId = null,
        decimal? ProposedAmount = null,
        string? Note = null);

    /// [S175] Thêm dòng hạng mục cho NCC ĐÃ CÓ trên phiếu — anh chốt "nhiều hạng mục → giá và ghi chú".
    public record AddSigningPlanLineBody(
        Guid SupplierId,
        Guid CatalogEntryId,
        decimal ProposedAmount,
        string? Note = null);

    // [K7 S167] Cầu → HĐ. `PlanId` lấy từ route (không nhận trong body — 1 nguồn sự thật).
    // `LineIds` nullable ở BODY để client gửi thiếu ra 400 của validator ("chọn ít nhất 1 dòng")
    // thay vì 500 NullReference ở tầng dưới.
    // [S187 W4] `ApprovalWorkflowId` NULLABLE: client không gửi ⇒ máy tự pin quy trình của đúng
    // loại HĐ (họ `HD-*`). FE hiện VẪN gửi (dialog bắt chọn) ⇒ payload cũ chạy y nguyên; đây là
    // nới rộng, không phải đổi hợp-đồng. Gửi bậy vẫn 409 với câu nêu đích danh mã quy trình.
    public record CreateContractFromSigningPlanBody(
        List<Guid>? LineIds,
        ContractType ContractType,
        Guid? ApprovalWorkflowId = null,
        string? TenHopDong = null);
}
