import { render, screen, fireEvent } from '@testing-library/react';
import { WorkflowRunViewer } from '../WorkflowRunViewer';
import { ExecNodeDetails } from '../ExecNodeDetails';
import type { WfExecutionView, NodeExecView } from '../types';

// jsdom lacks matchMedia, which DualPaneWorkspace uses for its `auto` layout.
beforeAll(() => {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: (query: string) => ({
      matches: false,
      media: query,
      onchange: null,
      addEventListener: () => {},
      removeEventListener: () => {},
      addListener: () => {},
      removeListener: () => {},
      dispatchEvent: () => false,
    }),
  });
});

function makeExecution(overrides: Partial<WfExecutionView> = {}): WfExecutionView {
  const nodeA: NodeExecView = {
    nodeId: 'a',
    name: 'Fetch data',
    type: 'trigger.event',
    status: 'success',
    executionOrder: 0,
    executionTimeMs: 1000,
    inputData: { foo: 'in' },
    outputData: { foo: 'out' },
  };
  const nodeB: NodeExecView = {
    nodeId: 'b',
    name: 'Transform',
    type: 'transform.set',
    status: 'running',
    executionOrder: 1,
  };
  return {
    executionId: 'exec-1',
    status: 'running',
    nodes: { a: nodeA, b: nodeB },
    ...overrides,
  };
}

describe('WorkflowRunViewer', () => {
  it('renders the run status header badge', () => {
    render(<WorkflowRunViewer execution={makeExecution({ status: 'completed' })} />);
    expect(screen.getByText('Completed')).toBeInTheDocument();
  });

  it('renders the timeline with a row per node, ordered by executionOrder', () => {
    render(<WorkflowRunViewer execution={makeExecution()} />);
    const rows = screen.getAllByRole('listitem');
    expect(rows).toHaveLength(2);
    expect(rows[0]).toHaveTextContent('Fetch data');
    expect(rows[1]).toHaveTextContent('Transform');
  });

  it('shows selected-node details when a timeline row is selected', () => {
    render(<WorkflowRunViewer execution={makeExecution()} />);
    fireEvent.click(screen.getByText('Fetch data'));
    // detail panel shows the selected node name as a heading + pretty JSON
    expect(
      screen.getByRole('heading', { name: 'Fetch data' })
    ).toBeInTheDocument();
    expect(screen.getByText(/"foo": "out"/)).toBeInTheDocument();
  });

  it('updates the selected-node details when a new snapshot is passed', () => {
    const { rerender } = render(<WorkflowRunViewer execution={makeExecution()} />);
    fireEvent.click(screen.getByText('Transform'));
    // node b selected; detail panel headed by its name
    expect(
      screen.getByRole('heading', { name: 'Transform' })
    ).toBeInTheDocument();

    const next = makeExecution({ status: 'completed' });
    next.nodes.b = {
      ...next.nodes.b,
      status: 'success',
      executionTimeMs: 250,
      outputData: { done: true },
    };
    rerender(<WorkflowRunViewer execution={next} />);
    // detail panel reflects the new snapshot for the still-selected node b
    expect(screen.getByText(/"done": true/)).toBeInTheDocument();
  });

  it('renders an empty state when there are no node executions', () => {
    render(
      <WorkflowRunViewer
        execution={{ executionId: 'x', status: 'pending', nodes: {} }}
      />
    );
    expect(screen.queryAllByRole('listitem')).toHaveLength(0);
    expect(screen.getByText('Pending')).toBeInTheDocument();
  });
});

describe('ExecNodeDetails', () => {
  const node: NodeExecView = {
    nodeId: 'a',
    name: 'Fetch data',
    type: 'trigger.event',
    status: 'error',
    executionOrder: 0,
    error: 'boom',
    inputData: { a: 1 },
    outputData: { b: 2 },
  };

  it('renders node name, type and status', () => {
    render(<ExecNodeDetails node={node} />);
    expect(screen.getByText('Fetch data')).toBeInTheDocument();
    expect(screen.getByText('trigger.event')).toBeInTheDocument();
    expect(screen.getByText('Error')).toBeInTheDocument();
  });

  it('pretty-prints input and output JSON', () => {
    render(<ExecNodeDetails node={node} />);
    expect(screen.getByText(/"a": 1/)).toBeInTheDocument();
    expect(screen.getByText(/"b": 2/)).toBeInTheDocument();
  });

  it('renders error message when present', () => {
    render(<ExecNodeDetails node={node} />);
    expect(screen.getByText('boom')).toBeInTheDocument();
  });

  it('renders an empty state when no node is selected', () => {
    render(<ExecNodeDetails node={null} />);
    expect(screen.getByText(/select a node/i)).toBeInTheDocument();
  });
});
