// [W7 KHKK — S161 2026-07-29] "Hợp đồng cứng" (GĐ4) — mô hình 1 MỐC.
// Anh chốt @S161 (verbatim): "... Upload file cứng lên -> Là xem như xong" ⇒ ký GĐ
// (b.19) · đóng dấu (b.20) · thủ tục lưu/phát hành (b.21) diễn ra NGOÀI hệ thống trên
// GIẤY. Hệ thống chỉ ghi nhận BẰNG CHỨNG CUỐI: 1 file scan bộ HĐ đã ký + đóng dấu
// (`AttachmentPurpose.SealedCopy = 3`, có sẵn — 0 migration, 0 enum-extend).
//
// Khuôn: `pages/khkk/KhkkListPage.tsx` (W2 — ui/PageHeader + card-accent table +
// phân trang) & `components/ContractAttachmentsSection.tsx` (multipart upload HĐ).
// File MIRROR SHA256 identical với fe-admin counterpart.
//
// URL vào từ menu (Layout.tsx staticMap — chỗ thứ 4 của Pattern 16-bis):
//   Hdc_ThauPhu … Hdc_NguyenTacDv → /hard-copies?type=1 … ?type=7
//   HopDongCung (root) có 7 con ⇒ render MenuGroup, path KHÔNG dùng — nhánh
//   "không query = tất cả loại" chỉ tới được bằng gõ URL tay (review W7 FLAG-2).
// 🔴 Lọc loại HĐ đi qua THAM SỐ SERVER `?type=` (review F-C1/F-05) — KHÔNG lọc
// client-side trên trang `pageSize` (sẽ mất HĐ khi 1 loại vượt trần trang).
import { Fragment, useRef, useState, type ChangeEvent, type DragEvent } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useSearchParams } from 'react-router-dom'
import {
  Archive, ChevronDown, ChevronRight, Download, FileText, Inbox,
  PenLine, Search, ShieldCheck, Upload,
} from 'lucide-react'
import { toast } from 'sonner'
import { PageHeader } from '@/components/ui/PageHeader'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
import { PipelineTreePanel } from '@/components/pipeline/PipelineTreePanel'
import { api, TOKEN_KEY } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError'
import { cn } from '@/lib/cn'
import {
  ContractPhase,
  type ContractAttachment,
  type ContractDetail,
  type ContractListItem,
} from '@/types/contracts'
import { ContractTypeLabel } from '@/types/forms'
import type { Paged } from '@/types/master'

const PAGE_SIZE = 20
// Mirror BE `AttachmentPurpose.SealedCopy` (Domain/Contracts/ContractAttachment.cs:5-11).
// 🔴 KHÔNG dùng `ScannedSigned = 2` — nghĩa của nó là "scan có chữ ký NCC ở phase
// Đang in ký" (gotcha #71), không phải bộ cứng đã đóng dấu.
const SEALED_COPY = 3
const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '') + '/api'

function formatVnd(n: number | null | undefined): string {
  if (n === null || n === undefined) return '—'
  return n.toLocaleString('vi-VN') + ' đ'
}

function formatDateTime(iso: string): string {
  return new Date(iso).toLocaleString('vi-VN', { dateStyle: 'short', timeStyle: 'short' })
}

function fmtSize(n: number): string {
  if (n < 1024) return `${n} B`
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
  return `${(n / (1024 * 1024)).toFixed(1)} MB`
}

export function HardCopiesPage() {
  const [searchParams] = useSearchParams()
  const rawType = searchParams.get('type')
  const parsedType = rawType === null ? Number.NaN : Number(rawType)
  const typeFilter = Number.isFinite(parsedType) && parsedType > 0 ? parsedType : null

  const [search, setSearch] = useState('')
  const [page, setPage] = useState(1)
  const [openId, setOpenId] = useState<string | null>(null)

  const list = useQuery({
    queryKey: ['hard-copies', { type: typeFilter, search, page }],
    queryFn: async () =>
      (
        await api.get<Paged<ContractListItem>>('/contracts', {
          params: {
            // GĐ4 chỉ theo dõi HĐ ĐÃ PHÁT HÀNH (phase 9 = terminal của GĐ3).
            phase: ContractPhase.DaPhatHanh,
            type: typeFilter ?? undefined, // `?? undefined` ⇒ axios bỏ hẳn key
            search: search.trim() || undefined,
            page,
            pageSize: PAGE_SIZE,
          },
        })
      ).data,
  })

  const items = list.data?.items ?? []
  const total = list.data?.total ?? 0
  const totalPages = list.data?.totalPages ?? 1
  const typeLabel = typeFilter !== null ? ContractTypeLabel[typeFilter] : null

  return (
    <div className="space-y-5">
      <PageHeader
        eyebrow="Giai đoạn 4"
        title={typeLabel ? `Hợp đồng cứng — ${typeLabel}` : 'Hợp đồng cứng'}
        subtitle="Theo dõi bộ hợp đồng bản cứng của các HĐ đã phát hành"
        icon={<Archive className="h-5 w-5" />}
        accent="teal"
      />

      {/* Ghi chú 1-MỐC — nói rõ ký/đóng dấu/lưu là việc NGOÀI hệ thống, tránh user
          tưởng còn thiếu trạm (rủi ro §③-C của spec W7). */}
      <div className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-[13px] leading-relaxed text-amber-900">
        <span className="font-semibold">Ký giám đốc · đóng dấu · thủ tục lưu bộ gốc được thực hiện NGOÀI hệ thống (trên giấy).</span>{' '}
        Hệ thống chỉ ghi nhận bằng chứng cuối: tải lên bản scan bộ hợp đồng đã ký + đóng dấu.
        Có file scan là xem như <span className="font-semibold">xong</span>.
      </div>

      {/* [S162] Cây toàn trình bám trái (owner chốt AskUser 30-07: folder GĐ áp
          cả các trang giai đoạn khác) — folder "Hợp đồng cứng" mở sẵn ở đây. */}
      <div className="grid gap-5 lg:grid-cols-[19rem_minmax(0,1fr)] xl:grid-cols-[21rem_minmax(0,1fr)]">
        <PipelineTreePanel currentStage={4} />

        <div className="min-w-0 space-y-5">
      <div
        className="card-accent flex items-center gap-3 px-4 py-3"
        style={{ ['--accent' as string]: 'var(--color-teal-500)' }}
      >
        <Search className="h-4 w-4 shrink-0 text-slate-400" />
        <Input
          value={search}
          onChange={(e) => {
            setSearch(e.target.value)
            setPage(1)
          }}
          placeholder="Tìm mã HĐ, tên HĐ hoặc nhà cung cấp..."
          className="max-w-md border-0 bg-transparent px-0 shadow-none focus-visible:ring-0"
        />
      </div>

      <div
        className="card-accent overflow-hidden"
        style={{ ['--accent' as string]: 'var(--color-teal-500)' }}
      >
        {/* [S162] Cột trái ăn ~19rem ⇒ bảng cuộn ngang trong thẻ, không tràn lưới. */}
        <div className="overflow-x-auto">
        <table className="w-full text-sm">
          <thead className="border-b border-slate-200 bg-slate-50/70">
            <tr>
              <th className="label-eyebrow w-8 px-4 py-2.5 text-left" />
              <th className="label-eyebrow px-4 py-2.5 text-left">Mã HĐ</th>
              <th className="label-eyebrow px-4 py-2.5 text-left">Tên hợp đồng</th>
              <th className="label-eyebrow px-4 py-2.5 text-left">Nhà cung cấp</th>
              <th className="label-eyebrow px-4 py-2.5 text-left">Dự án</th>
              <th className="label-eyebrow px-4 py-2.5 text-right">Giá trị</th>
              <th className="label-eyebrow px-4 py-2.5 text-left">Bản cứng</th>
            </tr>
          </thead>
          <tbody>
            {list.isLoading && (
              <tr>
                <td colSpan={7} className="px-4 py-8 text-center text-slate-500">
                  Đang tải...
                </td>
              </tr>
            )}
            {!list.isLoading && items.length === 0 && (
              <tr>
                <td colSpan={7} className="px-4 py-10 text-center text-slate-500">
                  <span
                    className="icon-chip mx-auto mb-2 flex"
                    style={{ ['--chip-bg' as string]: '#f1f5f9', ['--chip-fg' as string]: '#94a3b8' }}
                    aria-hidden
                  >
                    <Inbox className="h-4 w-4" />
                  </span>
                  {typeLabel
                    ? `Chưa có ${typeLabel} nào được phát hành.`
                    : 'Chưa có hợp đồng nào được phát hành.'}
                </td>
              </tr>
            )}
            {items.map((c) => {
              const open = openId === c.id
              return (
                <Fragment key={c.id}>
                  <tr
                    onClick={() => setOpenId(open ? null : c.id)}
                    className={cn(
                      'cursor-pointer border-b border-slate-100 transition',
                      open ? 'bg-teal-50/60' : 'hover:bg-teal-50/40',
                    )}
                  >
                    <td className="px-4 py-2.5 text-slate-400">
                      {open ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
                    </td>
                    <td className="px-4 py-2.5 font-mono text-xs text-teal-700">{c.maHopDong ?? '—'}</td>
                    <td className="max-w-xs truncate px-4 py-2.5 font-medium text-slate-800">
                      {c.tenHopDong ?? '(chưa đặt tên)'}
                    </td>
                    <td className="max-w-[12rem] truncate px-4 py-2.5 text-xs text-slate-600">{c.supplierName}</td>
                    <td className="max-w-[12rem] truncate px-4 py-2.5 text-xs text-slate-600">{c.projectName}</td>
                    <td className="px-4 py-2.5 text-right text-xs tabular-nums text-slate-700">
                      {formatVnd(c.giaTri)}
                    </td>
                    <td className="px-4 py-2.5">
                      {/* Pattern 14 — class đầy đủ dạng literal (Tailwind JIT không
                          thấy chuỗi ghép). Badge derive 1-mốc từ `hasSealedCopy`. */}
                      {c.hasSealedCopy ? (
                        <span className="inline-flex items-center gap-1 rounded-md border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700">
                          <ShieldCheck className="h-3.5 w-3.5" />
                          Đã lưu bản cứng
                        </span>
                      ) : (
                        <span className="inline-flex items-center gap-1 rounded-md border border-slate-200 bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">
                          <Upload className="h-3.5 w-3.5" />
                          Chưa có bản cứng
                        </span>
                      )}
                    </td>
                  </tr>
                  {open && (
                    <tr className="border-b border-slate-100">
                      <td colSpan={7} className="bg-slate-50/70 px-4 py-4">
                        <HardCopyPanel contractId={c.id} />
                      </td>
                    </tr>
                  )}
                </Fragment>
              )
            })}
          </tbody>
        </table>
        </div>
        {totalPages > 1 && (
          <div className="flex items-center justify-between border-t border-slate-200 px-4 py-2.5 text-sm">
            <div className="text-slate-500">
              {total} hợp đồng — Trang {page} / {totalPages}
            </div>
            <div className="flex gap-1">
              <Button variant="outline" size="sm" disabled={page <= 1} onClick={() => setPage((p) => p - 1)}>
                Trước
              </Button>
              <Button
                variant="outline"
                size="sm"
                disabled={page >= totalPages}
                onClick={() => setPage((p) => p + 1)}
              >
                Sau
              </Button>
            </div>
          </div>
        )}
      </div>
        </div>
      </div>
    </div>
  )
}

// ===== Panel mở dưới mỗi hàng — upload 1 purpose + danh sách file + chữ ký duyệt =====

function HardCopyPanel({ contractId }: { contractId: string }) {
  const qc = useQueryClient()
  const inputRef = useRef<HTMLInputElement>(null)
  const [dragging, setDragging] = useState(false)
  const [note, setNote] = useState('')

  // Dùng CHÍNH queryKey `['contract', id]` của module HĐ ⇒ mọi nơi invalidate
  // (ContractAttachmentsSection…) đều làm panel này tươi theo.
  const detail = useQuery({
    queryKey: ['contract', contractId],
    queryFn: async () => (await api.get<ContractDetail>(`/contracts/${contractId}`)).data,
  })

  const upload = useMutation({
    mutationFn: async (file: File) => {
      // Endpoint CÓ SẴN: POST /api/contracts/{id}/attachments (multipart) —
      // field name khớp controller `IFormFile file` + `[FromForm] purpose/note`
      // (ContractsController.cs:87-92). Không đẻ endpoint mới.
      const form = new FormData()
      form.append('file', file)
      form.append('purpose', String(SEALED_COPY))
      if (note.trim()) form.append('note', note.trim())
      return (
        await api.post(`/contracts/${contractId}/attachments`, form, {
          headers: { 'Content-Type': 'multipart/form-data' },
        })
      ).data
    },
    onSuccess: () => {
      setNote('')
      qc.invalidateQueries({ queryKey: ['contract', contractId] })
      qc.invalidateQueries({ queryKey: ['hard-copies'] }) // badge cột "Bản cứng" lật
      qc.invalidateQueries({ queryKey: ['pipeline-contract-index'] }) // [F-7 S162] badge GĐ4 trên cây lật cùng
      toast.success('Đã lưu bản cứng')
    },
    onError: (err) => toast.error(`Tải lên lỗi: ${getErrorMessage(err)}`),
  })

  function handleFiles(files: FileList | null) {
    if (!files || files.length === 0) return
    for (const f of Array.from(files)) upload.mutate(f)
  }

  function onDrop(e: DragEvent<HTMLDivElement>) {
    e.preventDefault()
    setDragging(false)
    handleFiles(e.dataTransfer.files)
  }

  function onPick(e: ChangeEvent<HTMLInputElement>) {
    handleFiles(e.target.files)
    e.target.value = '' // cho phép chọn lại đúng file vừa chọn
  }

  async function download(att: ContractAttachment) {
    const token = localStorage.getItem(TOKEN_KEY)
    const res = await fetch(`${BASE_URL}/contracts/${contractId}/attachments/${att.id}/download`, {
      headers: token ? { Authorization: `Bearer ${token}` } : {},
    })
    if (!res.ok) {
      toast.error(`Tải xuống lỗi (HTTP ${res.status})`)
      return
    }
    const blob = await res.blob()
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = att.fileName
    document.body.appendChild(a)
    a.click()
    a.remove()
    URL.revokeObjectURL(url)
  }

  const sealed = (detail.data?.attachments ?? []).filter((a) => a.purpose === SEALED_COPY)
  const opinions = detail.data?.levelOpinions ?? []

  return (
    <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_22rem]">
      <div className="space-y-3">
        <div
          onDragOver={(e) => {
            e.preventDefault()
            setDragging(true)
          }}
          onDragLeave={() => setDragging(false)}
          onDrop={onDrop}
          onClick={() => inputRef.current?.click()}
          className={cn(
            'cursor-pointer rounded-lg border-2 border-dashed px-4 py-6 text-center transition',
            dragging ? 'border-teal-500 bg-teal-50' : 'border-slate-300 bg-white hover:bg-slate-50',
          )}
        >
          <Upload className="mx-auto h-5 w-5 text-slate-400" />
          <div className="mt-2 text-sm font-medium text-slate-600">
            Kéo thả bản scan vào đây hoặc <span className="text-teal-700">chọn file</span>
          </div>
          <div className="mt-0.5 text-xs text-slate-400">
            Bộ HĐ đã ký + đóng dấu · PDF / DOCX / XLSX / PNG / JPG · tối đa 20 MB
          </div>
          <input
            ref={inputRef}
            type="file"
            multiple
            onChange={onPick}
            className="hidden"
            accept=".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.webp"
          />
          {upload.isPending && <div className="mt-2 text-xs text-teal-700">Đang tải lên…</div>}
        </div>

        <div>
          <label className="label-eyebrow mb-1 block">Ghi chú (tùy chọn)</label>
          <Textarea
            rows={2}
            value={note}
            onChange={(e) => setNote(e.target.value)}
            placeholder="Số bộ gốc · đã giao CCM/NTP · nơi lưu…"
          />
          <p className="mt-1 text-[11px] text-slate-400">
            Ghi chú đính theo file sắp tải lên — bỏ trống cũng được.
          </p>
        </div>

        <div className="rounded-lg border border-slate-200 bg-white">
          <div className="flex items-center gap-2 border-b border-slate-100 px-3 py-2 text-xs font-semibold text-slate-600">
            <FileText className="h-3.5 w-3.5" />
            Bản scan đã lưu ({sealed.length})
          </div>
          {detail.isLoading && <div className="px-3 py-4 text-xs text-slate-500">Đang tải…</div>}
          {!detail.isLoading && sealed.length === 0 && (
            <div className="px-3 py-4 text-xs text-slate-500">
              Chưa có bản scan nào. Tải lên bản scan bộ HĐ đã ký + đóng dấu để hoàn tất.
            </div>
          )}
          {sealed.length > 0 && (
            <ul className="divide-y divide-slate-100">
              {sealed.map((a) => (
                <li key={a.id} className="flex items-center gap-3 px-3 py-2">
                  <div className="min-w-0 flex-1">
                    <div className="truncate text-[13px] font-medium text-slate-700">{a.fileName}</div>
                    <div className="text-[11px] text-slate-400">
                      {fmtSize(a.fileSize)} · {formatDateTime(a.createdAt)}
                    </div>
                    {a.note && <div className="mt-0.5 text-[11px] text-slate-500">Ghi chú: {a.note}</div>}
                  </div>
                  <button
                    onClick={() => download(a)}
                    className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-slate-500 transition hover:bg-slate-100 hover:text-slate-700"
                    title="Tải xuống"
                  >
                    <Download className="h-4 w-4" />
                  </button>
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>

      {/* Chữ ký duyệt điện tử từ GĐ3 — CHỈ ĐỌC (khớp "Duyệt qua (nếu có)" @S161).
          HĐ V1 legacy không pin workflow V2 ⇒ opinions rỗng ⇒ khối VẪN HIỆN với
          câu placeholder (không ẩn — review W7 FLAG-3). */}
      <div className="rounded-lg border border-slate-200 bg-white">
        <div className="flex items-center gap-2 border-b border-slate-100 px-3 py-2 text-xs font-semibold text-slate-600">
          <PenLine className="h-3.5 w-3.5" />
          Ý kiến cấp duyệt ({opinions.length})
        </div>
        {opinions.length === 0 ? (
          <div className="px-3 py-4 text-xs text-slate-500">
            Hợp đồng này không có chữ ký duyệt điện tử.
          </div>
        ) : (
          <ul className="divide-y divide-slate-100">
            {opinions.map((o) => (
              <li key={o.id} className="px-3 py-2">
                <div className="flex items-center justify-between gap-2">
                  <span className="truncate text-[13px] font-medium text-slate-700">
                    {o.approverFullName ?? '—'}
                  </span>
                  <span className="shrink-0 text-[11px] text-slate-400">{formatDateTime(o.signedAt)}</span>
                </div>
                <div className="text-[11px] text-slate-500">
                  {o.stepName}
                  {o.levelName ? ` · ${o.levelName}` : ` · Cấp ${o.levelOrder}`}
                </div>
                <div className="mt-0.5 text-[12px] text-slate-600">
                  {o.comment?.trim() ? o.comment : '(duyệt — không ý kiến)'}
                </div>
                {o.signedByUserId !== o.approverUserId && (
                  <div className="mt-1 inline-flex items-center rounded border border-amber-200 bg-amber-50 px-1.5 py-0.5 text-[11px] font-medium text-amber-800">
                    Admin duyệt thay ({o.signedByFullName ?? '—'})
                  </div>
                )}
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  )
}
