import { describe, expect, it, vi, afterEach } from "vitest";
import {
  buildOnlineBoardStartSeo,
  buildFlightSearchSeo,
  buildDepartureSearchSeo,
  buildArrivalSearchSeo,
  buildRouteSearchSeo,
  buildFlightDetailsSeo,
  buildFlightDetailsSeoFromId,
} from "./seo.js";
import type { ISimpleFlight } from "./types.js";

/** Stub t() that returns the key + interpolation vars for assertion. */
function stubT(key: string, opts?: Record<string, unknown>): string {
  if (opts && Object.keys(opts).length > 0) {
    const vars = Object.entries(opts)
      .map(([k, v]) => `${k}=${v}`)
      .join(",");
    return `${key}|${vars}`;
  }
  return key;
}

/**
 * Stub t() that resolves SHARED.TODAY / SHARED.TOMORROW to their Russian
 * equivalents so we can assert the rendered value directly.
 */
function stubTRu(key: string, opts?: Record<string, unknown>): string {
  if (key === "SHARED.TODAY") return "сегодня";
  if (key === "SHARED.TOMORROW") return "завтра";
  return stubT(key, opts);
}

const CANONICAL = "https://www.aeroflot.ru";

// ---------------------------------------------------------------------------
// formatDateForTitle — via buildFlightSearchSeo with frozen clock
// ---------------------------------------------------------------------------

describe("formatDateForTitle date branches", () => {
  afterEach(() => {
    vi.useRealTimers();
  });

  it("returns 'сегодня' when date matches today (local)", () => {
    // Freeze clock to 2026-05-15T12:00:00 local
    vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) });

    const params = {
      type: "flight" as const,
      carrier: "SU",
      flightNumber: "0100",
      date: "20260515",
    };
    const result = buildFlightSearchSeo(stubTRu, params, "ru", CANONICAL);

    expect(result.title).toContain("date=сегодня");
    // description must use dd.MM.yyyy, not "сегодня"
    expect(result.description).toContain("date=15.05.2026");
  });

  it("returns 'завтра' when date is today + 1", () => {
    vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) });

    const params = {
      type: "flight" as const,
      carrier: "SU",
      flightNumber: "0100",
      date: "20260516",
    };
    const result = buildFlightSearchSeo(stubTRu, params, "ru", CANONICAL);

    expect(result.title).toContain("date=завтра");
    expect(result.description).toContain("date=16.05.2026");
  });

  it("returns dd.MM.yyyy for an arbitrary past date", () => {
    vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) });

    const params = {
      type: "flight" as const,
      carrier: "SU",
      flightNumber: "0100",
      date: "20250115",
    };
    const result = buildFlightSearchSeo(stubTRu, params, "ru", CANONICAL);

    expect(result.title).toContain("date=15.01.2025");
  });

  it("accepts yyyy-MM-dd API shape for today", () => {
    vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) });

    const params = {
      type: "departure" as const,
      station: "SVO",
      date: "2026-05-15",
    };
    const result = buildDepartureSearchSeo(stubTRu, params, "ru", CANONICAL);

    expect(result.title).toContain("date=сегодня");
    expect(result.description).toContain("date=15.05.2026");
  });
});

// ---------------------------------------------------------------------------
// buildOnlineBoardStartSeo
// ---------------------------------------------------------------------------

describe("buildOnlineBoardStartSeo", () => {
  it("uses MAIN translation keys for title and description", () => {
    const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);

    expect(result.title).toBe("SEO.BOARD.MAIN.TITLE");
    expect(result.description).toBe("SEO.BOARD.MAIN.DESCRIPTION");
  });

  it("sets canonical to /{locale}/onlineboard", () => {
    const result = buildOnlineBoardStartSeo(stubT, "en", CANONICAL);

    expect(result.canonical).toBe("https://www.aeroflot.ru/en/onlineboard");
  });

  it("includes hreflang with 10 entries (9 langs + x-default)", () => {
    const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);

    expect(result.hreflang).toHaveLength(10);
  });

  it("sets og tags", () => {
    const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);

    expect(result.og.title).toBe(result.title);
    expect(result.og.description).toBe(result.description);
    expect(result.og.url).toBe(result.canonical);
    expect(result.og.type).toBe("website");
    expect(result.og.locale).toBe("ru");
    expect(result.og.siteName).toBe("Aeroflot");
    expect(result.og.image).toContain("aeroflot");
  });

  it("sets twitter card", () => {
    const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);

    expect(result.twitter).toBeDefined();
    expect(result.twitter!.card).toBe("summary");
  });

  it("emits WebSite JSON-LD on the start page", () => {
    const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL);

    expect(result.jsonLd).toBeDefined();
    const ld = result.jsonLd as Record<string, unknown>;
    expect(ld["@type"]).toBe("WebSite");
    expect(ld["url"]).toBe("https://www.aeroflot.ru/ru/onlineboard");
    expect(ld["name"]).toBe("Aeroflot");
  });
});

// ---------------------------------------------------------------------------
// buildFlightSearchSeo
// ---------------------------------------------------------------------------

describe("buildFlightSearchSeo", () => {
  const params = {
    type: "flight" as const,
    carrier: "SU",
    flightNumber: "0100",
    date: "20250115",
  };

  it("uses FLIGHT-SEARCH translation keys with interpolation", () => {
    const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("SEO.BOARD.FLIGHT-SEARCH.TITLE");
    expect(result.title).toContain("flightNumber=SU 0100");
    expect(result.description).toContain("SEO.BOARD.FLIGHT-SEARCH.DESCRIPTION");
  });

  it("title date uses formatDateForTitle; description uses dd.MM.yyyy", () => {
    // 20250115 is not today/tomorrow, so title date = "15.01.2025"
    const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("date=15.01.2025");
    expect(result.description).toContain("date=15.01.2025");
  });

  it("sets canonical to the flight search URL", () => {
    const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.canonical).toBe(
      "https://www.aeroflot.ru/ru/onlineboard/flight/SU0100-20250115",
    );
  });

  it("includes hreflang entries", () => {
    const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.hreflang).toHaveLength(10);
  });
});

// ---------------------------------------------------------------------------
// buildDepartureSearchSeo
// ---------------------------------------------------------------------------

describe("buildDepartureSearchSeo", () => {
  const params = {
    type: "departure" as const,
    station: "SVO",
    date: "20250115",
  };

  it("uses DEPARTURE-SEARCH translation keys with city name", () => {
    const result = buildDepartureSearchSeo(
      stubT,
      params,
      "ru",
      CANONICAL,
      { departure: "Moscow" },
    );

    expect(result.title).toContain("SEO.BOARD.DEPARTURE-SEARCH.TITLE");
    expect(result.title).toContain("departureCity=Moscow");
  });

  it("falls back to station code when no city name provided", () => {
    const result = buildDepartureSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("departureCity=SVO");
  });

  it("title date uses formatDateForTitle; description keeps dd.MM.yyyy", () => {
    const result = buildDepartureSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("date=15.01.2025");
    expect(result.description).toContain("date=15.01.2025");
  });

  it("sets canonical to the departure search URL", () => {
    const result = buildDepartureSearchSeo(stubT, params, "en", CANONICAL);

    expect(result.canonical).toBe(
      "https://www.aeroflot.ru/en/onlineboard/departure/SVO-20250115",
    );
  });
});

// ---------------------------------------------------------------------------
// buildArrivalSearchSeo
// ---------------------------------------------------------------------------

describe("buildArrivalSearchSeo", () => {
  const params = {
    type: "arrival" as const,
    station: "JFK",
    date: "20250115",
  };

  it("uses ARRIVAL-SEARCH translation keys with city name", () => {
    const result = buildArrivalSearchSeo(
      stubT,
      params,
      "ru",
      CANONICAL,
      { arrival: "New York" },
    );

    expect(result.title).toContain("SEO.BOARD.ARRIVAL-SEARCH.TITLE");
    expect(result.title).toContain("arrivalCity=New York");
  });

  it("falls back to station code when no city name provided", () => {
    const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("arrivalCity=JFK");
  });

  it("title date uses formatDateForTitle; description keeps dd.MM.yyyy", () => {
    const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("date=15.01.2025");
    expect(result.description).toContain("date=15.01.2025");
  });

  it("sets canonical to the arrival search URL", () => {
    const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.canonical).toBe(
      "https://www.aeroflot.ru/ru/onlineboard/arrival/JFK-20250115",
    );
  });
});

// ---------------------------------------------------------------------------
// buildRouteSearchSeo
// ---------------------------------------------------------------------------

describe("buildRouteSearchSeo", () => {
  const params = {
    type: "route" as const,
    departure: "SVO",
    arrival: "JFK",
    date: "20250115",
  };

  it("uses ROUTE-SEARCH translation keys with both city names", () => {
    const result = buildRouteSearchSeo(
      stubT,
      params,
      "ru",
      CANONICAL,
      { departure: "Moscow", arrival: "New York" },
    );

    expect(result.title).toContain("SEO.BOARD.ROUTE-SEARCH.TITLE");
    expect(result.title).toContain("departureCity=Moscow");
    expect(result.title).toContain("arrivalCity=New York");
  });

  it("falls back to IATA codes when no city names provided", () => {
    const result = buildRouteSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("departureCity=SVO");
    expect(result.title).toContain("arrivalCity=JFK");
  });

  it("title date uses formatDateForTitle; description keeps dd.MM.yyyy", () => {
    const result = buildRouteSearchSeo(stubT, params, "ru", CANONICAL);

    expect(result.title).toContain("date=15.01.2025");
    expect(result.description).toContain("date=15.01.2025");
  });

  it("sets canonical to the route search URL", () => {
    const result = buildRouteSearchSeo(stubT, params, "en", CANONICAL);

    expect(result.canonical).toBe(
      "https://www.aeroflot.ru/en/onlineboard/route/SVO-JFK-20250115",
    );
  });
});

// ---------------------------------------------------------------------------
// buildFlightDetailsSeo
// ---------------------------------------------------------------------------

describe("buildFlightDetailsSeo", () => {
  const flight: ISimpleFlight = {
    id: "SU100-20250115",
    routeType: "Direct",
    flightId: {
      carrier: "SU",
      flightNumber: "0100",
      suffix: "",
      date: "20250115",
    },
    flyingTime: "10h 30m",
    operatingBy: {},
    status: "Scheduled",
    leg: {
      index: 0,
      status: "Scheduled",
      flyingTime: "10h 30m",
      updated: "2025-01-15T10:00:00Z",
      dayChange: 0,
      equipment: { name: "Boeing 777-300ER", code: "773" },
      flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
      operatingBy: {},
      departure: {
        scheduled: {
          airport: "Sheremetyevo International Airport",
          airportCode: "SVO",
          city: "Moscow",
          cityCode: "MOW",
          countryCode: "RU",
        },
        checkingStatus: "closed",
        times: {
          scheduledDeparture: {
            dayChange: { value: 0, title: "" },
            local: "2025-01-15T10:00:00",
            localTime: "10:00",
            tzOffset: 3,
            utc: "2025-01-15T07:00:00Z",
          },
        },
      },
      arrival: {
        scheduled: {
          airport: "John F. Kennedy International Airport",
          airportCode: "JFK",
          city: "New York",
          cityCode: "NYC",
          countryCode: "US",
        },
        times: {
          scheduledArrival: {
            dayChange: { value: 0, title: "" },
            local: "2025-01-15T14:30:00",
            localTime: "14:30",
            tzOffset: -5,
            utc: "2025-01-15T19:30:00Z",
          },
        },
      },
    },
  };

  it("uses FLIGHT-DETAILS.TITLE with flightNumber and routeCities (no date)", () => {
    const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);

    expect(result.title).toContain("SEO.BOARD.FLIGHT-DETAILS.TITLE");
    expect(result.title).toContain("flightNumber=SU 0100");
    expect(result.title).toContain("routeCities=Moscow-New York");
    // No date interpolation in title
    expect(result.title).not.toContain("date=");
  });

  it("description keeps dd.MM.yyyy date", () => {
    const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);

    expect(result.description).toContain("SEO.BOARD.FLIGHT-DETAILS.DESCRIPTION");
    expect(result.description).toContain("flightNumber=SU 0100");
    expect(result.description).toContain("date=15.01.2025");
  });

  it("uses TITLE-NO-ROUTE when cityNames not available (from id only)", () => {
    const result = buildFlightDetailsSeoFromId(
      stubT,
      { carrier: "SU", flightNumber: "0100", date: "20250115" },
      "ru",
      CANONICAL,
      // no cityNames provided
    );

    expect(result.title).toContain("SEO.BOARD.FLIGHT-DETAILS.TITLE-NO-ROUTE");
    expect(result.title).toContain("flightNumber=SU 0100");
    expect(result.title).not.toContain("routeCities=");
  });

  it("uses explicit cityNames over leg cities when both present", () => {
    const result = buildFlightDetailsSeo(
      stubT,
      flight,
      "ru",
      CANONICAL,
      { departure: "Москва", arrival: "Нью-Йорк" },
    );

    expect(result.title).toContain("routeCities=Москва-Нью-Йорк");
  });

  it("sets canonical to the details URL", () => {
    const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);

    expect(result.canonical).toBe(
      "https://www.aeroflot.ru/ru/onlineboard/SU0100-20250115",
    );
  });

  it("includes hreflang entries", () => {
    const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);

    expect(result.hreflang).toHaveLength(10);
  });

  it("sets og.type to article for details pages", () => {
    const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL);

    expect(result.og.type).toBe("article");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.19 — OpenGraph completeness assertions (all required tags present)
// ---------------------------------------------------------------------------

describe("4.1.19-R: OpenGraph completeness", () => {
  const ORIGIN = "https://flights.aeroflot.ru";

  it("4.1.19-R: Online-Board start page has all required OG tags", () => {
    const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);

    expect(seo.og.title).toBeTruthy();
    expect(seo.og.description).toBeTruthy();
    expect(seo.og.url).toMatch(/^https:\/\//);
    expect(seo.og.image).toMatch(/^https:\/\//);
    expect(seo.og.type).toBe("website");
    expect(seo.og.locale).toBe("ru");
    expect(seo.og.siteName).toBe("Aeroflot");
  });

  it("4.1.19-R: Online-Board flight search has all required OG tags", () => {
    const params = { type: "flight" as const, carrier: "SU", flightNumber: "0100", date: "20250115" };
    const seo = buildFlightSearchSeo(stubT, params, "ru", ORIGIN);

    expect(seo.og.title).toBeTruthy();
    expect(seo.og.description).toBeTruthy();
    expect(seo.og.url).toMatch(/^https:\/\//);
    expect(seo.og.image).toMatch(/^https:\/\//);
    expect(seo.og.type).toBe("website");
    expect(seo.og.locale).toBe("ru");
    expect(seo.og.siteName).toBe("Aeroflot");
  });

  it("4.1.19-R: Online-Board departure search has all required OG tags", () => {
    const params = { type: "departure" as const, station: "SVO", date: "20250115" };
    const seo = buildDepartureSearchSeo(stubT, params, "ru", ORIGIN);

    expect(seo.og.title).toBeTruthy();
    expect(seo.og.description).toBeTruthy();
    expect(seo.og.url).toMatch(/^https:\/\//);
    expect(seo.og.image).toMatch(/^https:\/\//);
    expect(seo.og.type).toBe("website");
    expect(seo.og.locale).toBe("ru");
    expect(seo.og.siteName).toBe("Aeroflot");
  });

  it("4.1.19-R: Online-Board arrival search has all required OG tags", () => {
    const params = { type: "arrival" as const, station: "JFK", date: "20250115" };
    const seo = buildArrivalSearchSeo(stubT, params, "ru", ORIGIN);

    expect(seo.og.title).toBeTruthy();
    expect(seo.og.description).toBeTruthy();
    expect(seo.og.url).toMatch(/^https:\/\//);
    expect(seo.og.image).toMatch(/^https:\/\//);
    expect(seo.og.type).toBe("website");
    expect(seo.og.locale).toBe("ru");
    expect(seo.og.siteName).toBe("Aeroflot");
  });

  it("4.1.19-R: Online-Board route search has all required OG tags", () => {
    const params = { type: "route" as const, departure: "SVO", arrival: "JFK", date: "20250115" };
    const seo = buildRouteSearchSeo(stubT, params, "ru", ORIGIN);

    expect(seo.og.title).toBeTruthy();
    expect(seo.og.description).toBeTruthy();
    expect(seo.og.url).toMatch(/^https:\/\//);
    expect(seo.og.image).toMatch(/^https:\/\//);
    expect(seo.og.type).toBe("website");
    expect(seo.og.locale).toBe("ru");
    expect(seo.og.siteName).toBe("Aeroflot");
  });

  it("4.1.19-R: Online-Board flight details has all required OG tags", () => {
    const seo = buildFlightDetailsSeoFromId(
      stubT,
      { carrier: "SU", flightNumber: "0100", date: "20250115" },
      "ru",
      ORIGIN,
    );

    expect(seo.og.title).toBeTruthy();
    expect(seo.og.description).toBeTruthy();
    expect(seo.og.url).toMatch(/^https:\/\//);
    expect(seo.og.image).toMatch(/^https:\/\//);
    expect(seo.og.type).toBe("article");
    expect(seo.og.locale).toBe("ru");
    expect(seo.og.siteName).toBe("Aeroflot");
  });
});

// ---------------------------------------------------------------------------
// TZ §4.1.20 — Robots, canonical, hreflang assertions
// ---------------------------------------------------------------------------

describe("4.1.20-R: canonical + hreflang", () => {
  const ORIGIN = "https://flights.aeroflot.ru";

  it("4.1.20-R: canonical URL is normalized (no query params)", () => {
    const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);

    expect(seo.canonical).not.toContain("?");
    expect(seo.canonical).toBe(`${ORIGIN}/ru/onlineboard`);
  });

  it("4.1.20-R: hreflang has all 9 languages + x-default", () => {
    const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);

    expect(seo.hreflang.length).toBeGreaterThanOrEqual(9);
    expect(seo.hreflang.some((h) => h.lang === "x-default")).toBe(true);
  });

  it("4.1.20-R: hreflang covers all 9 supported languages", () => {
    const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);
    const langs = seo.hreflang.map((h) => h.lang);

    expect(langs).toContain("ru");
    expect(langs).toContain("en");
    expect(langs).toContain("es");
    expect(langs).toContain("fr");
    expect(langs).toContain("it");
    expect(langs).toContain("ja");
    expect(langs).toContain("ko");
    expect(langs).toContain("zh");
    expect(langs).toContain("de");
  });

  it("4.1.20-R: hreflang hrefs are absolute URLs", () => {
    const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);

    for (const entry of seo.hreflang) {
      expect(entry.href).toMatch(/^https:\/\//);
    }
  });

  it("4.1.20-R: flight search canonical has no query params", () => {
    const params = { type: "flight" as const, carrier: "SU", flightNumber: "0100", date: "20250115" };
    const seo = buildFlightSearchSeo(stubT, params, "ru", ORIGIN);

    expect(seo.canonical).not.toContain("?");
    expect(seo.canonical).toMatch(/^https:\/\//);
  });

  it("4.1.20-R: noindex is not set on standard indexable pages (defaults to index,follow)", () => {
    const seo = buildOnlineBoardStartSeo(stubT, "ru", ORIGIN);

    // Standard pages must be indexable — noindex prop must be absent/falsy
    expect(seo.noindex).toBeFalsy();
  });
});
