import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createConnectPage } from "../../src/pages/ConnectPage";
import type { ConnectPageCallbacks, SimpleProfile } from "../../src/pages/ConnectPage";
import { uiStore, setTransientError } from "../../src/stores/ui.store";

// Mock credentials module
const mockLoadCredential = vi.fn().mockResolvedValue(null);
vi.mock("../../src/lib/credentials", () => ({
  loadCredential: (...args: unknown[]) => mockLoadCredential(...args),
}));

// Mock SettingsOverlay so we don't pull in all its dependencies
vi.mock("../../src/components/SettingsOverlay", () => ({
  createSettingsOverlay: () => ({
    mount: vi.fn(),
    destroy: vi.fn(),
  }),
}));

// Mock ui.store actions
vi.mock("../../src/stores/ui.store", async () => {
  const actual = await vi.importActual<typeof import("../../src/stores/ui.store")>(
    "../../src/stores/ui.store",
  );
  return {
    ...actual,
    openSettings: vi.fn(),
    closeSettings: vi.fn(),
  };
});

function makeCallbacks(overrides: Partial<ConnectPageCallbacks> = {}): ConnectPageCallbacks {
  return {
    onLogin: vi.fn().mockResolvedValue(undefined),
    onRegister: vi.fn().mockResolvedValue(undefined),
    onTotpSubmit: vi.fn().mockResolvedValue(undefined),
    ...overrides,
  };
}

const testProfiles: SimpleProfile[] = [{ name: "Test Server", host: "localhost:8443" }];

describe("ConnectPage", () => {
  let container: HTMLDivElement;

  beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
    mockLoadCredential.mockReset();
    mockLoadCredential.mockResolvedValue(null);
  });

  afterEach(() => {
    container.remove();
  });

  it("renders the connect page with form elements", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    expect(container.querySelector(".connect-page")).not.toBeNull();
    expect(container.querySelector(".connect-form")).not.toBeNull();
    expect(container.querySelector("#host")).not.toBeNull();
    expect(container.querySelector("#username")).not.toBeNull();
    expect(container.querySelector("#password")).not.toBeNull();

    page.destroy?.();
  });

  it("renders server profiles in the server panel", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const serverItems = container.querySelectorAll(".server-item");
    expect(serverItems.length).toBe(1);

    const serverName = container.querySelector(".srv-name");
    expect(serverName?.textContent).toBe("Test Server");

    page.destroy?.();
  });

  it("fills host input when a server profile is clicked", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const serverItem = container.querySelector(".server-item") as HTMLElement;
    serverItem.click();

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    expect(hostInput.value).toBe("localhost:8443");

    page.destroy?.();
  });

  it("shows error when submitting empty form", async () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    // Clear any default host value
    const hostInput = container.querySelector("#host") as HTMLInputElement;
    hostInput.value = "";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    // Wait for async handler
    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner");
      expect(errorBanner!.classList.contains("visible")).toBe(true);
    });

    page.destroy?.();
  });

  it("shows validation error for short password", async () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "short";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner");
      expect(errorBanner!.classList.contains("visible")).toBe(true);
      expect(errorBanner!.textContent).toContain("at least 8 characters");
    });

    page.destroy?.();
  });

  it("calls onLogin with form values on valid submit", async () => {
    const onLogin = vi.fn().mockResolvedValue(undefined);
    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      expect(onLogin).toHaveBeenCalledWith("localhost:8443", "testuser", "password123");
    });

    page.destroy?.();
  });

  it("toggles between login and register mode", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    // Initially in login mode — invite group hidden
    const inviteGroup = container.querySelector("#invite")!.closest(".form-group") as HTMLElement;
    expect(inviteGroup.classList.contains("form-group--hidden")).toBe(true);

    // Click toggle link
    const toggleLink = container.querySelector(".form-switch a") as HTMLElement;
    toggleLink.click();

    // Now in register mode — invite group visible
    expect(inviteGroup.classList.contains("form-group--hidden")).toBe(false);

    // Submit button text changes
    const btnText = container.querySelector(".btn-text");
    expect(btnText?.textContent).toBe("Register");

    page.destroy?.();
  });

  it("shows TOTP overlay when showTotp is called", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const totpOverlay = container.querySelector(".totp-overlay")!;
    expect(totpOverlay.classList.contains("totp-overlay--hidden")).toBe(true);

    page.showTotp();
    expect(totpOverlay.classList.contains("totp-overlay--hidden")).toBe(false);

    page.destroy?.();
  });

  it("shows error message via showError", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.showError("Connection refused");

    const errorBanner = container.querySelector(".error-banner");
    expect(errorBanner!.classList.contains("visible")).toBe(true);
    expect(errorBanner!.textContent).toBe("Connection refused");

    page.destroy?.();
  });

  it("resets to idle state via resetToIdle", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.showError("Some error");
    page.resetToIdle();

    const errorBanner = container.querySelector(".error-banner");
    expect(errorBanner!.classList.contains("visible")).toBe(false);

    const submitBtn = container.querySelector(".btn-primary") as HTMLButtonElement;
    expect(submitBtn.disabled).toBe(false);

    page.destroy?.();
  });

  it("disables form inputs during loading state", async () => {
    let resolveLogin: () => void;
    const loginPromise = new Promise<void>((resolve) => {
      resolveLogin = resolve;
    });
    const onLogin = vi.fn().mockReturnValue(loginPromise);

    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      expect(hostInput.disabled).toBe(true);
      expect(usernameInput.disabled).toBe(true);
      expect(passwordInput.disabled).toBe(true);
    });

    resolveLogin!();

    page.destroy?.();
  });

  it("cleans up on destroy", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    expect(container.querySelector(".connect-page")).not.toBeNull();

    page.destroy?.();
    expect(container.querySelector(".connect-page")).toBeNull();
  });

  // --- selectServer / auto-login flow ---

  it("selectServer sets host and loads credentials asynchronously", async () => {
    mockLoadCredential.mockResolvedValue({
      username: "saveduser",
      token: "tok",
      password: "savedpass",
    });
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.selectServer("localhost:8443", "initialuser");

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    expect(hostInput.value).toBe("localhost:8443");

    // Wait for async credential loading
    await vi.waitFor(() => {
      const usernameInput = container.querySelector("#username") as HTMLInputElement;
      expect(usernameInput.value).toBe("saveduser");
    });

    // The stored password prefills the field so the user isn't retyping it.
    const passwordInput = container.querySelector("#password") as HTMLInputElement;
    expect(passwordInput.value).toBe("savedpass");

    page.destroy?.();
  });

  it("selectServer sets credentials without password when username is provided", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.selectServer("localhost:8443", "myuser");

    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    expect(usernameInput.value).toBe("myuser");

    page.destroy?.();
  });

  it("selectServer without username does not set credentials", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.selectServer("localhost:8443");

    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    expect(usernameInput.value).toBe("");

    page.destroy?.();
  });

  it("selectServer ignores credential load when host has changed", async () => {
    mockLoadCredential.mockImplementation(async () => {
      // Simulate delay
      await new Promise((r) => setTimeout(r, 10));
      return { username: "staleuser", token: "tok", password: "stalepass" };
    });

    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.selectServer("localhost:8443");
    // Immediately change the host to something else
    const hostInput = container.querySelector("#host") as HTMLInputElement;
    hostInput.value = "other-server:8443";

    // Wait for async to finish
    await new Promise((r) => setTimeout(r, 50));

    // Username should NOT be set because host changed
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    expect(usernameInput.value).toBe("");

    page.destroy?.();
  });

  it("selectServer handles credential load failure gracefully", async () => {
    mockLoadCredential.mockRejectedValue(new Error("Tauri not available"));
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    // Should not throw
    page.selectServer("localhost:8443");
    await new Promise((r) => setTimeout(r, 20));

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    expect(hostInput.value).toBe("localhost:8443");

    page.destroy?.();
  });

  // --- showConnecting / showAutoConnecting ---

  it("showConnecting disables form and shows connecting text", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.showConnecting();

    const submitBtn = container.querySelector(".btn-primary") as HTMLButtonElement;
    expect(submitBtn.disabled).toBe(true);
    const btnText = container.querySelector(".btn-text")!;
    expect(btnText.textContent).toContain("Connecting");

    const statusBar = container.querySelector(".status-bar")!;
    expect(statusBar.classList.contains("visible")).toBe(true);

    page.destroy?.();
  });

  it("showAutoConnecting shows overlay with server name and calls cancel callback", () => {
    const onAutoLoginCancel = vi.fn();
    const page = createConnectPage(makeCallbacks({ onAutoLoginCancel }), testProfiles);
    page.mount(container);

    page.showAutoConnecting("My Server");

    const overlay = container.querySelector(".auto-connect-overlay")!;
    expect(overlay.classList.contains("auto-connect-overlay--hidden")).toBe(false);

    const serverName = container.querySelector(".auto-connect-server")!;
    expect(serverName.textContent).toBe("My Server");

    // Click cancel
    const cancelBtn = container.querySelector(".auto-connect-cancel") as HTMLElement;
    cancelBtn.click();

    expect(onAutoLoginCancel).toHaveBeenCalledTimes(1);
    expect(overlay.classList.contains("auto-connect-overlay--hidden")).toBe(true);

    page.destroy?.();
  });

  // --- refreshProfiles ---

  it("refreshProfiles re-renders the server profile list", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    expect(container.querySelectorAll(".server-item").length).toBe(1);

    page.refreshProfiles([
      { name: "Server A", host: "a.com:8443" },
      { name: "Server B", host: "b.com:8443" },
    ]);

    expect(container.querySelectorAll(".server-item").length).toBe(2);

    page.destroy?.();
  });

  // --- updateHealthStatus ---

  it("updateHealthStatus updates health elements for a known host", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.updateHealthStatus("localhost:8443", {
      status: "online",
      latencyMs: 42,
      version: null,
      onlineUsers: 5,
    });

    const dot = container.querySelector(".srv-status-dot")!;
    expect(dot.className).toContain("online");
    const latency = container.querySelector(".srv-latency")!;
    expect(latency.textContent).toBe("42ms");
    const onlineUsers = container.querySelector(".srv-online-users")!;
    expect(onlineUsers.textContent).toBe("5 online");

    page.destroy?.();
  });

  // --- getRememberPassword / getPassword ---

  it("getRememberPassword returns checkbox state", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    expect(page.getRememberPassword()).toBe(false);

    const checkbox = container.querySelector("#remember-password") as HTMLInputElement;
    checkbox.checked = true;
    expect(page.getRememberPassword()).toBe(true);

    page.destroy?.();
  });

  // --- getAutoConnect ---

  it("getAutoConnect returns checkbox state", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    expect(page.getAutoConnect()).toBe(false);

    const checkbox = container.querySelector("#auto-connect") as HTMLInputElement;
    checkbox.checked = true;
    expect(page.getAutoConnect()).toBe(true);

    page.destroy?.();
  });

  it("ticking auto-connect forces and disables remember password", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const autoConnectCheckbox = container.querySelector("#auto-connect") as HTMLInputElement;
    const rememberCheckbox = container.querySelector("#remember-password") as HTMLInputElement;

    autoConnectCheckbox.checked = true;
    autoConnectCheckbox.dispatchEvent(new Event("change"));

    expect(rememberCheckbox.checked).toBe(true);
    expect(rememberCheckbox.disabled).toBe(true);
    expect(page.getRememberPassword()).toBe(true);

    page.destroy?.();
  });

  it("unticking auto-connect re-enables remember password", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const autoConnectCheckbox = container.querySelector("#auto-connect") as HTMLInputElement;
    const rememberCheckbox = container.querySelector("#remember-password") as HTMLInputElement;

    autoConnectCheckbox.checked = true;
    autoConnectCheckbox.dispatchEvent(new Event("change"));

    autoConnectCheckbox.checked = false;
    autoConnectCheckbox.dispatchEvent(new Event("change"));

    expect(rememberCheckbox.disabled).toBe(false);
    expect(rememberCheckbox.checked).toBe(true);

    page.destroy?.();
  });

  it("getPassword returns password input value", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const passwordInput = container.querySelector("#password") as HTMLInputElement;
    passwordInput.value = "mypassword";
    expect(page.getPassword()).toBe("mypassword");

    page.destroy?.();
  });

  // --- Pending transient error display ---

  it("shows pending transient error on mount and clears it", () => {
    setTransientError("Already connected from another client");

    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const errorBanner = container.querySelector(".error-banner")!;
    expect(errorBanner.classList.contains("visible")).toBe(true);
    expect(errorBanner.textContent).toBe("Already connected from another client");

    // Transient error should be cleared
    expect(uiStore.getState().transientError).toBeNull();

    page.destroy?.();
  });

  // B4_conn_ipc-6: a WS auth_error, cert-mismatch reject, or credential-save
  // warning can set transientError AFTER this page is already mounted — a
  // one-time getState() read at mount would silently drop it.
  it("shows a transient error set after mount, not just one already pending at mount time", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    // No error yet at mount time.
    expect(container.querySelector(".error-banner")!.classList.contains("visible")).toBe(false);

    setTransientError("Your session expired — sign in again.");
    uiStore.flush();

    const errorBanner = container.querySelector(".error-banner")!;
    expect(errorBanner.classList.contains("visible")).toBe(true);
    expect(errorBanner.textContent).toBe("Your session expired — sign in again.");
    expect(uiStore.getState().transientError).toBeNull();

    page.destroy?.();
  });

  // B4_conn_ipc-15: a transient error set while this page was mounted (e.g. a
  // background credential-save failure fired after a successful login moved
  // on to MainPage) must not survive to resurface at the NEXT mount — which
  // only happens after a later logout, where it would misleadingly read as a
  // fresh login failure.
  it("clears a transient error on destroy so it cannot resurface as a bogus login error later", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    // Set while mounted but not yet observed (no flush before destroy) —
    // mirrors a background failure landing just as the page is torn down.
    setTransientError("Could not save credentials — auto-login won't work");
    page.destroy?.();

    expect(uiStore.getState().transientError).toBeNull();
  });

  // --- TOTP overlay interactions ---

  it("TOTP submit calls onTotpSubmit with 6-digit code", async () => {
    const onTotpSubmit = vi.fn().mockResolvedValue(undefined);
    const page = createConnectPage(makeCallbacks({ onTotpSubmit }), testProfiles);
    page.mount(container);

    page.showTotp();

    const totpInput = container.querySelector(".totp-overlay input") as HTMLInputElement;
    totpInput.value = "123456";

    const verifyBtn = container.querySelector(".totp-overlay .btn-primary") as HTMLButtonElement;
    verifyBtn.click();

    await vi.waitFor(() => {
      expect(onTotpSubmit).toHaveBeenCalledWith("123456");
    });

    page.destroy?.();
  });

  it("TOTP submit rejects invalid code (not 6 digits)", () => {
    const onTotpSubmit = vi.fn().mockResolvedValue(undefined);
    const page = createConnectPage(makeCallbacks({ onTotpSubmit }), testProfiles);
    page.mount(container);

    page.showTotp();

    const totpInput = container.querySelector(".totp-overlay input") as HTMLInputElement;
    totpInput.value = "abc";

    const verifyBtn = container.querySelector(".totp-overlay .btn-primary") as HTMLButtonElement;
    verifyBtn.click();

    expect(onTotpSubmit).not.toHaveBeenCalled();
    expect(totpInput.classList.contains("error")).toBe(true);

    page.destroy?.();
  });

  it("TOTP cancel returns to idle state", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.showTotp();
    const totpOverlay = container.querySelector(".totp-overlay")!;
    expect(totpOverlay.classList.contains("totp-overlay--hidden")).toBe(false);

    const cancelBtn = container.querySelector(".totp-back") as HTMLElement;
    cancelBtn.click();

    expect(totpOverlay.classList.contains("totp-overlay--hidden")).toBe(true);

    page.destroy?.();
  });

  it("TOTP Enter key submits the code", async () => {
    const onTotpSubmit = vi.fn().mockResolvedValue(undefined);
    const page = createConnectPage(makeCallbacks({ onTotpSubmit }), testProfiles);
    page.mount(container);

    page.showTotp();

    const totpInput = container.querySelector(".totp-overlay input") as HTMLInputElement;
    totpInput.value = "654321";

    totpInput.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));

    await vi.waitFor(() => {
      expect(onTotpSubmit).toHaveBeenCalledWith("654321");
    });

    page.destroy?.();
  });

  it("TOTP submit shows error when callback throws", async () => {
    const onTotpSubmit = vi.fn().mockRejectedValue(new Error("Invalid TOTP"));
    const page = createConnectPage(makeCallbacks({ onTotpSubmit }), testProfiles);
    page.mount(container);

    page.showTotp();

    const totpInput = container.querySelector(".totp-overlay input") as HTMLInputElement;
    totpInput.value = "999999";

    const verifyBtn = container.querySelector(".totp-overlay .btn-primary") as HTMLButtonElement;
    verifyBtn.click();

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.classList.contains("visible")).toBe(true);
      expect(errorBanner.textContent).toBe("Invalid TOTP");
    });

    // Verify button should be re-enabled
    expect(verifyBtn.disabled).toBe(false);
    expect(verifyBtn.textContent).toBe("Verify");

    page.destroy?.();
  });

  // --- Password visibility toggle ---

  it("toggles password visibility when eye button is clicked", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const passwordInput = container.querySelector("#password") as HTMLInputElement;
    expect(passwordInput.type).toBe("password");

    const toggleBtn = container.querySelector(".password-toggle") as HTMLButtonElement;
    toggleBtn.click();
    expect(passwordInput.type).toBe("text");

    toggleBtn.click();
    expect(passwordInput.type).toBe("password");

    page.destroy?.();
  });

  // --- Register mode ---

  it("calls onRegister when in register mode with valid form", async () => {
    const onRegister = vi.fn().mockResolvedValue(undefined);
    const page = createConnectPage(makeCallbacks({ onRegister }), testProfiles);
    page.mount(container);

    // Switch to register mode
    const toggleLink = container.querySelector(".form-switch a") as HTMLElement;
    toggleLink.click();

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;
    const inviteInput = container.querySelector("#invite") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "newuser";
    passwordInput.value = "password123";
    inviteInput.value = "INVITE-CODE";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      expect(onRegister).toHaveBeenCalledWith(
        "localhost:8443",
        "newuser",
        "password123",
        "INVITE-CODE",
      );
    });

    page.destroy?.();
  });

  it("shows error when register mode missing invite code", async () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const toggleLink = container.querySelector(".form-switch a") as HTMLElement;
    toggleLink.click();

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "newuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.classList.contains("visible")).toBe(true);
      expect(errorBanner.textContent).toContain("Invite code is required");
    });

    page.destroy?.();
  });

  // --- Error handling in submit ---

  it("shows error when onLogin throws a string", async () => {
    const onLogin = vi.fn().mockRejectedValue("string error");
    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.textContent).toBe("string error");
    });

    page.destroy?.();
  });

  it("shows error when onLogin throws an object with message", async () => {
    const onLogin = vi.fn().mockRejectedValue({ message: "object error" });
    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.textContent).toBe("object error");
    });

    page.destroy?.();
  });

  it("shows stringified error when onLogin throws unknown type", async () => {
    const onLogin = vi.fn().mockRejectedValue(42);
    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.textContent).toBe("42");
    });

    page.destroy?.();
  });

  it("truncates an over-long auth error to 200 chars plus ellipsis (anti-phishing cap)", async () => {
    const onLogin = vi.fn().mockRejectedValue(new Error("x".repeat(500)));
    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.textContent).toBe("x".repeat(200) + "...");
      expect(errorBanner.textContent).toHaveLength(203);
    });

    page.destroy?.();
  });

  // --- Empty username validation ---

  it("shows error when username is empty", async () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.textContent).toContain("Username is required");
    });

    page.destroy?.();
  });

  it("shows error when password is empty", async () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const errorBanner = container.querySelector(".error-banner")!;
      expect(errorBanner.textContent).toContain("Password is required");
    });

    page.destroy?.();
  });

  // --- Duplicate submit prevention ---

  it("ignores submit while already loading", async () => {
    let resolveLogin: () => void;
    const loginPromise = new Promise<void>((resolve) => {
      resolveLogin = resolve;
    });
    const onLogin = vi.fn().mockReturnValue(loginPromise);

    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      expect(onLogin).toHaveBeenCalledTimes(1);
    });

    // Submit again while loading
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
    expect(onLogin).toHaveBeenCalledTimes(1);

    resolveLogin!();
    page.destroy?.();
  });

  // --- Toggle mode clears error ---

  it("toggling mode clears existing error state", async () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.showError("Some error");
    const errorBanner = container.querySelector(".error-banner")!;
    expect(errorBanner.classList.contains("visible")).toBe(true);

    const toggleLink = container.querySelector(".form-switch a") as HTMLElement;
    toggleLink.click();

    expect(errorBanner.classList.contains("visible")).toBe(false);

    page.destroy?.();
  });

  // --- Toggle back to login ---

  it("toggling back to login shows correct text", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const toggleLink = container.querySelector(".form-switch a") as HTMLElement;
    toggleLink.click(); // to register
    toggleLink.click(); // back to login

    const btnText = container.querySelector(".btn-text");
    expect(btnText?.textContent).toBe("Login");
    expect(toggleLink.textContent).toContain("Need an account?");

    page.destroy?.();
  });

  // --- Credential loaded guard (host mismatch) ---

  it("does not apply credentials when host has changed before onCredentialLoaded", async () => {
    mockLoadCredential.mockResolvedValue({ username: "loaded", token: "tok", password: "pass" });
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    // Click server item to trigger credential loading
    const serverItem = container.querySelector(".server-item") as HTMLElement;
    serverItem.click();

    // Immediately change host
    const hostInput = container.querySelector("#host") as HTMLInputElement;
    hostInput.value = "different-host:8443";

    // Wait for credential loading to complete
    await new Promise((r) => setTimeout(r, 20));

    // Username should not be updated due to host mismatch guard
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    // The onServerClick call doesn't have a username in testProfiles, so it stays empty
    // The credential should NOT be applied because host changed
    expect(usernameInput.value).toBe("");

    page.destroy?.();
  });

  // --- Settings gear ---

  it("opens settings when gear button is clicked", async () => {
    const { openSettings } = await import("../../src/stores/ui.store");
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const gearBtn = container.querySelector(".settings-gear") as HTMLButtonElement;
    gearBtn.click();

    expect(openSettings).toHaveBeenCalledTimes(1);

    page.destroy?.();
  });

  // OC-0083: destroy() must clear settingsOpen — otherwise MainPage, which
  // mounts its own SettingsOverlay eagerly and shows it off a stale
  // settingsOpen === true, pops the settings panel open over the freshly
  // loaded app right after login.
  it("closes settings on destroy so the flag doesn't leak into the next page", async () => {
    const { closeSettings } = await import("../../src/stores/ui.store");
    vi.mocked(closeSettings).mockClear();
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    page.destroy?.();

    expect(closeSettings).toHaveBeenCalledTimes(1);
  });

  // --- setCredentials with password sets remember checkbox ---

  it("setCredentials with password checks the remember password checkbox", async () => {
    mockLoadCredential.mockResolvedValue({ username: "user", token: "tok", password: "pass123" });
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const serverItem = container.querySelector(".server-item") as HTMLElement;
    serverItem.click();

    await vi.waitFor(() => {
      expect(page.getRememberPassword()).toBe(true);
    });

    const passwordInput = container.querySelector("#password") as HTMLInputElement;
    expect(passwordInput.value).toBe("pass123");

    page.destroy?.();
  });

  // --- Status bar states ---

  it("status bar is hidden on idle/error/totp states and visible on loading/connecting", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const statusBar = container.querySelector(".status-bar")!;

    // idle
    expect(statusBar.classList.contains("visible")).toBe(false);

    // connecting
    page.showConnecting();
    expect(statusBar.classList.contains("visible")).toBe(true);

    // back to idle
    page.resetToIdle();
    expect(statusBar.classList.contains("visible")).toBe(false);

    // totp
    page.showTotp();
    expect(statusBar.classList.contains("visible")).toBe(false);

    // error
    page.showError("err");
    expect(statusBar.classList.contains("visible")).toBe(false);

    page.destroy?.();
  });

  // --- Loading state button text ---

  it("shows correct button text during loading states", async () => {
    let resolveLogin: () => void;
    const loginPromise = new Promise<void>((resolve) => {
      resolveLogin = resolve;
    });
    const onLogin = vi.fn().mockReturnValue(loginPromise);

    const page = createConnectPage(makeCallbacks({ onLogin }), testProfiles);
    page.mount(container);

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "testuser";
    passwordInput.value = "password123";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const btnText = container.querySelector(".btn-text")!;
      expect(btnText.textContent).toContain("Logging in");
    });

    resolveLogin!();
    page.destroy?.();
  });

  it("shows registering text in register mode during loading", async () => {
    let resolveRegister: () => void;
    const registerPromise = new Promise<void>((resolve) => {
      resolveRegister = resolve;
    });
    const onRegister = vi.fn().mockReturnValue(registerPromise);

    const page = createConnectPage(makeCallbacks({ onRegister }), testProfiles);
    page.mount(container);

    // Switch to register mode
    const toggleLink = container.querySelector(".form-switch a") as HTMLElement;
    toggleLink.click();

    const hostInput = container.querySelector("#host") as HTMLInputElement;
    const usernameInput = container.querySelector("#username") as HTMLInputElement;
    const passwordInput = container.querySelector("#password") as HTMLInputElement;
    const inviteInput = container.querySelector("#invite") as HTMLInputElement;

    hostInput.value = "localhost:8443";
    usernameInput.value = "newuser";
    passwordInput.value = "password123";
    inviteInput.value = "CODE";

    const form = container.querySelector(".connect-form") as HTMLFormElement;
    form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));

    await vi.waitFor(() => {
      const btnText = container.querySelector(".btn-text")!;
      expect(btnText.textContent).toContain("Registering");
    });

    resolveRegister!();
    page.destroy?.();
  });

  // --- Auto-connect overlay hidden by default ---

  it("auto-connect overlay is hidden by default", () => {
    const page = createConnectPage(makeCallbacks(), testProfiles);
    page.mount(container);

    const overlay = container.querySelector(".auto-connect-overlay")!;
    expect(overlay.classList.contains("auto-connect-overlay--hidden")).toBe(true);

    page.destroy?.();
  });

  // --- Uses default profiles when none provided ---

  it("uses default profiles when no initialProfiles provided", () => {
    const page = createConnectPage(makeCallbacks());
    page.mount(container);

    const serverItems = container.querySelectorAll(".server-item");
    expect(serverItems.length).toBe(1);

    const serverHost = container.querySelector(".srv-host");
    expect(serverHost?.textContent).toBe("localhost:8443");

    page.destroy?.();
  });
});
