import { render, screen } from "@testing-library/svelte";
import { tick } from "svelte";
import { user } from "../setup-tests";
import DataTable from "./DataTable.test.svelte";

describe("DataTable", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  const headers = [
    { key: "name", value: "Name" },
    { key: "protocol", value: "Protocol" },
    { key: "port", value: "Port" },
    { key: "rule", value: "Rule" },
  ] as const;

  const rows = [
    {
      id: "a",
      name: "Load Balancer 3",
      protocol: "HTTP",
      port: 3000,
      rule: "Round robin",
    },
    {
      id: "b",
      name: "Load Balancer 1",
      protocol: "HTTP",
      port: 443,
      rule: "Round robin",
    },
    {
      id: "c",
      name: "Load Balancer 2",
      protocol: "HTTP",
      port: 80,
      rule: "DNS delegation",
    },
  ];

  // Basic rendering and structure tests
  it("renders with default props", async () => {
    const { container } = render(DataTable);
    // Check if table headers are rendered
    headers.forEach((header) => {
      expect(screen.getByText(header.value)).toBeInTheDocument();
    });

    // Check if table has correct structure
    const table = container.querySelector("table");
    expect(table).toBeInTheDocument();
    expect(table).toHaveClass("bx--data-table");

    // Check if table has correct number of rows
    const tableRows = container.querySelectorAll("tbody tr");
    expect(tableRows).toHaveLength(3);

    // Check if all rows contain the expected data
    rows.forEach((row) => {
      const rowElement = screen.getByText(row.name).closest("tr");
      expect(rowElement).toBeInTheDocument();

      if (rowElement) {
        const cells = rowElement.querySelectorAll("td");
        expect(cells.length).toBe(4);

        const protocolCell = Array.from(cells).find(
          (cell) => cell.textContent?.trim() === row.protocol,
        );
        expect(protocolCell).toBeInTheDocument();

        const portCell = Array.from(cells).find(
          (cell) => cell.textContent?.trim() === row.port.toString(),
        );
        expect(portCell).toBeInTheDocument();

        const ruleCell = Array.from(cells).find(
          (cell) => cell.textContent?.trim() === row.rule,
        );
        expect(ruleCell).toBeInTheDocument();
      }
    });
  });

  it("renders with title and description", () => {
    render(DataTable, {
      props: {
        title: "Test Table",
        description: "Test Description",
        headers,
        rows,
      },
    });

    expect(
      screen.getByRole("heading", { name: "Test Table" }),
    ).toBeInTheDocument();
    expect(screen.getByText("Test Description")).toBeInTheDocument();
  });

  it("handles empty table state", () => {
    const { container } = render(DataTable, {
      props: {
        headers,
        rows: [],
      },
    });

    const tableBody = container.querySelector("tbody");
    assert(tableBody);
    expect(tableBody.children.length).toBe(0);
  });

  it("handles table with only one column", () => {
    const singleColumnHeaders = [{ key: "name", value: "Name" }];

    const { container } = render(DataTable, {
      props: {
        headers: singleColumnHeaders,
        rows: rows.map(({ id, name }) => ({ id, name })),
      },
    });

    const columns = container.querySelectorAll("th");
    expect(columns.length).toBe(1);
    expect(columns[0]).toHaveTextContent("Name");
  });

  it("handles table with very long content", () => {
    const longContentRows = [
      {
        id: "a",
        name: "A very long name that should be truncated or wrapped in the table cell",
        protocol: "HTTP",
        port: 3000,
        rule: "Round robin",
      },
    ];

    const { container } = render(DataTable, {
      props: {
        headers,
        rows: longContentRows,
      },
    });

    const longCell = container.querySelector("td");
    assert(longCell);
    expect(longCell).toBeInTheDocument();
  });

  // Sorting tests
  it("handles sorting functionality", async () => {
    const { container } = render(DataTable, {
      props: {
        sortable: true,
        headers,
        rows,
      },
    });

    // Get all header cells
    const headerCells = container.querySelectorAll("th");
    expect(headerCells.length).toBe(4);

    // Test sorting by name (ascending)
    const nameHeader = screen.getByText("Name");
    await user.click(nameHeader);

    // Verify rows are sorted by name ascending
    const rowsAfterNameSort = container.querySelectorAll("tbody tr");
    const firstRowName = rowsAfterNameSort[0].querySelector("td");
    expect(firstRowName).toHaveTextContent("Load Balancer 1");

    // Test sorting by name (descending)
    await user.click(nameHeader);
    const rowsAfterNameDescSort = container.querySelectorAll("tbody tr");
    const firstRowNameDesc = rowsAfterNameDescSort[0].querySelector("td");
    expect(firstRowNameDesc).toHaveTextContent("Load Balancer 3");

    // Test sorting by port (ascending)
    const portHeader = screen.getByText("Port");
    await user.click(portHeader);

    // Verify rows are sorted by port ascending
    const rowsAfterPortSort = container.querySelectorAll("tbody tr");
    const firstRowPort = rowsAfterPortSort[0].querySelectorAll("td")[2];
    expect(firstRowPort).toHaveTextContent("80");

    // Test sorting by port (descending)
    await user.click(portHeader);
    const rowsAfterPortDescSort = container.querySelectorAll("tbody tr");
    const firstRowPortDesc = rowsAfterPortDescSort[0].querySelectorAll("td")[2];
    expect(firstRowPortDesc).toHaveTextContent("3000");
  });

  it("handles sorting with custom display and sort methods", async () => {
    const customHeaders = [
      { key: "name", value: "Name" },
      { key: "cost", value: "Cost", display: (cost: number) => cost + " €" },
      {
        key: "expireDate",
        value: "Expire date",
        display: (date: string) => new Date(date).toLocaleString(),
        sort: (a: string, b: string) =>
          new Date(a).getTime() - new Date(b).getTime(),
      },
    ];

    const customRows = [
      {
        id: "a",
        name: "Load Balancer 1",
        cost: 100,
        expireDate: "2024-01-01",
      },
      {
        id: "b",
        name: "Load Balancer 2",
        cost: 200,
        expireDate: "2023-12-31",
      },
      {
        id: "c",
        name: "Load Balancer 3",
        cost: 150,
        expireDate: "2024-02-01",
      },
    ];

    const { container } = render(DataTable, {
      props: {
        sortable: true,
        headers: customHeaders,
        rows: customRows,
      },
    });

    // Verify custom display formatting
    const costCells = container.querySelectorAll("td:nth-child(2)");
    expect(costCells[0]).toHaveTextContent("100 €");
    expect(costCells[1]).toHaveTextContent("200 €");

    // Test sorting by expireDate
    const dateHeader = screen.getByText("Expire date");
    await user.click(dateHeader);

    // Verify rows are sorted by date ascending
    const rowsAfterDateSort = container.querySelectorAll("tbody tr");
    expect(rowsAfterDateSort[0].querySelector("td")).toHaveTextContent(
      "Load Balancer 2",
    );
    expect(rowsAfterDateSort[2].querySelector("td")).toHaveTextContent(
      "Load Balancer 3",
    );
  });

  it("handles sorting with nested object values", async () => {
    const nestedHeaders = [
      { key: "name", value: "Name" },
      { key: "network.protocol", value: "Protocol" },
      { key: "network.port", value: "Port" },
    ] as const;

    const nestedRows = [
      {
        id: "a",
        name: "Load Balancer 1",
        network: { protocol: "HTTP", port: 3000 },
      },
      {
        id: "b",
        name: "Load Balancer 2",
        network: { protocol: "HTTPS", port: 443 },
      },
      {
        id: "c",
        name: "Load Balancer 3",
        network: { protocol: "HTTP", port: 80 },
      },
    ];

    const { container } = render(DataTable, {
      props: {
        sortable: true,
        headers: nestedHeaders,
        rows: nestedRows,
      },
    });

    // Test sorting by nested port value
    const portHeader = screen.getByText("Port");
    await user.click(portHeader);

    // Verify rows are sorted by port ascending
    const rowsAfterPortSort = container.querySelectorAll("tbody tr");
    expect(rowsAfterPortSort[0].querySelectorAll("td")[2]).toHaveTextContent(
      "80",
    );
    expect(rowsAfterPortSort[1].querySelectorAll("td")[2]).toHaveTextContent(
      "443",
    );
    expect(rowsAfterPortSort[2].querySelectorAll("td")[2]).toHaveTextContent(
      "3000",
    );
  });

  it("handles disabled sorting on specific columns", async () => {
    const customHeaders = [
      { key: "name", value: "Name" },
      { key: "protocol", value: "Protocol", sort: false as const },
      { key: "port", value: "Port" },
    ];

    const { container } = render(DataTable, {
      props: {
        sortable: true,
        headers: customHeaders,
        rows,
      },
    });

    const protocolHeader = screen.getByText("Protocol");
    await user.click(protocolHeader);

    // Verify no sorting occurred
    const firstRow = container.querySelector("tbody tr:first-child");
    assert(firstRow);
    expect(firstRow.querySelector("td:first-child")).toHaveTextContent(
      "Load Balancer 3",
    );
  });

  it("handles table with numeric sorting", async () => {
    const numericRows = [
      { id: "a", value: 10 },
      { id: "b", value: 2 },
      { id: "c", value: 20 },
    ];

    const numericHeaders = [{ key: "value", value: "Value" }];

    const { container } = render(DataTable, {
      props: {
        sortable: true,
        headers: numericHeaders,
        rows: numericRows,
      },
    });

    const valueHeader = screen.getByText("Value");
    await user.click(valueHeader);

    const sortedCells = container.querySelectorAll("td");
    expect(sortedCells[0]).toHaveTextContent("2");
    expect(sortedCells[1]).toHaveTextContent("10");
    expect(sortedCells[2]).toHaveTextContent("20");
  });

  // Selection tests
  it("handles selectable rows", async () => {
    const { container } = render(DataTable, {
      props: {
        selectable: true,
        headers,
        rows,
      },
    });

    // Verify checkboxes are present in each row
    const checkboxes = container.querySelectorAll("input[type='checkbox']");
    expect(checkboxes.length).toBe(3);

    // Select first row
    await user.click(checkboxes[0]);

    // Verify row is selected
    const selectedRow = container.querySelector(".bx--data-table--selected");
    expect(selectedRow).toBeInTheDocument();
  });

  it("handles batch selection", async () => {
    const { container } = render(DataTable, {
      props: {
        batchSelection: true,
        headers,
        rows,
      },
    });

    // Verify batch selection checkbox is present
    const batchCheckbox = container.querySelector(
      ".bx--table-column-checkbox input[type='checkbox']",
    );
    assert(batchCheckbox);

    // Click batch selection checkbox
    await user.click(batchCheckbox);

    // Verify all rows are selected
    const selectedRows = container.querySelectorAll(
      ".bx--data-table--selected",
    );
    expect(selectedRows.length).toBe(3);

    // Click batch selection checkbox again
    await user.click(batchCheckbox);

    // Verify no rows are selected
    const unselectedRows = container.querySelectorAll(
      ".bx--data-table--selected",
    );
    expect(unselectedRows.length).toBe(0);
  });

  it("handles radio selection", async () => {
    const { container } = render(DataTable, {
      props: {
        selectable: true,
        radio: true,
        headers,
        rows,
      },
    });

    const radioButtons = container.querySelectorAll("input[type='radio']");
    expect(radioButtons.length).toBe(3);

    await user.click(radioButtons[0]);
    const selectedRow = container.querySelector(".bx--data-table--selected");
    expect(selectedRow).toBeInTheDocument();
  });

  it("handles non-selectable and non-expandable rows", async () => {
    const { container } = render(DataTable, {
      props: {
        selectable: true,
        expandable: true,
        nonSelectableRowIds: ["b"],
        nonExpandableRowIds: ["c"],
        headers,
        rows,
      },
    });

    // Verify non-selectable row doesn't have a checkbox
    const nonSelectableRow = container.querySelector("tr[data-row='b']");
    const nonSelectableCheckbox = nonSelectableRow?.querySelector(
      "input[type='checkbox']",
    );
    expect(nonSelectableCheckbox).not.toBeInTheDocument();

    // Verify non-expandable row doesn't have an expand button
    const nonExpandableRow = container.querySelector("tr[data-row='c']");
    const nonExpandableButton = nonExpandableRow?.querySelector(
      ".bx--table-expand__button",
    );
    expect(nonExpandableButton).not.toBeInTheDocument();
  });

  // Expandable rows tests
  it("handles expandable rows", async () => {
    const { container } = render(DataTable, {
      props: {
        expandable: true,
        headers,
        rows,
      },
    });

    // Verify expand button is present in each row
    const expandButtons = container.querySelectorAll(
      ".bx--table-expand__button",
    );
    expect(expandButtons.length).toBe(3);

    // Click expand button on first row
    await user.click(expandButtons[0]);

    const expandedRow = container.querySelector(".bx--expandable-row");
    expect(expandedRow).toBeInTheDocument();
    expect(expandedRow).toHaveClass("bx--expandable-row bx--parent-row");
  });

  it("handles batch expansion", async () => {
    const { container } = render(DataTable, {
      props: {
        expandable: true,
        batchExpansion: true,
        headers,
        rows,
      },
    });

    expect(
      container.querySelectorAll(".bx--child-row-inner-container"),
    ).toHaveLength(0);

    const expandAllButton = screen.getByLabelText("Expand all rows");

    await user.click(expandAllButton);
    expect(
      container.querySelectorAll(".bx--child-row-inner-container"),
    ).toHaveLength(3);

    await user.click(expandAllButton);
    expect(
      container.querySelectorAll(".bx--child-row-inner-container"),
    ).toHaveLength(0);
  });

  // Styling and layout tests
  it("applies zebra stripe styling", async () => {
    const { container } = render(DataTable, {
      props: {
        zebra: true,
        headers,
        rows,
      },
    });

    // Verify zebra stripe classes are applied
    const table = container.querySelector("table");
    expect(table).toHaveClass("bx--data-table--zebra");
  });

  it("applies different size variants", async () => {
    type Size = "compact" | "short" | "medium" | "tall";
    const sizeMappings: Record<Size, string> = {
      compact: "bx--data-table--compact",
      short: "bx--data-table--short",
      medium: "bx--data-table--md",
      tall: "bx--data-table--tall",
    };

    for (const [size, expectedClass] of Object.entries(sizeMappings)) {
      const { container } = render(DataTable, {
        props: {
          size: size as Size,
          headers,
          rows,
        },
      });

      // Verify size class is applied
      const table = container.querySelector("table");
      expect(table).toHaveClass(expectedClass);
    }
  });

  it("applies custom column widths", async () => {
    const customHeaders = [
      { key: "name", value: "Name", width: "200px" },
      { key: "protocol", value: "Protocol", minWidth: "100px" },
      { key: "port", value: "Port" },
      { key: "rule", value: "Rule" },
    ] as const;

    const { container } = render(DataTable, {
      props: {
        headers: customHeaders,
        rows,
      },
    });

    // Verify table has fixed layout
    const table = container.querySelector("table");
    expect(table).toHaveStyle({ "table-layout": "fixed" });

    // Verify name column has correct width
    const nameHeader = container.querySelector("th:first-child");
    expect(nameHeader).toHaveStyle({ width: "200px" });

    // Verify protocol column has correct min-width
    const protocolHeader = container.querySelector("th:nth-child(2)");
    expect(protocolHeader).toHaveStyle({ "min-width": "100px" });
  });

  it("applies sticky header", async () => {
    const { container } = render(DataTable, {
      props: {
        stickyHeader: true,
        headers,
        rows,
      },
    });

    const table = container.querySelector("table");
    expect(table).toHaveClass("bx--data-table--sticky-header");
  });

  it("handles static width", () => {
    const { container } = render(DataTable, {
      props: {
        useStaticWidth: true,
        headers,
        rows,
      },
    });

    const tableContainer = container.querySelector(".bx--data-table-container");
    expect(tableContainer).toHaveClass("bx--data-table-container--static");

    const table = container.querySelector("table");
    expect(table).toHaveClass("bx--data-table--static");
  });

  // Custom cell display tests
  it("handles custom cell display", () => {
    const customHeaders = [
      { key: "name", value: "Name" },
      {
        key: "port",
        value: "Port",
        display: (value: number) => `Port ${value}`,
      },
    ];

    const { container } = render(DataTable, {
      props: {
        headers: customHeaders,
        rows,
      },
    });

    const portCells = container.querySelectorAll("td:nth-child(2)");
    expect(portCells[0]).toHaveTextContent("Port 3000");
    expect(portCells[1]).toHaveTextContent("Port 443");
    expect(portCells[2]).toHaveTextContent("Port 80");
  });

  it("handles empty columns for custom content", () => {
    const emptyColumnHeaders = [
      { key: "name", value: "Name" },
      { key: "protocol", value: "Protocol" },
      { key: "port", value: "Port" },
      { key: "rule", value: "Rule" },
      { key: "actions", value: "", empty: true },
    ] as const;

    const { container } = render(DataTable, {
      props: {
        headers: emptyColumnHeaders,
        rows,
      },
    });

    // Verify empty column header exists and has no text content
    const headerCells = screen.getAllByRole("columnheader");
    expect(headerCells.length).toBe(5); // 4 regular headers + 1 empty column
    expect(headerCells[4]).toHaveTextContent("");

    // Verify empty column cells exist in each row
    const tableRows = container.querySelectorAll("tbody tr");
    tableRows.forEach((row) => {
      const cells = row.querySelectorAll("td");
      expect(cells.length).toBe(5);
    });
  });

  // Pagination tests
  it("handles pagination", async () => {
    const paginatedRows = Array.from({ length: 15 }).map((_, i) => ({
      id: `row-${i}`,
      name: `Load Balancer ${i + 1}`,
      protocol: "HTTP",
      port: 3000 + i,
      rule: i % 2 ? "Round robin" : "DNS delegation",
    }));

    const { container, component } = render(DataTable, {
      props: {
        headers,
        rows: paginatedRows,
        pageSize: 5,
        page: 1,
      },
    });

    // Verify only 5 rows are displayed on first page
    const firstPageRows = container.querySelectorAll("tbody tr");
    expect(firstPageRows.length).toBe(5);
    expect(firstPageRows[0].querySelector("td")).toHaveTextContent(
      "Load Balancer 1",
    );
    expect(firstPageRows[4].querySelector("td")).toHaveTextContent(
      "Load Balancer 5",
    );

    // Update page to 2
    component.$set({ page: 2 });
    await tick();

    // Verify 5 rows are displayed on second page
    const secondPageRows = container.querySelectorAll("tbody tr");
    expect(secondPageRows.length).toBe(5);
    expect(secondPageRows[0].querySelector("td")).toHaveTextContent(
      "Load Balancer 6",
    );
    expect(secondPageRows[4].querySelector("td")).toHaveTextContent(
      "Load Balancer 10",
    );

    // Update page to 3
    component.$set({ page: 3 });
    await tick();

    // Verify remaining rows are displayed on third page
    const thirdPageRows = container.querySelectorAll("tbody tr");
    expect(thirdPageRows.length).toBe(5);
    expect(thirdPageRows[0].querySelector("td")).toHaveTextContent(
      "Load Balancer 11",
    );
    expect(thirdPageRows[4].querySelector("td")).toHaveTextContent(
      "Load Balancer 15",
    );
  });

  // Event handling tests
  it("emits proper events", async () => {
    const consoleLog = vi.spyOn(console, "log");

    const { container } = render(DataTable, {
      props: {
        headers,
        rows,
        sortable: true,
      },
    });

    // Click header
    const nameHeader = screen.getByText("Name");
    await user.click(nameHeader);

    // Click row
    const firstRow = container.querySelector("tbody tr");
    assert(firstRow);
    await user.click(firstRow);

    // Click cell
    const firstCell = container.querySelector("td");
    assert(firstCell);
    await user.click(firstCell);

    // Verify events were logged
    expect(consoleLog).toHaveBeenCalledWith("click:header", expect.any(Object));
    expect(consoleLog).toHaveBeenCalledWith("click:row", expect.any(Object));
    expect(consoleLog).toHaveBeenCalledWith("click:cell", expect.any(Object));
  });

  it("handles row hover events", async () => {
    const consoleLog = vi.spyOn(console, "log");
    const { container } = render(DataTable, {
      props: {
        headers,
        rows,
      },
    });

    const firstRow = container.querySelector("tbody tr");
    assert(firstRow);
    await user.hover(firstRow);
    await user.unhover(firstRow);

    expect(consoleLog).toHaveBeenCalledWith(
      "mouseenter:row",
      expect.any(Object),
    );
    expect(consoleLog).toHaveBeenCalledWith(
      "mouseleave:row",
      expect.any(Object),
    );
  });
});
