import { useCallback, useEffect, useMemo, useState } from "react";
import {
  confirmPlanLayin,
  dismissPlan,
  fetchPlans,
  fetchPlatforms,
  restorePlan,
  sendPlanDecision
} from "../../api/tguApi";
import type { TguPlan, TguPlanDecision, TguPlatform } from "../../model/tguTypes";
import type { TguActiveTab, TguPlanUi, TimelineRange } from "../../model/timelineTypes";
import { TguComplexPlanTab } from "../tgu-complex-plan/TguComplexPlanTab";
import { ConstellationTab } from "../constellation/ConstellationTab";
import { TguEditorTab } from "../tgu-editor/TguEditorTab";
import { Tgu2DMapTab } from "../tgu-map-2d/Tgu2DMapTab";
import { TguRequestsTab } from "../tgu-requests/TguRequestsTab";
import { TguSlotsTab } from "../tgu-slots/TguSlotsTab";
import {
  abandonDraft,
  createDraft,
  fetchDraftPlans,
  fetchZrvWindows,
  promoteDraft,
  type ZrvWindowResponse
} from "../../api/tguPlanApi";
import { fetchPlanModes } from "../tgu-editor/editorApi";
import { CreateChainPlanDialog } from "./CreateChainPlanDialog";
import { TguPlanDetails, type PlanModeCounts } from "./TguPlanDetails";
import { TguPlanningLayout } from "./TguPlanningLayout";
import { TguSidebar } from "./TguSidebar";
import { TguTimeline, type ZrvWindow } from "./TguTimeline";
import { TguToolbar } from "./TguToolbar";
import { TguZrvPanel } from "./TguZrvPanel";
import {
  buildPlatformsForInterval,
  buildTimelineRows,
  isOperationalPlatform,
  mapPlans,
  operationalSpacecraftIds,
  pausedSpacecraftIds
} from "./tguTimelineMapper";
import { parseLocal, toLocalInputValue } from "../../utils/localTime";

const DAY_MS = 24 * 60 * 60 * 1000;
const MINUTE_MS = 60 * 1000;

export function TguPlanningPage() {
  const initialRange = useMemo(defaultRange, []);
  const [fromValue, setFromValue] = useState(toLocalInputValue(initialRange.fromMs));
  const [toValue, setToValue] = useState(toLocalInputValue(initialRange.toMs));
  const [appliedRange, setAppliedRange] = useState<TimelineRange>(initialRange);
  const [rawPlatforms, setRawPlatforms] = useState<TguPlatform[]>([]);
  const [plans, setPlans] = useState<TguPlanUi[]>([]);
  const [loading, setLoading] = useState(false);
  const [pageError, setPageError] = useState<string>();
  const [platformLoadFailed, setPlatformLoadFailed] = useState(false);
  const [search, setSearch] = useState("");
  const [selectedSpacecraftId, setSelectedSpacecraftId] = useState<string>();
  const [selectedPlanId, setSelectedPlanId] = useState<string>();
  const [activeTab, setActiveTab] = useState<TguActiveTab>("timeline");
  const [createChainOpen, setCreateChainOpen] = useState(false);
  // Черновики (DRAFT) — план-кандидаты, скрытые из основного read-path; грузим отдельно.
  const [drafts, setDrafts] = useState<TguPlanUi[]>([]);
  const [draftActionInFlight, setDraftActionInFlight] = useState(false);
  // Тумблеры видимости «непринятого будущего» на схеме.
  const [showDrafts, setShowDrafts] = useState(false);
  const [showProjection, setShowProjection] = useState(true);
  // ЗРВ выбранного КА — подсветка доступных зон на таймлайне (на какие зоны можно опереться).
  const [zrvWindows, setZrvWindows] = useState<ZrvWindowResponse[]>([]);
  // Выбор ЗРВ на таймлайне: пересекающиеся в точке зоны + индекс активной (панель справа).
  const [zrvSelection, setZrvSelection] = useState<{ windows: ZrvWindow[]; index: number }>();
  // Вкладки рендерятся в режиме keep-alive: каждую монтируем при первом открытии
  // и дальше держим смонтированной (скрытой через display:none), чтобы её
  // внутреннее состояние — загруженные заявки, выбор, слои, скролл — не сбрасывалось
  // при переключении между вкладками.
  const [visitedTabs, setVisitedTabs] = useState<ReadonlySet<TguActiveTab>>(() => new Set(["timeline"]));
  const [decisionInFlight, setDecisionInFlight] = useState(false);
  const [decisionNotice, setDecisionNotice] = useState<string>();
  const [decisionError, setDecisionError] = useState<string>();

  const parsedInputRange = useMemo(
    () => ({
      fromMs: parseLocal(fromValue),
      toMs: parseLocal(toValue)
    }),
    [fromValue, toValue]
  );
  const invalidRange =
    !Number.isFinite(parsedInputRange.fromMs) ||
    !Number.isFinite(parsedInputRange.toMs) ||
    parsedInputRange.fromMs >= parsedInputRange.toMs;

  const loadData = useCallback(
    async (range: TimelineRange) => {
      setLoading(true);
      setPageError(undefined);
      setDecisionError(undefined);

      const historyDays = historyDaysForRange(range);
      const [platformResult, planResult, draftResult] = await Promise.allSettled([
        fetchPlatforms(),
        fetchPlans(historyDays),
        fetchDraftPlans()
      ]);

      // На морде работаем только с эксплуатируемыми КА — остальные отсекаем сразу
      // на входе, чтобы они не всплывали ни в списке, ни как fallback-строки по планам.
      const operationalPlatforms =
        platformResult.status === "fulfilled" ? platformResult.value.filter(isOperationalPlatform) : [];
      const operationalIds = operationalSpacecraftIds(operationalPlatforms);

      if (platformResult.status === "rejected") {
        setPlatformLoadFailed(true);
      } else {
        setPlatformLoadFailed(false);
        setRawPlatforms(operationalPlatforms);
      }

      if (planResult.status === "rejected") {
        setPlans([]);
        setPageError(planResult.reason instanceof Error ? planResult.reason.message : "Не удалось загрузить планы.");
      } else {
        setPlans(mapPlans(planResult.value, operationalPlatforms).filter((plan) => operationalIds.has(plan.spacecraftId)));
      }

      // Черновики читаем из pcp-tgu-service отдельно; сбой непрозрачен — просто нет черновиков.
      if (draftResult.status === "fulfilled") {
        setDrafts(
          mapPlans(draftResult.value as unknown as TguPlan[], operationalPlatforms)
            .filter((plan) => operationalIds.has(plan.spacecraftId))
        );
      } else {
        setDrafts([]);
      }

      setLoading(false);
    },
    []
  );

  useEffect(() => {
    void loadData(appliedRange);
  }, [appliedRange, loadData]);

  useEffect(() => {
    setVisitedTabs((prev) => (prev.has(activeTab) ? prev : new Set(prev).add(activeTab)));
  }, [activeTab]);

  // ЗРВ выбранного КА для подсветки на таймлайне. Сбой непрозрачен — просто нет подсветки.
  useEffect(() => {
    if (!selectedSpacecraftId) {
      setZrvWindows([]);
      return;
    }
    let cancelled = false;
    void fetchZrvWindows(selectedSpacecraftId)
      .then((windows) => {
        if (!cancelled) setZrvWindows(windows);
      })
      .catch(() => {
        if (!cancelled) setZrvWindows([]);
      });
    return () => {
      cancelled = true;
    };
  }, [selectedSpacecraftId]);

  const zrvBands = useMemo(() => {
    if (!selectedSpacecraftId || zrvWindows.length === 0) return undefined;
    return {
      spacecraftId: selectedSpacecraftId,
      windows: zrvWindows.map((zrv) => ({
        startMs: parseLocal(zrv.startTime),
        endMs: parseLocal(zrv.endTime),
        kppId: zrv.kppId
      }))
    };
  }, [selectedSpacecraftId, zrvWindows]);

  // Сбрасываем выбранную ЗРВ при смене КА (зоны перечитываются) — панель не должна показывать чужие.
  useEffect(() => {
    setZrvSelection(undefined);
  }, [selectedSpacecraftId]);

  const selectedZrvId = useMemo(() => {
    if (!zrvSelection) return undefined;
    const zrv = zrvSelection.windows[zrvSelection.index];
    return zrv ? `${zrv.kppId}|${zrv.startMs}` : undefined;
  }, [zrvSelection]);

  // display:contents для активной вкладки сохраняет grid-раскладку shell
  // (внутренний <main> остаётся прямым grid-элементом), display:none скрывает остальные.
  const tabStyle = (tab: TguActiveTab) =>
    ({ display: activeTab === tab ? "contents" : "none" }) as const;

  // Все планы для выбора/поиска (включая черновики), независимо от тумблеров видимости.
  const allPlans = useMemo(() => [...plans, ...drafts], [plans, drafts]);
  // Планы для отрисовки на таймлайне с учётом тумблеров: проекция PLANNED и черновики.
  const visiblePlans = useMemo(() => {
    const base = showProjection ? plans : plans.filter((plan) => plan.status !== "PLANNED");
    return showDrafts ? [...base, ...drafts] : base;
  }, [plans, drafts, showProjection, showDrafts]);

  // КА с цепочкой на паузе после REJECTED (см. pausedSpacecraftIds): считаем по persisted-планам,
  // черновики/проекция паузу не снимают. Совпадает с серверной паузой воркера выдачи.
  const paused = useMemo(() => pausedSpacecraftIds(plans), [plans]);

  const platforms = useMemo(
    () => buildPlatformsForInterval(rawPlatforms, allPlans, appliedRange),
    [rawPlatforms, allPlans, appliedRange]
  );
  const rows = useMemo(
    () => buildTimelineRows(visiblePlans, platforms, appliedRange, selectedSpacecraftId),
    [visiblePlans, platforms, appliedRange, selectedSpacecraftId]
  );
  const selectedPlan = useMemo(() => {
    const plan = allPlans.find((item) => item.planId === selectedPlanId);
    if (!plan) return undefined;
    const platform = platforms.find((item) => item.spacecraftId === plan.spacecraftId);
    return { ...plan, platform };
  }, [allPlans, platforms, selectedPlanId]);

  // Состав включений выбранного плана (счётчики съёмок/сбросов) для правой панели.
  // planId == mission_id, поэтому грузим режимы плана тем же эндпоинтом, что и редактор.
  // Зависим и от activeTab: при возврате на вкладку «Планы» перечитываем состав, иначе
  // счётчик маршрутов остаётся прежним после правок в редакторе (selectedPlanId не менялся).
  const [planModeCounts, setPlanModeCounts] = useState<PlanModeCounts>();
  useEffect(() => {
    if (!selectedPlanId || activeTab !== "timeline") {
      setPlanModeCounts(undefined);
      return;
    }
    let cancelled = false;
    setPlanModeCounts(undefined);
    void fetchPlanModes(selectedPlanId)
      .then((modes) => {
        if (cancelled) return;
        setPlanModeCounts({
          survey: modes.filter((mode) => mode.type === "SURVEY").length,
          drop: modes.filter((mode) => mode.type === "DROP").length
        });
      })
      .catch(() => {
        if (!cancelled) setPlanModeCounts(undefined);
      });
    return () => {
      cancelled = true;
    };
  }, [selectedPlanId, activeTab]);

  const applyRange = () => {
    setDecisionNotice(undefined);
    if (invalidRange) {
      setPageError("Некорректный интервал: from должен быть меньше to.");
      return;
    }
    setPageError(undefined);
    setAppliedRange(parsedInputRange);
  };

  const quickRange = (hours: number) => {
    const fromMs = startOfCurrentDayMs();
    const toMs = fromMs + hours * 60 * 60 * 1000;
    setFromValue(toLocalInputValue(fromMs));
    setToValue(toLocalInputValue(toMs));
    setAppliedRange({ fromMs, toMs });
    setPageError(undefined);
    setDecisionNotice(undefined);
  };

  const handleDecision = async (planId: string, decision: TguPlanDecision, reason?: string) => {
    setDecisionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);

    try {
      await sendPlanDecision(planId, decision, reason);
      setDecisionNotice(
        decision === "REJECTED"
          ? "Решение REJECTED отправлено. План может вернуться в PLANNED для повторной выдачи."
          : "Решение ACCEPTED отправлено."
      );
      await loadData(appliedRange);
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось отправить решение.");
    } finally {
      setDecisionInFlight(false);
    }
  };

  // ТЕСТ: подтвердить закладку выбранного плана (AWAITING_LAYIN → LAID_IN) через тест-контроллер.
  const handleConfirmLayin = async (planId: string) => {
    setDecisionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);
    try {
      await confirmPlanLayin(planId);
      setDecisionNotice("Закладка подтверждена (LAID_IN). План стал базой цепочки.");
      await loadData(appliedRange);
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось подтвердить закладку.");
    } finally {
      setDecisionInFlight(false);
    }
  };

  // Гард ±10 мин: нельзя подтвердить закладку, если по КА уже заложен (LAID_IN) план со стартом
  // в пределах 10 минут. Дублирует бэкенд-гард, чтобы заранее погасить кнопку с подсказкой.
  const layinConflict = useMemo(() => {
    if (!selectedPlan || selectedPlan.status !== "AWAITING_LAYIN") return false;
    const windowMs = 10 * 60_000;
    return allPlans.some(
      (item) =>
        item.planId !== selectedPlan.planId &&
        item.spacecraftId === selectedPlan.spacecraftId &&
        item.status === "LAID_IN" &&
        Math.abs(item.startMs - selectedPlan.startMs) < windowMs
    );
  }, [allPlans, selectedPlan]);

  // Открыть черновик в редакторе: выбираем его план/КА и переключаемся на вкладку «Редактор».
  const handleEditDraft = (planId: string) => {
    const draft = drafts.find((item) => item.planId === planId);
    setSelectedPlanId(planId);
    if (draft) setSelectedSpacecraftId(draft.spacecraftId);
    setDecisionNotice(undefined);
    setActiveTab("editor");
  };

  // Отправить черновик вручную (promote): уходит в выдачу, рабочим станет после приёмки.
  const handlePromoteDraft = async (planId: string) => {
    setDraftActionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);
    try {
      await promoteDraft(planId);
      setDecisionNotice("Черновик отправлен на выдачу. Рабочим (якорем цепочки) он станет после приёмки.");
      await loadData(appliedRange);
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось отправить черновик.");
    } finally {
      setDraftActionInFlight(false);
    }
  };

  // Создать черновик-копию на основе существующего плана (replace, окно 1:1, все включения).
  // Это путь «сделать исправленный план», а не повторная отправка того же. В редактор сами
  // не уходим — оператор выберет черновик и перейдёт в «Редактор» вручную.
  const handleCreateDraftFromPlan = async (planId: string) => {
    // Один открытый черновик на слот (КА + окно): replace-черновик копирует окно исходного плана
    // 1:1, а его basePlanId теперь = база исходного (предшественник), а не сам исходный план —
    // поэтому ищем уже созданный черновик по совпадению окна, а не по basePlanId.
    const source = allPlans.find((item) => item.planId === planId);
    const existing = source
      ? drafts.find(
          (item) =>
            item.spacecraftId === source.spacecraftId &&
            item.startMs === source.startMs &&
            item.endMs === source.endMs
        )
      : undefined;
    if (existing) {
      setShowDrafts(true);
      setSelectedSpacecraftId(existing.spacecraftId);
      setSelectedPlanId(existing.planId);
      setDecisionNotice("Черновик на основе этого плана уже создан — выбран на схеме.");
      return;
    }
    setDraftActionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);
    try {
      const { draftPlanId } = await createDraft(planId, { mode: "replace" });
      await loadData(appliedRange);
      const base = allPlans.find((item) => item.planId === planId);
      setShowDrafts(true);
      if (base) setSelectedSpacecraftId(base.spacecraftId);
      setSelectedPlanId(draftPlanId);
      setDecisionNotice("Черновик создан и выбран на схеме. Откройте «Редактор», чтобы поправить и отправить его.");
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось создать черновик на основе плана.");
    } finally {
      setDraftActionInFlight(false);
    }
  };

  // Удалить черновик (abandon): убирает DRAFT и его клон-миссию.
  const handleDeleteDraft = async (planId: string) => {
    setDraftActionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);
    try {
      await abandonDraft(planId);
      setSelectedPlanId(undefined);
      setDecisionNotice("Черновик удалён.");
      await loadData(appliedRange);
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось удалить черновик.");
    } finally {
      setDraftActionInFlight(false);
    }
  };

  // Снять/вернуть план с учёта: косметико-статистический флаг, не трогает статус и цепочку.
  const handleDismissPlan = async (planId: string) => {
    setDraftActionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);
    try {
      await dismissPlan(planId);
      setDecisionNotice("План снят с учёта — больше не считается проблемой КА.");
      await loadData(appliedRange);
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось снять план с учёта.");
    } finally {
      setDraftActionInFlight(false);
    }
  };

  const handleRestorePlan = async (planId: string) => {
    setDraftActionInFlight(true);
    setDecisionError(undefined);
    setDecisionNotice(undefined);
    try {
      await restorePlan(planId);
      setDecisionNotice("План возвращён на учёт.");
      await loadData(appliedRange);
    } catch (error) {
      setDecisionError(error instanceof Error ? error.message : "Не удалось вернуть план на учёт.");
    } finally {
      setDraftActionInFlight(false);
    }
  };

  return (
    <TguPlanningLayout
      toolbar={
        <TguToolbar
          fromValue={fromValue}
          toValue={toValue}
          loading={loading}
          activeTab={activeTab}
          error={pageError}
          onTabChange={setActiveTab}
          onFromChange={setFromValue}
          onToChange={setToValue}
          onApply={applyRange}
          onQuickRange={quickRange}
        />
      }
    >
      <div style={tabStyle("timeline")}>
        <main className="tgu-workspace">
          <TguSidebar
            platforms={platforms}
            selectedSpacecraftId={selectedSpacecraftId}
            search={search}
            platformLoadFailed={platformLoadFailed}
            onSearchChange={setSearch}
            onSelectSpacecraft={(spacecraftId) => {
              setSelectedSpacecraftId(spacecraftId);
              setSelectedPlanId(undefined);
              setDecisionNotice(undefined);
            }}
          />
          <section className="tgu-center">
            <div className="tgu-center__actions">
              <label className="tgu-center__toggle">
                <input type="checkbox" checked={showDrafts} onChange={(e) => setShowDrafts(e.target.checked)} />
                Черновики
              </label>
              <label className="tgu-center__toggle">
                <input type="checkbox" checked={showProjection} onChange={(e) => setShowProjection(e.target.checked)} />
                Возможные будущие планы
              </label>
              <button
                className="tgu-button tgu-button--primary"
                type="button"
                disabled={!selectedSpacecraftId}
                title={selectedSpacecraftId ? "Создать план новой цепочки" : "Сначала выберите КА"}
                onClick={() => setCreateChainOpen(true)}
              >
                Создать план
              </button>
            </div>
            <TguTimeline
              rows={rows}
              range={appliedRange}
              invalidRange={invalidRange}
              zrvBands={zrvBands}
              selectedZrvId={selectedZrvId}
              pausedSpacecraftIds={paused}
              selectedPlanId={selectedPlanId}
              onSelectPlan={(planId) => {
                setZrvSelection(undefined);
                setDecisionNotice(undefined);
                setDecisionError(undefined);
                setSelectedPlanId(planId);
                const plan = allPlans.find((item) => item.planId === planId);
                if (plan) {
                  setSelectedSpacecraftId(plan.spacecraftId);
                }
              }}
              onSelectZrv={(windows, index) => {
                setSelectedPlanId(undefined);
                setDecisionNotice(undefined);
                setDecisionError(undefined);
                setZrvSelection({ windows, index });
              }}
            />
          </section>
          {zrvSelection ? (
            <TguZrvPanel
              windows={zrvSelection.windows}
              selectedIndex={zrvSelection.index}
              onSelect={(index) => setZrvSelection((current) => (current ? { ...current, index } : current))}
              onClose={() => setZrvSelection(undefined)}
            />
          ) : (
            <TguPlanDetails
              plan={selectedPlan}
              modeCounts={planModeCounts}
              chainPaused={selectedPlan ? paused.has(selectedPlan.spacecraftId) : false}
              decisionInFlight={decisionInFlight}
              notice={decisionNotice}
              error={decisionError}
              onDecision={handleDecision}
              draftActionInFlight={draftActionInFlight}
              onEditDraft={handleEditDraft}
              onPromoteDraft={handlePromoteDraft}
              onDeleteDraft={handleDeleteDraft}
              onCreateDraftFromPlan={handleCreateDraftFromPlan}
              onConfirmLayin={handleConfirmLayin}
              layinConflict={layinConflict}
              onDismissPlan={handleDismissPlan}
              onRestorePlan={handleRestorePlan}
              onClose={() => setSelectedPlanId(undefined)}
            />
          )}
        </main>
        {createChainOpen && selectedSpacecraftId && (
          <CreateChainPlanDialog
            spacecraftId={selectedSpacecraftId}
            plans={plans}
            initialBasePlanId={selectedPlanId}
            onClose={() => setCreateChainOpen(false)}
            onCreated={() => {
              setShowDrafts(true);
              void loadData(appliedRange);
            }}
            onEdit={(draftPlanId) => {
              setCreateChainOpen(false);
              setSelectedPlanId(draftPlanId);
              setDecisionNotice(undefined);
              setActiveTab("editor");
            }}
          />
        )}
      </div>

      {visitedTabs.has("map") && (
        <div style={tabStyle("map")}>
          <Tgu2DMapTab
            range={appliedRange}
            invalidRange={invalidRange}
            selectedPlan={selectedPlan}
            platforms={platforms}
          />
        </div>
      )}

      {visitedTabs.has("complex") && (
        <div style={tabStyle("complex")}>
          <TguComplexPlanTab
            range={appliedRange}
            platforms={platforms}
          />
        </div>
      )}

      {visitedTabs.has("requests") && (
        <div style={tabStyle("requests")}>
          <TguRequestsTab
            range={appliedRange}
            invalidRange={invalidRange}
            platforms={platforms}
          />
        </div>
      )}

      {visitedTabs.has("slots") && (
        <div style={tabStyle("slots")}>
          <TguSlotsTab range={appliedRange} />
        </div>
      )}

      {visitedTabs.has("constellation") && (
        <div style={tabStyle("constellation")}>
          <ConstellationTab />
        </div>
      )}

      {visitedTabs.has("editor") && (
        <div style={tabStyle("editor")}>
          <TguEditorTab
            selectedSpacecraftId={selectedPlan?.spacecraftId ?? selectedSpacecraftId}
            selectedPlan={selectedPlan}
            appliedRange={appliedRange}
            platforms={platforms}
          />
        </div>
      )}
    </TguPlanningLayout>
  );
}

function defaultRange(): TimelineRange {
  const now = Date.now();
  return { fromMs: now - 30 * MINUTE_MS, toMs: now + 3 * DAY_MS };
}

function startOfCurrentDayMs(): number {
  const now = new Date();
  return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
}

function historyDaysForRange(range: TimelineRange): number {
  const daysBack = Math.ceil(Math.max(0, Date.now() - range.fromMs) / DAY_MS);
  return Math.max(1, daysBack + 1);
}
