// @vitest-environment jsdom
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, waitFor, within, fireEvent, cleanup } from "@testing-library/react";
import React from "react";
import { MemoryRouter } from "react-router-dom";

// ── Shared mock state (vi.hoisted avoids TDZ with vi.mock hoisting) ────────────

const H = vi.hoisted(() => ({
  mockNavigate: vi.fn(),
  mockListNetworks: vi.fn(),
  mockListAvailableZtNetworks: vi.fn(),
  mockToast: vi.fn(),
  state: {
    orgId: "org-test-123" as string | null,
  },
  navigateCalls: [] as string[],
}));

vi.mock("react-router-dom", async () => {
  const actual = await vi.importActual("react-router-dom");
  return {
    ...actual,
    useNavigate: () => {
      const fn = (path: string) => {
        H.navigateCalls.push(path);
        H.mockNavigate(path);
      };
      return fn;
    },
    useParams: () => ({ orgId: H.state.orgId }),
  };
});

vi.mock("@/hooks/useCurrentOrganization", () => ({
  useCurrentOrganizationId: () => ({
    orgId: H.state.orgId,
    isLoading: false,
  }),
  useCurrentOrganization: () => ({
    org: {
      id: H.state.orgId,
      name: "Test Org",
      slug: "test-org",
      description: null,
      logo_url: null,
      is_active: true,
      role: "admin",
      created_at: "2024-01-01",
      updated_at: "2024-01-01",
    },
    isLoading: false,
  }),
}));

vi.mock("@/hooks/use-toast", () => ({
  useToast: () => ({
    toast: H.mockToast,
    dismiss: () => {},
    toasts: [],
  }),
}));

vi.mock("@/lib/api", () => ({
  api: {
    zerotier: {
      listNetworks: H.mockListNetworks,
      listAvailableZtNetworks: H.mockListAvailableZtNetworks,
      createNetwork: vi.fn(),
      updateNetwork: vi.fn(),
      deleteNetwork: vi.fn(),
    },
  },
  ApiError: class ApiError extends Error {
    code: number;
    type: string;
    details: Record<string, unknown>;
    constructor(message: string, code: number, type: string, details: Record<string, unknown> = {}) {
      super(message);
      this.name = "ApiError";
      this.code = code;
      this.type = type;
      this.details = details;
    }
  },
}));

import NetworksPage from "../src/pages/org/NetworksPage";

// ── Test data ──────────────────────────────────────────────────────────────────

const MOCK_NETWORKS = [
  {
    id: "net-001",
    organization_id: "org-test-123",
    name: "Production VPN",
    description: "Main production network",
    owner_user_id: "user-1",
    zerotier_network_id: "d6578dd03c894448",
    environment: "production" as const,
    request_mode: "approval_required" as const,
    default_activation_lifetime_minutes: 480,
    max_activation_lifetime_minutes: null,
    is_active: true,
    created_at: "2024-01-01T00:00:00Z",
    updated_at: "2024-01-01T00:00:00Z",
    deleted_at: null,
    approved_user_count: 25,
    active_membership_count: 12,
  },
  {
    id: "net-002",
    organization_id: "org-test-123",
    name: "Dev Network",
    description: "Development and staging",
    owner_user_id: "user-1",
    zerotier_network_id: "abcdef1234567890",
    environment: "development" as const,
    request_mode: "open" as const,
    default_activation_lifetime_minutes: 240,
    max_activation_lifetime_minutes: 1440,
    is_active: false,
    created_at: "2024-01-02T00:00:00Z",
    updated_at: "2024-01-02T00:00:00Z",
    deleted_at: null,
    approved_user_count: 5,
    active_membership_count: 0,
  },
];

const MOCK_ZT_NETWORKS = [
  {
    id: "zt-net-001",
    name: "External ZeroTier",
    description: "An external ZT network",
    owner_id: null,
    online_member_count: 3,
    authorized_member_count: 10,
    total_member_count: 10,
    already_managed: false,
    portal_network_id: null,
    portal_network_name: null,
  },
];

// ── Helpers ────────────────────────────────────────────────────────────────────

function renderPage() {
  return render(
    <MemoryRouter>
      <NetworksPage />
    </MemoryRouter>,
  );
}

// Default: all API calls return never-resolving promise (loading state)
// Individual tests override BEFORE calling renderPage().
beforeEach(() => {
  vi.clearAllMocks();
  H.navigateCalls.length = 0;
  H.mockListNetworks.mockImplementation(() => new Promise(() => {}));
  H.mockListAvailableZtNetworks.mockImplementation(() => new Promise(() => {}));
});

afterEach(() => {
  cleanup();
  vi.restoreAllMocks();
});

// ═══════════════════════════════════════════════════════════════════════════════
//  HAPPY PATH: Data Loading
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — Data Loading", () => {
  test("renders loading state while fetching networks", () => {
    renderPage();

    expect(screen.getByText("Loading networks…")).toBeDefined();
  });

  test("renders network data when API resolves", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    expect(screen.getByText("Dev Network")).toBeDefined();
    expect(screen.getByText("d6578dd03c894448")).toBeDefined();
    expect(screen.getByText("abcdef1234567890")).toBeDefined();
  });

  test("renders error state when API fails", async () => {
    H.mockListNetworks.mockRejectedValue(new Error("Network error"));

    renderPage();

    await waitFor(() => {
      expect(
        screen.getByText("Failed to load networks. Please try again."),
      ).toBeDefined();
    });
  });

  test("renders empty state when no networks exist", async () => {
    H.mockListNetworks.mockResolvedValue({ networks: [], count: 0 });

    renderPage();

    await waitFor(() => {
      expect(
        screen.getByText("No networks configured yet. Add one to get started."),
      ).toBeDefined();
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════════
//  NAVIGATION: Row Click
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — Row Click Navigation", () => {
  test("clicking a network row navigates to /org/zerotier/networks/{networkId}", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    const productionRow = screen.getByText("Production VPN").closest("button");
    expect(productionRow).not.toBeNull();

    fireEvent.click(productionRow!);

    expect(H.mockNavigate).toHaveBeenCalledTimes(1);
    expect(H.mockNavigate).toHaveBeenCalledWith("/org/zerotier/networks/net-001");
    expect(H.navigateCalls).toEqual(["/org/zerotier/networks/net-001"]);
  });

  test("clicking second network row navigates to its URL", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Dev Network")).toBeDefined();
    });

    const devRow = screen.getByText("Dev Network").closest("button");
    expect(devRow).not.toBeNull();

    fireEvent.click(devRow!);

    expect(H.mockNavigate).toHaveBeenCalledTimes(1);
    expect(H.mockNavigate).toHaveBeenCalledWith("/org/zerotier/networks/net-002");
  });

  test("navigate NOT called before any click", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    expect(H.mockNavigate).not.toHaveBeenCalled();
    expect(H.navigateCalls).toEqual([]);
  });
});

// ═══════════════════════════════════════════════════════════════════════════════
//  NAVIGATION: Dropdown "View details"
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — Dropdown View details Navigation", () => {
  test('"View details" dropdown item navigates to /org/zerotier/networks/{networkId}', async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    // The MoreHorizontal button is a CHILD of the row button (nested inside it).
    // Find it by looking for the button with the MoreHorizontal icon inside the row.
    const productionRow = screen.getByText("Production VPN").closest("button")!;
    // Find ALL nested buttons within the row
    const nestedButtons = productionRow.querySelectorAll("button");
    // The first nested button should be the MoreHorizontal dropdown trigger
    expect(nestedButtons.length).toBeGreaterThan(0);
    // Radix DropdownMenu opens on pointerdown
    fireEvent.pointerDown(nestedButtons[0]);

    // DropdownMenuContent renders in a portal, screen.getByText searches the whole document
    await waitFor(() => {
      expect(screen.getByText("View details")).toBeDefined();
    });

    fireEvent.click(screen.getByText("View details"));

    expect(H.mockNavigate).toHaveBeenCalledWith("/org/zerotier/networks/net-001");
  });

  test('"View details" for second network navigates to its URL', async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Dev Network")).toBeDefined();
    });

    const devRow = screen.getByText("Dev Network").closest("button")!;
    const nestedButtons = devRow.querySelectorAll("button");
    expect(nestedButtons.length).toBeGreaterThan(0);
    fireEvent.pointerDown(nestedButtons[0]);

    await waitFor(() => {
      expect(screen.getByText("View details")).toBeDefined();
    });

    fireEvent.click(screen.getByText("View details"));

    expect(H.mockNavigate).toHaveBeenCalledWith("/org/zerotier/networks/net-002");
  });
});

// ═══════════════════════════════════════════════════════════════════════════════
//  CARD DESCRIPTION TEXT
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — Card Description", () => {
  test("CardDescription reflects page navigation (not old drawer text)", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    const description = screen.getByText(
      "Click a network to manage members, devices, and access requests",
    );
    expect(description).toBeDefined();
  });

  test("old drawer-related text is absent", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    // The old Sheet content (Network Details, member list) should NOT be present
    expect(screen.queryByText("Network Details")).toBeNull();
    expect(screen.queryByText("Members")).toBeNull();
  });
});

// ═══════════════════════════════════════════════════════════════════════════════
//  ZERO TIER NETWORK PICKER SHEET
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — ZeroTier Picker Sheet", () => {
  test('"Import from ZeroTier" button is present', async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    const importButton = screen.getByRole("button", {
      name: /import from zerotier/i,
    });
    expect(importButton).toBeDefined();
  });

  test('clicking "Import from ZeroTier" opens the ZT Picker Sheet', async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });
    H.mockListAvailableZtNetworks.mockResolvedValue({
      networks: MOCK_ZT_NETWORKS,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    const importButton = screen.getByRole("button", {
      name: /import from zerotier/i,
    });
    fireEvent.click(importButton);

    // Wait for the Sheet to render with its content
    await waitFor(() => {
      expect(screen.getByText("External ZeroTier")).toBeDefined();
    });

    expect(screen.getByText("zt-net-001")).toBeDefined();
    expect(screen.getByText("Import")).toBeDefined();
  });

  test("ZT Picker calls API with correct orgId", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });
    H.mockListAvailableZtNetworks.mockResolvedValue({
      networks: MOCK_ZT_NETWORKS,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    const importButton = screen.getByRole("button", {
      name: /import from zerotier/i,
    });
    fireEvent.click(importButton);

    await waitFor(() => {
      expect(screen.getByText("External ZeroTier")).toBeDefined();
    });

    expect(H.mockListAvailableZtNetworks).toHaveBeenCalledTimes(1);
    expect(H.mockListAvailableZtNetworks).toHaveBeenCalledWith("org-test-123");
  });
});

// ═══════════════════════════════════════════════════════════════════════════════
//  DATA DISPLAY
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — Data Display", () => {
  test("displays network count badge", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    // The CardTitle contains "Portal Networks" and the count badge
    expect(screen.getByText("Portal Networks")).toBeDefined();
    // Badge with count "2" should be present
    expect(screen.getByText("2")).toBeDefined();
  });

  test("displays approved user counts", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    expect(screen.getByText("25")).toBeDefined();
    expect(screen.getByText("5")).toBeDefined();
  });

  test("displays active device counts", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    expect(screen.getByText("12")).toBeDefined();
    expect(screen.getByText("0")).toBeDefined();
  });

  test("displays environment badges", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    expect(screen.getByText("Production")).toBeDefined();
    expect(screen.getByText("Development")).toBeDefined();
  });

  test("displays request mode badges", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    expect(screen.getByText("Approval Required")).toBeDefined();
    expect(screen.getByText("Open")).toBeDefined();
  });

  test('displays "Inactive" badge for inactive networks', async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Dev Network")).toBeDefined();
    });

    expect(screen.getByText("Inactive")).toBeDefined();
  });

  test("renders search-empty state when filter matches nothing", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: MOCK_NETWORKS,
      count: MOCK_NETWORKS.length,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    const searchInput = screen.getByPlaceholderText("Search networks…");
    fireEvent.change(searchInput, { target: { value: "zzzz_nonexistent" } });

    await waitFor(() => {
      expect(
        screen.getByText("No networks match your search."),
      ).toBeDefined();
    });
  });
});

// ═══════════════════════════════════════════════════════════════════════════════
//  EDGE CASES / ADVERSARIAL INPUTS
// ═══════════════════════════════════════════════════════════════════════════════

describe("NetworksPage — Adversarial Inputs", () => {
  test("handles XSS-like network name as text", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: [
        {
          ...MOCK_NETWORKS[0],
          name: 'VPN <script>alert("xss")</script>',
          description: "desc ${injection}",
          zerotier_network_id: "../../etc/passwd",
        },
      ],
      count: 1,
    });

    renderPage();

    await waitFor(() => {
      expect(
        screen.getByText('VPN <script>alert("xss")</script>'),
      ).toBeDefined();
    });
  });

  test("handles very long network name", async () => {
    const longName = "A".repeat(500);
    H.mockListNetworks.mockResolvedValue({
      networks: [{ ...MOCK_NETWORKS[0], name: longName, id: "net-long" }],
      count: 1,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText(longName)).toBeDefined();
    });
  });

  test("handles Unicode network name", async () => {
    const unicodeName = "ネットワーク \u{1F525} 测试";
    H.mockListNetworks.mockResolvedValue({
      networks: [
        { ...MOCK_NETWORKS[0], name: unicodeName, id: "net-unicode" },
      ],
      count: 1,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText(unicodeName)).toBeDefined();
    });
  });

  test("handles missing optional counts (undefined)", async () => {
    H.mockListNetworks.mockResolvedValue({
      networks: [
        {
          ...MOCK_NETWORKS[0],
          approved_user_count: undefined,
          active_membership_count: undefined,
        },
      ],
      count: 1,
    });

    renderPage();

    await waitFor(() => {
      expect(screen.getByText("Production VPN")).toBeDefined();
    });

    // Should show "0" for undefined counts (nullish coalescing: ?? 0)
    const zeros = screen.getAllByText("0");
    expect(zeros.length).toBeGreaterThanOrEqual(2);
  });
});
