"use client";

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

import { HTTPError, apiFetch, apiFetchWithStatus } from "@/lib/api";
import type { ParcelAnalysis } from "@/types/site-finder";

// #93 — on-demand cadastre fetch flow.
// Backend returns 202 Accepted when geometry needs fetching; frontend polls
// /fetch-status every 2s, re-triggers analyze когда status=ready.

export type FetchStatus =
  | "ready"
  | "fetching"
  | "failed"
  | "not_in_nspd"
  | "invalid_format";

export interface FetchStatusResponse {
  status: FetchStatus;
  job_id: number | null;
  error_msg: string | null;
  eta_seconds: number | null;
}

export interface AnalyzeAcceptedResponse {
  status: "fetching";
  cad_num: string;
  job_id: number;
  eta_seconds: number;
  message: string;
}

export type AnalyzeResult =
  | { kind: "ready"; data: ParcelAnalysis }
  | { kind: "fetching"; jobId: number; etaSeconds: number };

const POLL_INTERVAL_MS = 2000;
const POLL_MAX_ITERATIONS = 60; // 60 × 2s = 2 min hard cap

export interface AnalyzeOptions {
  /** If set, backend uses this saved profile for weights. */
  profileId?: number;
  /** If set together with no profileId, backend uses user's default profile. */
  profileUserId?: string;
  /**
   * Inline POI weights override — sent as request body.
   * Priority: inline → profileId → profileUserId default → system.
   */
  weights?: Record<string, number> | null;
}

/**
 * Custom hook для analyze flow с graceful on-demand fetch fallback.
 *
 * Состояния:
 * - idle: ничего не делали
 * - pending (mutation): первичный POST /analyze в полёте
 * - fetching: получили 202, идёт polling /fetch-status
 * - data set: ParcelAnalysis отрисовываем
 * - error: not_in_nspd / invalid_format / failed
 */
export function useSiteAnalysis() {
  const [fetchingState, setFetchingState] = useState<{
    cadNum: string;
    jobId: number;
    etaSeconds: number;
  } | null>(null);
  // ref для cancel polling — invalidates in-flight iteration
  const cancelledRef = useRef(false);

  const mutation = useMutation({
    mutationFn: async ({
      cad,
      options,
    }: {
      cad: string;
      options?: AnalyzeOptions;
    }): Promise<ParcelAnalysis> => {
      cancelledRef.current = false;
      setFetchingState(null);

      // Build optional query string for weight profile params.
      const qs = new URLSearchParams();
      if (options?.profileId != null) {
        qs.set("profile_id", String(options.profileId));
      }
      if (options?.profileUserId) {
        qs.set("profile_user_id", options.profileUserId);
      }
      const qsStr = qs.toString();
      const analyzeUrl = (cadNum: string) => {
        const base = `/api/v1/parcels/${encodeURIComponent(cadNum)}/analyze`;
        return qsStr ? `${base}?${qsStr}` : base;
      };

      // Build optional JSON body for inline weights (#201).
      const bodyPayload =
        options?.weights != null
          ? JSON.stringify({ weights: options.weights })
          : undefined;

      // First request — POST /analyze
      const first = await apiFetchWithStatus<
        ParcelAnalysis | AnalyzeAcceptedResponse
      >(analyzeUrl(cad), {
        method: "POST",
        ...(bodyPayload
          ? {
              body: bodyPayload,
              headers: { "Content-Type": "application/json" },
            }
          : {}),
      });

      if (first.status === 200) {
        return first.body as ParcelAnalysis;
      }

      // 202 Accepted — entering polling mode
      if (first.status === 202) {
        const accepted = first.body as AnalyzeAcceptedResponse;
        setFetchingState({
          cadNum: cad,
          jobId: accepted.job_id,
          etaSeconds: accepted.eta_seconds,
        });

        // Poll loop
        for (let i = 0; i < POLL_MAX_ITERATIONS; i++) {
          await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
          if (cancelledRef.current) {
            throw new Error("Загрузка отменена пользователем");
          }
          const status = await apiFetch<FetchStatusResponse>(
            `/api/v1/parcels/${encodeURIComponent(cad)}/fetch-status`,
          );
          if (status.status === "ready") {
            // Re-trigger analyze — should be 200 now.
            // setFetchingState(null) ПОСЛЕ await чтобы не было flicker:
            // если очистить до await, mutation isPending=true но
            // fetchingState=null → пустой экран ~1 RTT. После await
            // mutation сразу резолвится с data — render skipped.
            const second = await apiFetch<ParcelAnalysis>(analyzeUrl(cad), {
              method: "POST",
              ...(bodyPayload
                ? {
                    body: bodyPayload,
                    headers: { "Content-Type": "application/json" },
                  }
                : {}),
            });
            setFetchingState(null);
            return second;
          }
          if (status.status === "not_in_nspd") {
            setFetchingState(null);
            throw new HTTPError(
              404,
              status,
              status.error_msg ?? "Кадастровый номер не найден в НСПД",
            );
          }
          if (status.status === "failed") {
            setFetchingState(null);
            throw new HTTPError(
              503,
              status,
              status.error_msg ?? "НСПД временно недоступен",
            );
          }
          if (status.status === "invalid_format") {
            setFetchingState(null);
            throw new HTTPError(
              400,
              status,
              status.error_msg ?? "Неверный формат кадастрового номера",
            );
          }
          // status === "fetching" → continue polling
        }
        // Polling exhausted (2 min)
        setFetchingState(null);
        throw new Error(
          "Загрузка длится слишком долго (>2 мин). Попробуйте позже.",
        );
      }

      throw new Error(`Unexpected response status: ${first.status}`);
    },
  });

  const cancel = useCallback(() => {
    cancelledRef.current = true;
    setFetchingState(null);
    mutation.reset();
  }, [mutation]);

  return {
    ...mutation,
    fetchingState,
    cancel,
  };
}
