import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';

// Mock dagre module with inline class definition
vi.mock('dagre', () => {
  // Define mock graph class inside the mock factory
  function MockGraph() {
    this.setDefaultEdgeLabel = vi.fn().mockReturnValue(this);
    this.setGraph = vi.fn().mockReturnValue(this);
    this.setNode = vi.fn().mockReturnValue(this);
    this.setEdge = vi.fn().mockReturnValue(this);
    this.node = vi.fn().mockReturnValue({ x: 100, y: 100 });
  }

  const dagreModule = {
    graphlib: {
      Graph: MockGraph,
    },
    layout: vi.fn(),
  };
  return { ...dagreModule, default: dagreModule };
});

// Mock ReactFlow and related components
vi.mock('reactflow', () => {
  return {
    __esModule: true,
    default: ({ children, nodes, edges, onNodeClick, onPaneClick }) => (
      <div data-testid="react-flow" onClick={onPaneClick}>
        {nodes?.map(node => (
          <div
            key={node.id}
            data-testid={`node-${node.id}`}
            className={`flow-node ${node.type}`}
            onClick={e => {
              e.stopPropagation();
              onNodeClick?.(e, node);
            }}
          >
            {/* For metrics, use shortName; for preaggs, use name */}
            {node.data.shortName || node.data.name}
          </div>
        ))}
        {children}
      </div>
    ),
    Background: () => <div data-testid="background" />,
    Controls: () => <div data-testid="controls" />,
    MarkerType: { ArrowClosed: 'arrowclosed' },
    useNodesState: nodes => [nodes, vi.fn(), vi.fn()],
    useEdgesState: edges => [edges, vi.fn(), vi.fn()],
    Handle: ({ type, position }) => (
      <div data-testid={`handle-${type}-${position}`} />
    ),
    Position: { Left: 'left', Right: 'right' },
  };
});

// Import the component after mocks are set up
import { MetricFlowGraph } from '../MetricFlowGraph';

const mockGrainGroups = [
  {
    parent_name: 'default.repair_orders',
    grain: ['date_id', 'customer_id'],
    components: [
      { name: 'sum_revenue', expression: 'SUM(revenue)' },
      { name: 'count_orders', expression: 'COUNT(*)' },
    ],
  },
  {
    parent_name: 'inventory.stock',
    grain: ['warehouse_id'],
    components: [{ name: 'sum_quantity', expression: 'SUM(quantity)' }],
  },
];

const mockMetricFormulas = [
  {
    name: 'default.num_repair_orders',
    short_name: 'num_repair_orders',
    combiner: 'SUM(count_orders)',
    is_derived: false,
    components: ['count_orders'],
  },
  {
    name: 'default.avg_repair_price',
    short_name: 'avg_repair_price',
    combiner: 'SUM(sum_revenue) / SUM(count_orders)',
    is_derived: true,
    components: ['sum_revenue', 'count_orders'],
  },
  {
    name: 'inventory.total_stock',
    short_name: 'total_stock',
    combiner: 'SUM(sum_quantity)',
    is_derived: false,
    components: ['sum_quantity'],
  },
];

describe('MetricFlowGraph', () => {
  describe('Empty State', () => {
    it('shows empty state when no grain groups provided', () => {
      render(
        <MetricFlowGraph
          grainGroups={[]}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(
        screen.getByText(
          /Select metrics and dimensions above to visualize the data flow/i,
        ),
      ).toBeInTheDocument();
    });

    it('shows empty state when no metric formulas provided', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={[]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(
        screen.getByText(
          /Select metrics and dimensions above to visualize the data flow/i,
        ),
      ).toBeInTheDocument();
    });

    it('shows empty state when both are null', () => {
      render(
        <MetricFlowGraph
          grainGroups={null}
          metricFormulas={null}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(
        screen.getByText(
          /Select metrics and dimensions above to visualize the data flow/i,
        ),
      ).toBeInTheDocument();
    });

    it('shows empty icon in empty state', () => {
      render(
        <MetricFlowGraph
          grainGroups={[]}
          metricFormulas={[]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByText('◎')).toBeInTheDocument();
    });
  });

  describe('Graph Rendering', () => {
    it('renders ReactFlow when data is provided', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('renders background and controls', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByTestId('background')).toBeInTheDocument();
      expect(screen.getByTestId('controls')).toBeInTheDocument();
    });

    it('renders pre-aggregation nodes', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByText('repair_orders')).toBeInTheDocument();
      expect(screen.getByText('stock')).toBeInTheDocument();
    });

    it('renders metric nodes', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
      expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
      expect(screen.getByText('total_stock')).toBeInTheDocument();
    });
  });

  describe('Node Selection', () => {
    it('calls onNodeSelect with preagg data when preagg node is clicked', () => {
      const onNodeSelect = vi.fn();
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={onNodeSelect}
        />,
      );

      const preaggNode = screen.getByText('repair_orders');
      fireEvent.click(preaggNode);

      expect(onNodeSelect).toHaveBeenCalledWith(
        expect.objectContaining({
          type: 'preagg',
          index: 0,
          data: mockGrainGroups[0],
        }),
      );
    });

    it('calls onNodeSelect with metric data when metric node is clicked', () => {
      const onNodeSelect = vi.fn();
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={onNodeSelect}
        />,
      );

      const metricNode = screen.getByText('num_repair_orders');
      fireEvent.click(metricNode);

      expect(onNodeSelect).toHaveBeenCalledWith(
        expect.objectContaining({
          type: 'metric',
          index: 0,
          data: mockMetricFormulas[0],
        }),
      );
    });

    it('calls onNodeSelect with null when pane is clicked', () => {
      const onNodeSelect = vi.fn();
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={onNodeSelect}
        />,
      );

      const flowPane = screen.getByTestId('react-flow');
      fireEvent.click(flowPane);

      expect(onNodeSelect).toHaveBeenCalledWith(null);
    });
  });

  describe('Legend', () => {
    it('renders legend with Pre-agg, Metric, and Derived labels', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByText('Pre-agg')).toBeInTheDocument();
      expect(screen.getByText('Metric')).toBeInTheDocument();
      expect(screen.getByText('Derived')).toBeInTheDocument();
    });
  });

  describe('Node Types', () => {
    it('creates preagg nodes for each grain group', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      // 2 grain groups = 2 preagg nodes
      const preaggNodes = document.querySelectorAll('.flow-node.preagg');
      expect(preaggNodes.length).toBe(2);
    });

    it('creates metric nodes for each metric formula', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      // 3 metric formulas = 3 metric nodes
      const metricNodes = document.querySelectorAll('.flow-node.metric');
      expect(metricNodes.length).toBe(3);
    });
  });

  describe('Node Selection by Index', () => {
    it('selects correct preagg when second preagg is clicked', () => {
      const onNodeSelect = vi.fn();
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={onNodeSelect}
        />,
      );

      const stockNode = screen.getByText('stock');
      fireEvent.click(stockNode);

      expect(onNodeSelect).toHaveBeenCalledWith(
        expect.objectContaining({
          type: 'preagg',
          index: 1,
          data: mockGrainGroups[1],
        }),
      );
    });

    it('selects correct metric when derived metric is clicked', () => {
      const onNodeSelect = vi.fn();
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={onNodeSelect}
        />,
      );

      const derivedMetric = screen.getByText('avg_repair_price');
      fireEvent.click(derivedMetric);

      expect(onNodeSelect).toHaveBeenCalledWith(
        expect.objectContaining({
          type: 'metric',
          index: 1,
          data: expect.objectContaining({
            is_derived: true,
            short_name: 'avg_repair_price',
          }),
        }),
      );
    });
  });

  describe('Graph Layout', () => {
    it('uses dagre for layout computation', async () => {
      const dagre = await import('dagre');

      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
        />,
      );

      // dagre.layout should have been called
      expect(dagre.layout).toHaveBeenCalled();
    });
  });
});

describe('MetricFlowGraph Node Display', () => {
  it('shows parent name without namespace prefix', () => {
    render(
      <MetricFlowGraph
        grainGroups={[
          {
            parent_name: 'default.namespace.my_table',
            grain: ['id'],
            components: [{ name: 'comp1' }],
          },
        ]}
        metricFormulas={[
          {
            name: 'default.metric',
            short_name: 'metric',
            components: ['comp1'],
          },
        ]}
        onNodeSelect={vi.fn()}
      />,
    );

    // Should show just 'my_table', not the full path
    expect(screen.getByText('my_table')).toBeInTheDocument();
  });

  it('shows short name for metric', () => {
    render(
      <MetricFlowGraph
        grainGroups={mockGrainGroups}
        metricFormulas={mockMetricFormulas}
        onNodeSelect={vi.fn()}
      />,
    );

    // Should show short_name
    expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
    expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
  });

  describe('Branch Coverage - Edge Cases', () => {
    it('handles grain group with empty grain array', () => {
      const grainGroupsEmptyGrain = [
        {
          parent_name: 'default.orders',
          grain: [], // Empty grain array
          components: [{ name: 'count_orders', expression: 'COUNT(*)' }],
        },
      ];

      render(
        <MetricFlowGraph
          grainGroups={grainGroupsEmptyGrain}
          metricFormulas={[
            {
              name: 'default.metric',
              short_name: 'metric',
              components: ['count_orders'],
              is_derived: false,
            },
          ]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('handles grain group with no components', () => {
      const grainGroupsNoComponents = [
        {
          parent_name: 'default.orders',
          grain: ['date_id'],
          components: [], // Empty components
        },
      ];

      render(
        <MetricFlowGraph
          grainGroups={grainGroupsNoComponents}
          metricFormulas={[
            {
              name: 'default.metric',
              short_name: 'metric',
              components: [],
              is_derived: false,
            },
          ]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('handles grain group with undefined components', () => {
      const grainGroupsUndefinedComponents = [
        {
          parent_name: 'default.orders',
          grain: ['date_id'],
          // components is undefined
        },
      ];

      render(
        <MetricFlowGraph
          grainGroups={grainGroupsUndefinedComponents}
          metricFormulas={[
            {
              name: 'default.metric',
              short_name: 'metric',
              components: [],
              is_derived: false,
            },
          ]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('handles metric with is_derived false', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={[
            {
              name: 'default.simple_metric',
              short_name: 'simple_metric',
              combiner: 'SUM(count)',
              is_derived: false,
              components: ['count_orders'],
            },
          ]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByText('simple_metric')).toBeInTheDocument();
    });

    it('handles metric with is_derived true', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={[
            {
              name: 'default.derived_metric',
              short_name: 'derived_metric',
              combiner: 'SUM(a) / SUM(b)',
              is_derived: true,
              components: ['sum_revenue', 'count_orders'],
            },
          ]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByText('derived_metric')).toBeInTheDocument();
    });

    it('handles selectedNode prop for preagg', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
          selectedNode={{ type: 'preagg', index: 0, data: mockGrainGroups[0] }}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('handles selectedNode prop for metric', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
          selectedNode={{
            type: 'metric',
            index: 0,
            data: mockMetricFormulas[0],
          }}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('handles no selectedNode', () => {
      render(
        <MetricFlowGraph
          grainGroups={mockGrainGroups}
          metricFormulas={mockMetricFormulas}
          onNodeSelect={vi.fn()}
          selectedNode={null}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });

    it('handles grain group with undefined grain', () => {
      const grainGroupsUndefinedGrain = [
        {
          parent_name: 'default.orders',
          // grain is undefined
          components: [{ name: 'count_orders', expression: 'COUNT(*)' }],
        },
      ];

      render(
        <MetricFlowGraph
          grainGroups={grainGroupsUndefinedGrain}
          metricFormulas={[
            {
              name: 'default.metric',
              short_name: 'metric',
              components: ['count_orders'],
              is_derived: false,
            },
          ]}
          onNodeSelect={vi.fn()}
        />,
      );

      expect(screen.getByTestId('react-flow')).toBeInTheDocument();
    });
  });
});
