import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { App } from "../App";
import { POSITIONS } from "../data/simulation";

function setup() {
  const user = userEvent.setup();
  render(<App />);
  return user;
}

async function goToChoose(user: ReturnType<typeof userEvent.setup>) {
  await user.click(screen.getByTestId("next")); // landing -> compare
  await user.click(screen.getByTestId("next")); // compare -> choose
}

describe("default behaviour: keep every V2 position", () => {
  it("starts on the landing step with nothing selected", () => {
    render(<App />);
    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "landing");
    expect(screen.getByTestId("screen-landing")).toBeInTheDocument();
  });

  it("renders every opt-in checkbox unchecked", async () => {
    const user = setup();
    await goToChoose(user);
    for (const p of POSITIONS) {
      expect(screen.getByTestId(`optin-${p.id}`)).not.toBeChecked();
    }
    expect(screen.getByTestId("selection-count")).toHaveTextContent(
      "0 positions opted in — everything stays in V2",
    );
  });

  it("blocks progress past Choose while nothing is opted in", async () => {
    const user = setup();
    await goToChoose(user);
    expect(screen.getByTestId("next")).toBeDisabled();
    expect(screen.getByTestId("no-selection-hint")).toBeInTheDocument();
    expect(screen.getByTestId("step-review")).toBeDisabled();
    expect(screen.getByTestId("step-sign")).toBeDisabled();
    expect(screen.getByTestId("step-confirm")).toBeDisabled();
  });

  it("cannot opt in a position that has no V3 equivalent", async () => {
    const user = setup();
    await goToChoose(user);
    const noV3 = POSITIONS.find((p) => p.v3 === null)!;
    const box = screen.getByTestId(`optin-${noV3.id}`);
    expect(box).toBeDisabled();
    await user.click(box);
    expect(box).not.toBeChecked();
    expect(screen.getByTestId(`no-v3-${noV3.id}`)).toHaveTextContent("stays in V2");
  });
});

describe("explicit opt-in", () => {
  it("selects only the position the user checks and can be reset", async () => {
    const user = setup();
    await goToChoose(user);

    await user.click(screen.getByTestId("optin-pb-1041"));
    expect(screen.getByTestId("optin-pb-1041")).toBeChecked();
    expect(screen.getByTestId("optin-ab-388")).not.toBeChecked();
    expect(screen.getByTestId("selection-count")).toHaveTextContent(
      `1 of ${POSITIONS.length} positions opted in to V3`,
    );
    expect(screen.getByTestId("decision-pb-1041")).toHaveTextContent("opted in to V3");
    expect(screen.getByTestId("decision-ab-388")).toHaveTextContent("keeping V2");
    expect(screen.getByTestId("next")).toBeEnabled();

    await user.click(screen.getByTestId("keep-all-v2"));
    expect(screen.getByTestId("optin-pb-1041")).not.toBeChecked();
    expect(screen.getByTestId("next")).toBeDisabled();
  });

  it("is keyboard operable", async () => {
    const user = setup();
    await goToChoose(user);
    const box = screen.getByTestId("optin-pb-1041");
    box.focus();
    expect(box).toHaveFocus();
    await user.keyboard("{ }");
    expect(box).toBeChecked();
  });
});

describe("dataSourceErrors block the flow", () => {
  async function degrade(user: ReturnType<typeof userEvent.setup>) {
    await user.selectOptions(screen.getByTestId("scenario-select"), "degraded");
  }

  it("shows the verbatim errors and disables every opt-in", async () => {
    const user = setup();
    await degrade(user);

    const block = screen.getByTestId("data-source-error-block");
    expect(block).toBeInTheDocument();
    const list = within(screen.getByTestId("data-source-error-list"));
    expect(list.getByText("liability-read-plan-unconfigured")).toBeInTheDocument();
    expect(
      list.getByText("base:8453 eth_call reverted — solvency view unavailable (HTTP 503)"),
    ).toBeInTheDocument();
    expect(block).toHaveTextContent("Migration blocked");

    await goToChoose(user);
    for (const p of POSITIONS) {
      expect(screen.getByTestId(`optin-${p.id}`)).toBeDisabled();
    }
    expect(screen.getByTestId("next")).toBeDisabled();
    expect(screen.getByTestId("step-review")).toBeDisabled();
    expect(screen.getByTestId("step-sign")).toBeDisabled();
  });

  it("pulls the user off the payload/sign/result screens when the feed starts failing", async () => {
    const user = setup();
    await goToChoose(user);
    await user.click(screen.getByTestId("optin-pb-1041"));
    await user.click(screen.getByTestId("next")); // review
    await user.click(screen.getByTestId("next")); // sign
    await user.click(screen.getByTestId("simulate-sign")); // confirm
    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "confirm");

    await degrade(user);
    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "choose");
    expect(screen.getByTestId("data-source-error-block")).toBeInTheDocument();
  });

  it("clears any prior selection when the feed starts failing", async () => {
    const user = setup();
    await goToChoose(user);
    await user.click(screen.getByTestId("optin-pb-1041"));
    expect(screen.getByTestId("optin-pb-1041")).toBeChecked();

    await degrade(user);
    expect(screen.getByTestId("optin-pb-1041")).not.toBeChecked();
    expect(screen.getByTestId("selection-count")).toHaveTextContent(
      "everything stays in V2",
    );
  });
});

describe("navigation flow", () => {
  it("walks landing -> compare -> choose -> review -> sign -> confirm", async () => {
    const user = setup();
    const container = () => screen.getByTestId("step-container");

    await user.click(screen.getByTestId("next"));
    expect(container()).toHaveAttribute("data-step", "compare");
    expect(screen.getByTestId("compare-table")).toBeInTheDocument();

    await user.click(screen.getByTestId("next"));
    expect(container()).toHaveAttribute("data-step", "choose");

    await user.click(screen.getByTestId("optin-pb-1041"));
    await user.click(screen.getByTestId("next"));
    expect(container()).toHaveAttribute("data-step", "review");
    expect(screen.getByTestId("domain-chainid")).toHaveTextContent("8453");
    expect(screen.getByTestId("typed-data-json")).toHaveTextContent('"MigrationIntent"');
    expect(screen.getByTestId("message-position-count")).toHaveTextContent("1");
    expect(screen.getByTestId("keep-v2-live")).toHaveTextContent("true");

    await user.click(screen.getByTestId("next"));
    expect(container()).toHaveAttribute("data-step", "sign");
    expect(screen.getByTestId("sign-position-count")).toHaveTextContent("1");

    await user.click(screen.getByTestId("simulate-sign"));
    expect(container()).toHaveAttribute("data-step", "confirm");
    expect(screen.getByTestId("confirm-signature").textContent).toMatch(/^0x[0-9a-f]{130}$/);
    expect(screen.getByTestId("confirm-broadcast")).toHaveTextContent(
      "none — simulation only",
    );
  });

  it("goes back without losing the selection, and restart clears it", async () => {
    const user = setup();
    await goToChoose(user);
    await user.click(screen.getByTestId("optin-mutual-aid"));
    await user.click(screen.getByTestId("next")); // review
    await user.click(screen.getByTestId("back")); // choose
    expect(screen.getByTestId("optin-mutual-aid")).toBeChecked();

    await user.click(screen.getByTestId("next"));
    await user.click(screen.getByTestId("next"));
    await user.click(screen.getByTestId("simulate-sign"));
    await user.click(screen.getByTestId("restart"));
    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "landing");
  });

  it("flags an irreversible selection before signing", async () => {
    const user = setup();
    await goToChoose(user);
    await user.click(screen.getByTestId("optin-vault-shares"));
    await user.click(screen.getByTestId("next"));
    expect(screen.getByTestId("irreversible-warning")).toHaveTextContent("Irreversible");
  });
});

describe("no wallet, signing, or broadcast side effects", () => {
  const ethRequest = vi.fn();
  let fetchSpy: ReturnType<typeof vi.fn>;
  let xhrOpen: ReturnType<typeof vi.spyOn>;
  let setItem: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    ethRequest.mockClear();
    (window as unknown as Record<string, unknown>).ethereum = {
      request: ethRequest,
      enable: ethRequest,
      isMetaMask: true,
    };
    fetchSpy = vi.fn(() => Promise.reject(new Error("network access is not allowed")));
    vi.stubGlobal("fetch", fetchSpy);
    xhrOpen = vi.spyOn(XMLHttpRequest.prototype, "open");
    setItem = vi.spyOn(Storage.prototype, "setItem");
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    xhrOpen.mockRestore();
    setItem.mockRestore();
    delete (window as unknown as Record<string, unknown>).ethereum;
  });

  it("never touches an injected provider, the network, or storage", async () => {
    const user = setup();
    await goToChoose(user);
    await user.click(screen.getByTestId("optin-pb-1041"));
    await user.click(screen.getByTestId("next")); // review
    await user.click(screen.getByTestId("next")); // sign
    await user.click(screen.getByTestId("simulate-sign"));

    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "confirm");
    expect(ethRequest).not.toHaveBeenCalled();
    expect(fetchSpy).not.toHaveBeenCalled();
    expect(xhrOpen).not.toHaveBeenCalled();
    expect(setItem).not.toHaveBeenCalled();
    expect(window.localStorage.length).toBe(0);
    expect(window.sessionStorage.length).toBe(0);
    expect(document.cookie).toBe("");
  });

  it("cannot sign while the data source is failing", async () => {
    const user = setup();
    await goToChoose(user);
    await user.click(screen.getByTestId("optin-pb-1041"));
    await user.click(screen.getByTestId("next"));
    await user.click(screen.getByTestId("next"));
    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "sign");

    await user.selectOptions(screen.getByTestId("scenario-select"), "degraded");
    // A failing feed removes the sign screen entirely and returns to selection.
    expect(screen.queryByTestId("simulate-sign")).toBeNull();
    expect(screen.getByTestId("step-container")).toHaveAttribute("data-step", "choose");
    expect(screen.getByTestId("step-sign")).toBeDisabled();
    await user.click(screen.getByTestId("step-sign"));
    expect(screen.getByTestId("step-container")).not.toHaveAttribute(
      "data-step",
      "confirm",
    );
    expect(ethRequest).not.toHaveBeenCalled();
  });

  it("does not reference any signing, broadcast, or storage API in the app source", () => {
    const root = path.resolve(__dirname, "..");
    const files: string[] = [];
    const walk = (dir: string) => {
      for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
        const full = path.join(dir, entry.name);
        if (entry.isDirectory()) {
          if (entry.name !== "__tests__") walk(full);
        } else if (/\.tsx?$/.test(entry.name)) {
          files.push(full);
        }
      }
    };
    walk(root);
    expect(files.length).toBeGreaterThan(5);

    const forbidden = [
      "window.ethereum",
      "eth_signTypedData",
      "eth_sendTransaction",
      "sendRawTransaction",
      "personal_sign",
      "localStorage",
      "sessionStorage",
      "document.cookie",
      "XMLHttpRequest",
      "fetch(",
      "WebSocket",
    ];
    for (const file of files) {
      const src = fs.readFileSync(file, "utf8");
      for (const token of forbidden) {
        expect(src, `${file} must not reference ${token}`).not.toContain(token);
      }
    }
  });
});

describe("required disclosures", () => {
  it("states the Base pilot cap, V2 continuity, and the mutual-aid caveat", () => {
    render(<App />);
    const page = screen.getByTestId("screen-landing");
    expect(page).toHaveTextContent(/Base only, chainId 8453/i);
    expect(page).toHaveTextContent(/V2 remains live and unchanged/i);
    expect(page).toHaveTextContent(/Mutual aid is not insurance/i);
    expect(screen.getByTestId("simulation-badge")).toHaveTextContent("simulation");
  });
});
