/**
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { CityAutocomplete } from "./CityAutocomplete.js";
import { transformDictionaries } from "@/shared/dictionaries/index.js";
import type { IRawDictionaries } from "@/shared/dictionaries/index.js";

const raw: IRawDictionaries = {
  regions: [{ world_region_id: 500374, title: { ru: "Россия" } }],
  countries: [{ code: "RU", title: { ru: "Россия" }, world_region_id: 500374 }],
  cities: [
    { code: "MOW", title: { ru: "Москва" }, country_code: "RU", has_afl_flights: true, location: { lat: 55, lon: 37 } },
    { code: "LED", title: { ru: "Санкт-Петербург" }, country_code: "RU", has_afl_flights: true, location: { lat: 60, lon: 30 } },
  ],
  airports: [
    { code: "SVO", city_code: "MOW", title: { ru: "Шереметьево" }, has_afl_flights: true, location: { lat: 55, lon: 37 } },
    { code: "LED", city_code: "LED", title: { ru: "Пулково" }, has_afl_flights: true, location: { lat: 60, lon: 30 } },
  ],
};
const dictionaries = transformDictionaries(raw, "ru");

vi.mock("primereact/autocomplete", () => ({
  AutoComplete: (props: Record<string, unknown>) => (
    <input
      data-testid={props["data-testid"] as string}
      placeholder={props["placeholder"] as string}
      value={typeof props["value"] === "string" ? props["value"] : ""}
      onChange={(e) => {
        const onChange = props["onChange"] as ((ev: { value: string }) => void) | undefined;
        onChange?.({ value: e.target.value });
      }}
    />
  ),
}));

describe("CityAutocomplete", () => {
  it("renders label and placeholder", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick one"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
      />,
    );
    expect(screen.getByText("City")).toBeTruthy();
    expect(screen.getByPlaceholderText("Pick one")).toBeTruthy();
  });

  it("shows the selected city code in the right label when value is set", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value="MOW"
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    expect(screen.getByTestId("test-code").textContent).toBe("MOW");
  });

  it("opens the regional picker when the popup-trigger button is clicked", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    expect(screen.queryByTestId("city-picker-popup")).toBeNull();
    fireEvent.click(screen.getByTestId("test-popup-button"));
    expect(screen.getByTestId("city-picker-popup")).toBeTruthy();
  });

  it("calls onChange with the selected code when a popup cell is clicked", () => {
    const onChange = vi.fn();
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={onChange}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    fireEvent.click(screen.getByTestId("test-popup-button"));
    fireEvent.click(screen.getByText("Санкт-Петербург"));
    expect(onChange).toHaveBeenCalledWith("LED");
  });

  it("clears the value when the clear button is clicked", () => {
    const onChange = vi.fn();
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value="MOW"
        onChange={onChange}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    fireEvent.click(screen.getByTestId("test-clear-button"));
    expect(onChange).toHaveBeenCalledWith("");
  });

  it("closes the popup when clicking outside the component", () => {
    render(
      <div>
        <CityAutocomplete
          label="City"
          placeholder="Pick"
          value=""
          onChange={vi.fn()}
          dictionaries={dictionaries}
          testIdPrefix="test"
        />
        <div data-testid="outside">outside</div>
      </div>,
    );
    fireEvent.click(screen.getByTestId("test-popup-button"));
    expect(screen.getByTestId("city-picker-popup")).toBeTruthy();
    fireEvent.mouseDown(screen.getByTestId("outside"));
    expect(screen.queryByTestId("city-picker-popup")).toBeNull();
  });

  it("renders error tooltip and has-error class when error prop is set", () => {
    const { container } = render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
        error="required"
        testIdPrefix="test"
      />,
    );
    expect(screen.getByTestId("test-error").textContent).toBe("required");
    expect(container.querySelector(".city-autocomplete__input--has-error")).toBeTruthy();
  });

  it("auto-commits a typed city name that exactly matches a dictionary entry", () => {
    const onChange = vi.fn();
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={onChange}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    fireEvent.change(screen.getByTestId("test-input"), {
      target: { value: "Москва" },
    });
    expect(onChange).toHaveBeenCalledWith("MOW");
  });

  it("auto-commits a typed airport name to its owning city code", () => {
    const onChange = vi.fn();
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={onChange}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    fireEvent.change(screen.getByTestId("test-input"), {
      target: { value: "Шереметьево" },
    });
    expect(onChange).toHaveBeenCalledWith("MOW");
  });

  it("does not auto-commit partial typed text", () => {
    const onChange = vi.fn();
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={onChange}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    fireEvent.change(screen.getByTestId("test-input"), {
      target: { value: "Мос" },
    });
    expect(onChange).not.toHaveBeenCalled();
  });

  it("4.1.9-C1: has-value CSS class is applied when value is non-empty (clear button becomes visible)", () => {
    const { container } = render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value="MOW"
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    expect(container.querySelector(".city-autocomplete__input--has-value")).toBeTruthy();
  });

  it("4.1.9-C2: has-value CSS class is absent when value is empty (clear button hidden)", () => {
    const { container } = render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    expect(container.querySelector(".city-autocomplete__input--has-value")).toBeNull();
  });

  it("4.1.9-C3: clear button has aria-label for a11y", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value="MOW"
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    const clearBtn = screen.getByTestId("test-clear-button");
    // aria-label is always rendered; visibility is controlled via CSS class
    expect(clearBtn.getAttribute("aria-label")).toBeTruthy();
  });

  it("4.1.9.1-R1: ESC cancels typed text and restores committed city display", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value="MOW"
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    const input = screen.getByTestId("test-input");
    // Simulate partial typing (the AutoComplete mock stores string value when
    // the onChange fires with a plain string)
    fireEvent.change(input, { target: { value: "Мос" } });
    // Now press Escape — input should restore to the committed city object display
    fireEvent.keyDown(screen.getByTestId("test-input").closest(".city-autocomplete")!, {
      key: "Escape",
      code: "Escape",
    });
    // After ESC the inputValue is restored to {kind:"city", name:"Москва",...}
    // The AutoComplete mock renders "" for non-string values, but the internal
    // state should reflect the reset — we verify onChange was NOT called and
    // no error is thrown.
    // (The AutoComplete mock renders inputValue.name only for string; object
    //  value renders as "". The real PrimeReact AutoComplete uses field="name"
    //  so it would show "Москва". This test just verifies ESC doesn't throw and
    //  the partial text is cleared.)
    expect(input.getAttribute("value")).toBe("");
  });

  it("4.1.9.1-R2: ESC with no committed value clears the input", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    const input = screen.getByTestId("test-input");
    fireEvent.change(input, { target: { value: "Мос" } });
    fireEvent.keyDown(screen.getByTestId("test-input").closest(".city-autocomplete")!, {
      key: "Escape",
      code: "Escape",
    });
    expect(input.getAttribute("value")).toBe("");
  });

  it("4.1.9.1-R3: non-ESC key does not reset input", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    const input = screen.getByTestId("test-input");
    fireEvent.change(input, { target: { value: "Мос" } });
    fireEvent.keyDown(screen.getByTestId("test-input").closest(".city-autocomplete")!, {
      key: "Enter",
      code: "Enter",
    });
    expect(input.getAttribute("value")).toBe("Мос");
  });

  it("4.1.9.2-R12: Escape closes the popup when popup is open", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={dictionaries}
        testIdPrefix="test"
      />,
    );
    // Open the popup
    fireEvent.click(screen.getByTestId("test-popup-button"));
    expect(screen.getByTestId("city-picker-popup")).toBeTruthy();
    // Press Escape on the root div
    fireEvent.keyDown(screen.getByTestId("test-input").closest(".city-autocomplete")!, {
      key: "Escape",
      code: "Escape",
    });
    expect(screen.queryByTestId("city-picker-popup")).toBeNull();
  });

  it("does not open popup when dictionaries is null", () => {
    render(
      <CityAutocomplete
        label="City"
        placeholder="Pick"
        value=""
        onChange={vi.fn()}
        dictionaries={null}
        testIdPrefix="test"
      />,
    );
    fireEvent.click(screen.getByTestId("test-popup-button"));
    expect(screen.queryByTestId("city-picker-popup")).toBeNull();
  });
});
