"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";

import { API_BASE_URL, apiFetch, apiFetchWithStatus } from "@/lib/api";
import type { components } from "@/lib/api-types";

// Reuse generated OpenAPI types (api-types.ts) — НЕ дублировать руками (#2259 PR-D).
export type ReportBuildResponse = components["schemas"]["ReportBuildResponse"];
export type ReportStatusResponse =
  components["schemas"]["ReportStatusResponse"];

/**
 * useFullReport — единый хук кнопки «Полный отчёт (PDF)» (#2259 PR-E).
 *
 * Переиспользуется двумя компонентами (ptica/reports/PticaReports «тёмная»
 * витрина экспортов + analysis/ExportButtons «светлая» страница) — вся логика
 * enqueue → poll → download живёт здесь, компоненты только рендерят состояние.
 *
 * Контракт бэкенда (PR-D #2287):
 *   • POST /api/v1/parcels/{cad}/report
 *       → 202 { status: "building" }  джоба поставлена, PDF собирается в фоне
 *       → 200 { status: "ready", ... } готовый кэш уже на диске, качать сразу
 *   • GET  /api/v1/parcels/{cad}/report/status
 *       → { status: "none" | "building" | "ready", report_generated_at?, ... }
 *   • GET  /api/v1/parcels/{cad}/report/download → application/pdf (attachment)
 *
 * «failed»-состояния в контракте НЕТ: если building висит дольше POLL_CAP_MS,
 * фронт сам останавливает поллинг и показывает «Сборка затянулась — попробуйте
 * позже» (uiState === "timeout"), кнопка снова активна.
 *
 * Скачивание: страница за Caddy basic_auth. Обычная навигация браузера к
 * /report/download наследует уже авторизованную сессию (cookie/basic-auth),
 * поэтому качаем через скрытый <a href download> (см. triggerReportDownload) —
 * не тянем blob отдельным fetch'ем, который пришлось бы отдельно авторизовывать.
 */

// Поллинг: тик каждые 5с, cap ~3 мин (36 тиков) → стоп + сообщение.
const POLL_INTERVAL_MS = 5_000;
const POLL_CAP_MS = 3 * 60 * 1000;

export type FullReportUiState =
  | "idle" // ничего не запускалось / ещё не готов
  | "building" // джоба идёт, поллим status
  | "ready" // PDF готов, есть report_generated_at
  | "timeout" // building провисел дольше cap — остановились
  | "error"; // POST enqueue упал

export interface UseFullReportResult {
  uiState: FullReportUiState;
  /** ISO дата генерации готового отчёта (для подписи «Скачать отчёт (дата)»). */
  reportGeneratedAt: string | null;
  /** Прошедшее с начала сборки время, мс (для «Готовится… мм:сс»). */
  elapsedMs: number;
  /** Клик по кнопке: enqueue → сразу download если ready, иначе старт поллинга. */
  start: () => void;
  /** Ручное скачивание готового PDF (кнопка «Скачать отчёт»). */
  download: () => void;
}

function buildReportPath(cad: string, suffix: string): string {
  return `/api/v1/parcels/${encodeURIComponent(cad)}/report${suffix}`;
}

/**
 * Навигация браузера к /report/download через скрытый <a download>. Наследует
 * Caddy basic_auth сессию текущей вкладки — отдельный авторизованный fetch не
 * нужен. Browser-only (использует document) — вызывать только из обработчика.
 */
function triggerReportDownload(cad: string): void {
  if (typeof document === "undefined") return;
  const a = document.createElement("a");
  a.href = `${API_BASE_URL}${buildReportPath(cad, "/download")}`;
  // Content-Disposition: attachment на бэкенде задаёт имя файла; download-атрибут
  // без значения лишь подтверждает намерение скачать, не переопределяя имя.
  a.download = "";
  a.rel = "noopener";
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

export function useFullReport(cad: string): UseFullReportResult {
  // Флаг активного поллинга — заводится на building, гасится на ready/timeout.
  const [polling, setPolling] = useState(false);
  const [timedOut, setTimedOut] = useState(false);
  const [elapsedMs, setElapsedMs] = useState(0);
  // Момент начала сборки — источник истины для elapsed и cap.
  const startedAtRef = useRef<number | null>(null);
  // На ready один раз авто-скачиваем — иначе повторные тики status дёргали бы
  // загрузку снова.
  const autoDownloadedRef = useRef(false);

  // ── POST enqueue ────────────────────────────────────────────────────────────
  const enqueue = useMutation({
    mutationFn: async (): Promise<ReportBuildResponse> => {
      // apiFetchWithStatus терпит 202 Accepted без throw'а (как async-джобы §22).
      const { body } = await apiFetchWithStatus<ReportBuildResponse>(
        buildReportPath(cad, ""),
        { method: "POST" },
      );
      return body;
    },
  });

  // ── GET status polling ────────────────────────────────────────────────────────
  const statusQuery = useQuery({
    queryKey: ["full-report-status", cad],
    queryFn: (): Promise<ReportStatusResponse> =>
      apiFetch<ReportStatusResponse>(buildReportPath(cad, "/status")),
    // enabled=false вне активной сборки → нет фонового поллинга и утечки после
    // ухода со страницы (unmount снимает подписку, а enabled сам гасит тик).
    enabled: !!cad && polling,
    // Тик каждые POLL_INTERVAL_MS пока building; на ready — стоп (false).
    refetchInterval: (query) =>
      query.state.data?.status === "ready" ? false : POLL_INTERVAL_MS,
    refetchIntervalInBackground: false,
    gcTime: 0,
  });

  const statusData = statusQuery.data;

  // ── Тикающий elapsed + cap-стоп (только пока идёт поллинг) ────────────────────
  useEffect(() => {
    if (!polling) return;
    if (startedAtRef.current === null) startedAtRef.current = Date.now();

    const tick = () => {
      const started = startedAtRef.current;
      if (started === null) return;
      const el = Date.now() - started;
      setElapsedMs(el);
      if (el >= POLL_CAP_MS) {
        setPolling(false);
        setTimedOut(true);
      }
    };
    tick();
    const id = window.setInterval(tick, 1000);
    // Cleanup на unmount / смене polling → интервал не течёт после ухода.
    return () => window.clearInterval(id);
  }, [polling]);

  // ── На ready: авто-download один раз + стоп поллинга ──────────────────────────
  useEffect(() => {
    if (statusData?.status === "ready" && polling) {
      setPolling(false);
      if (!autoDownloadedRef.current) {
        autoDownloadedRef.current = true;
        triggerReportDownload(cad);
      }
    }
  }, [statusData?.status, polling, cad]);

  // ── Смена участка: полный сброс прогона ──────────────────────────────────────
  // Оба потребителя живут под route-сегментом [cad] (смена участка = remount),
  // этот эффект — страховка на случай будущего in-place реюза хука с другим cad.
  useEffect(() => {
    setPolling(false);
    setTimedOut(false);
    setElapsedMs(0);
    autoDownloadedRef.current = false;
    startedAtRef.current = null;
  }, [cad]);

  // ── Действие: клик по кнопке ──────────────────────────────────────────────────
  const start = useCallback(() => {
    // Сброс прошлого прогона (retry после timeout/error).
    setTimedOut(false);
    setElapsedMs(0);
    startedAtRef.current = null;
    autoDownloadedRef.current = false;

    enqueue.mutate(undefined, {
      onSuccess: (data) => {
        if (data.status === "ready") {
          // Кэш готов — качаем сразу, поллинг не нужен.
          autoDownloadedRef.current = true;
          triggerReportDownload(cad);
        } else {
          // building → стартуем поллинг status.
          startedAtRef.current = Date.now();
          setPolling(true);
        }
      },
    });
  }, [enqueue, cad]);

  const download = useCallback(() => {
    triggerReportDownload(cad);
  }, [cad]);

  // ── Производное UI-состояние ──────────────────────────────────────────────────
  let uiState: FullReportUiState = "idle";
  if (statusData?.status === "ready" || enqueue.data?.status === "ready") {
    uiState = "ready";
  } else if (timedOut) {
    uiState = "timeout";
  } else if (polling || enqueue.isPending) {
    uiState = "building";
  } else if (enqueue.isError) {
    uiState = "error";
  }

  const reportGeneratedAt =
    (uiState === "ready" ? statusData?.report_generated_at : null) ?? null;

  return { uiState, reportGeneratedAt, elapsedMs, start, download };
}

// ── Хелперы микрокопии кнопки (общие для обоих компонентов) ────────────────────

/** «мм:сс» из миллисекунд — для «Готовится… 1:05». */
export function formatElapsed(ms: number): string {
  const totalSec = Math.floor(ms / 1000);
  const min = Math.floor(totalSec / 60);
  const sec = totalSec % 60;
  return `${min}:${String(sec).padStart(2, "0")}`;
}

/** «Скачать отчёт (дата)»; на fast-path из кэша даты ещё нет — без пустых скобок. */
export function readyReportLabel(iso: string | null): string {
  const d = formatReportDate(iso);
  return d ? `Скачать отчёт (${d})` : "Скачать отчёт";
}

/** ISO → «ru-RU» дата генерации отчёта (репо-конвенция, NaN-fallback). */
export function formatReportDate(iso: string | null): string {
  if (!iso) return "";
  const d = new Date(iso);
  // new Date(мусор) не бросает, а даёт Invalid Date — try/catch тут мёртв.
  if (Number.isNaN(d.getTime())) return "";
  return d.toLocaleDateString("ru-RU", {
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
  });
}
