import { describe, expect, it } from "vitest";
import { computeConflicts } from "./slotConflicts";

/** Минимальный слот: КА + окно [tn,tk] + крен + ключ. */
type S = { satelliteId: number; n: number; tn: string; tk: string; roll: number; state?: string };

const keyOf = (s: S) => `${s.satelliteId}:${s.n}`;

const pad = (v: number) => String(v).padStart(2, "0");
/** ISO-момент 10:MM:SS от секунд смещения относительно 10:00:00. */
function fmt(sec: number): string {
  return `2026-06-17T10:${pad(Math.floor(sec / 60))}:${pad(sec % 60)}`;
}
/** Слот КА на окне [startSec, endSec] (секунды от 10:00:00), с креном roll. */
function slot(satelliteId: number, n: number, startSec: number, endSec: number, roll: number, state = "AVAILABLE"): S {
  return { satelliteId, n, tn: fmt(startSec), tk: fmt(endSec), roll, state };
}

const notBlocked = () => false;
// Выбран ровно слот A (sat 1, n 1) — источник занятого интервала во всех кейсах.
const selectedIsA = (s: S) => s.satelliteId === 1 && s.n === 1;

// A: [0,30], крен 10.
const A = slot(1, 1, 0, 30, 10);

describe("computeConflicts (зазор на манёвр)", () => {
  it("без выбора — конфликтов нет", () => {
    const arr = [A, slot(1, 2, 38, 60, 10)];
    expect(computeConflicts(arr, arr, keyOf, () => false, notBlocked).size).toBe(0);
  });

  it("тот же крен: зазор < 10 с → конфликт, ≥ 10 с → ок", () => {
    const close = slot(1, 2, 38, 60, 10); // зазор 8 с → конфликт
    const ok = slot(1, 3, 41, 60, 10); // зазор 11 с → ок
    const arr = [A, close, ok];
    const res = computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked);
    expect(res.has("1:2")).toBe(true);
    expect(res.has("1:3")).toBe(false);
  });

  it("смена крена: тот же зазор 25 с конфликтует (нужно 30), при том же крене — нет (хватает 10)", () => {
    const diffRoll = slot(1, 4, 55, 80, 20); // зазор 25 с, крен сменился → конфликт
    const sameRoll = slot(1, 5, 55, 80, 10); // зазор 25 с, крен тот же → ок
    const arr = [A, diffRoll, sameRoll];
    const res = computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked);
    expect(res.has("1:4")).toBe(true);
    expect(res.has("1:5")).toBe(false);
  });

  it("смена крена: зазор ≥ 30 с → ок", () => {
    const ok = slot(1, 6, 61, 90, 20); // зазор 31 с → ок
    const arr = [A, ok];
    expect(computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked).has("1:6")).toBe(false);
  });

  it("пересечение по времени → конфликт; сам выбранный не помечается", () => {
    const overlap = slot(1, 7, 20, 50, 20); // перекрывает A
    const arr = [A, overlap];
    const res = computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked);
    expect(res.has("1:7")).toBe(true);
    expect(res.has("1:1")).toBe(false);
  });

  it("другой КА не конфликтует", () => {
    const other = slot(2, 8, 38, 60, 10); // тот же зазор, но КА 2
    const arr = [A, other];
    expect(computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked).has("2:8")).toBe(false);
  });

  it("кандидат ПЕРЕД выбранным тоже ловится (тот же крен, зазор < 10 с)", () => {
    const later = slot(1, 10, 100, 130, 10); // выбран, позже по времени
    const before = slot(1, 11, 88, 95, 10); // зазор 5 с до later → конфликт
    const farBefore = slot(1, 12, 50, 70, 10); // зазор 30 с до later → ок
    const arr = [later, before, farBefore];
    const res = computeConflicts(arr, arr, keyOf, (s) => s.n === 10, notBlocked);
    expect(res.has("1:11")).toBe(true);
    expect(res.has("1:12")).toBe(false);
  });

  it("бронь/BUSY исключены из конфликтов", () => {
    const busy = slot(1, 9, 38, 60, 10, "BUSY"); // зазор 8 с, но BUSY
    const arr = [A, busy];
    const res = computeConflicts(arr, arr, keyOf, selectedIsA, (s) => s.state === "BUSY");
    expect(res.has("1:9")).toBe(false);
  });

  it("слоты ОДНОЙ полосы между собой не конфликтуют (groupOf)", () => {
    const sameStrip = slot(1, 2, 38, 60, 10); // зазор 8 с, тот же крен — конфликт по умолчанию
    const arr = [A, sameStrip];
    // Без groupOf — конфликт.
    expect(computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked).has("1:2")).toBe(true);
    // A и sameStrip в одной полосе — конфликт снимается.
    const sameGroup = () => "polosa:1";
    expect(computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked, sameGroup).has("1:2")).toBe(false);
  });

  it("groupOf: разные полосы того же КА по-прежнему конфликтуют", () => {
    const other = slot(1, 2, 38, 60, 10); // зазор 8 с, тот же крен
    const arr = [A, other];
    const groupOf = (s: S) => (s.n === 1 ? "polosa:a" : "polosa:b");
    expect(computeConflicts(arr, arr, keyOf, selectedIsA, notBlocked, groupOf).has("1:2")).toBe(true);
  });
});
