import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import DJClientContext from '../../../providers/djclient';
import { CubeBuilderPage } from '../index';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import React from 'react';

const mockDjClient = {
  metrics: vi.fn(),
  searchMetrics: vi.fn(),
  getMetricsInfo: vi.fn(),
  commonDimensions: vi.fn(),
  createCube: vi.fn(),
  namespaces: vi.fn(),
  cube: vi.fn(),
  getCubeForEditing: vi.fn(),
  node: vi.fn(),
  listTags: vi.fn(),
  tagsNode: vi.fn(),
  patchCube: vi.fn(),
  users: vi.fn(),
  whoami: vi.fn(),
  metricsV3: vi.fn(),
};

const mockMetrics = [
  'default.num_repair_orders',
  'default.avg_repair_price',
  'default.total_repair_cost',
];

const mockCube = {
  name: 'default.repair_orders_cube',
  type: 'CUBE',
  owners: [
    {
      username: 'someone@example.com',
    },
  ],
  current: {
    displayName: 'Default: Repair Orders Cube',
    description: 'Repairs cube',
    mode: 'DRAFT',
    cubeMetrics: [
      {
        name: 'default.total_repair_cost',
      },
      {
        name: 'default.num_repair_orders',
      },
    ],
    cubeDimensions: [
      {
        name: 'default.hard_hat.country',
        attribute: 'country',
        properties: ['dimension'],
      },
      {
        name: 'default.hard_hat.state',
        attribute: 'state',
        properties: ['dimension'],
      },
    ],
  },
  tags: [
    {
      name: 'repairs',
      displayName: 'Repairs Domain',
    },
  ],
};

const mockCommonDimensions = [
  {
    name: 'default.date_dim.dateint',
    type: 'timestamp',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.birth_date',
    ],
  },
  {
    name: 'default.date_dim.dateint',
    type: 'timestamp',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.hire_date',
    ],
  },
  {
    name: 'default.date_dim.day',
    type: 'int',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.birth_date',
    ],
  },
  {
    name: 'default.date_dim.day',
    type: 'int',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.hire_date',
    ],
  },
  {
    name: 'default.date_dim.month',
    type: 'int',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.birth_date',
    ],
  },
  {
    name: 'default.date_dim.month',
    type: 'int',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.hire_date',
    ],
  },
  {
    name: 'default.date_dim.year',
    type: 'int',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.birth_date',
    ],
  },
  {
    name: 'default.date_dim.year',
    type: 'int',
    node_name: 'default.date_dim',
    node_display_name: 'Date',
    properties: [],
    path: [
      'default.repair_order_details.repair_order_id',
      'default.repair_order.hard_hat_id',
      'default.hard_hat.hire_date',
    ],
  },
];

const mockSearchMetricsResults = mockMetrics.map(m => ({
  value: m,
  label: m,
}));

describe('CubeBuilderPage', () => {
  beforeEach(() => {
    mockDjClient.metrics.mockResolvedValue(mockMetrics);
    mockDjClient.searchMetrics.mockResolvedValue(mockSearchMetricsResults);
    mockDjClient.commonDimensions.mockResolvedValue(mockCommonDimensions);
    mockDjClient.createCube.mockResolvedValue({ status: 201, json: {} });
    mockDjClient.namespaces.mockResolvedValue(['default']);
    mockDjClient.getCubeForEditing.mockResolvedValue(mockCube);
    mockDjClient.listTags.mockResolvedValue([]);
    mockDjClient.tagsNode.mockResolvedValue([]);
    mockDjClient.patchCube.mockResolvedValue({ status: 201, json: {} });
    mockDjClient.users.mockResolvedValue([{ username: 'dj' }]);
    mockDjClient.whoami.mockResolvedValue({ username: 'dj' });
    mockDjClient.getMetricsInfo.mockResolvedValue([]);
    mockDjClient.metricsV3.mockResolvedValue({ sql: '', errors: [] });

    window.scrollTo = vi.fn();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it('renders without crashing', async () => {
    render(
      <MemoryRouter>
        <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
          <CubeBuilderPage />
        </DJClientContext.Provider>
      </MemoryRouter>,
    );

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

  it('renders the Metrics section', async () => {
    render(
      <MemoryRouter>
        <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
          <CubeBuilderPage />
        </DJClientContext.Provider>
      </MemoryRouter>,
    );

    expect(
      screen.getByRole('heading', { name: 'Metrics' }),
    ).toBeInTheDocument();
  });

  it('renders the Dimensions section', async () => {
    render(
      <MemoryRouter>
        <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
          <CubeBuilderPage />
        </DJClientContext.Provider>
      </MemoryRouter>,
    );

    expect(
      screen.getByRole('heading', { name: 'Dimensions' }),
    ).toBeInTheDocument();
  });

  it('shows the Create Cube submit button in Add mode', async () => {
    render(
      <MemoryRouter>
        <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
          <CubeBuilderPage />
        </DJClientContext.Provider>
      </MemoryRouter>,
    );

    expect(
      await screen.findByRole('button', { name: 'CreateCube' }),
    ).toHaveTextContent('Create Cube');
  });

  it('renders the Edit page with prefilled fields and saves via patchCube', async () => {
    render(
      <MemoryRouter
        initialEntries={['/nodes/default.repair_orders_cube/edit-cube']}
      >
        <Routes>
          <Route
            path="nodes/:name/edit-cube"
            element={
              <DJClientContext.Provider
                value={{ DataJunctionAPI: mockDjClient }}
              >
                <CubeBuilderPage />
              </DJClientContext.Provider>
            }
          />
        </Routes>
      </MemoryRouter>,
    );

    // The Edit branch fetches the cube and renders its name + display name.
    await waitFor(() =>
      expect(mockDjClient.getCubeForEditing).toHaveBeenCalledWith(
        'default.repair_orders_cube',
      ),
    );
    expect(
      await screen.findByText('default.repair_orders_cube'),
    ).toBeInTheDocument();
    // Heading reads "Edit" in edit mode.
    expect(screen.getByRole('heading', { name: /Edit/ })).toBeInTheDocument();

    // Save button reads "Save" in edit mode.
    const saveButton = screen.getByRole('button', { name: 'CreateCube' });
    expect(saveButton).toHaveTextContent('Save');
    fireEvent.click(saveButton);

    await waitFor(() => expect(mockDjClient.patchCube).toHaveBeenCalled(), {
      timeout: 1500,
    });
  });

  it('shows a save error when patchCube fails', async () => {
    mockDjClient.patchCube.mockResolvedValueOnce({
      status: 500,
      json: { message: 'something exploded' },
    });

    render(
      <MemoryRouter
        initialEntries={['/nodes/default.repair_orders_cube/edit-cube']}
      >
        <Routes>
          <Route
            path="nodes/:name/edit-cube"
            element={
              <DJClientContext.Provider
                value={{ DataJunctionAPI: mockDjClient }}
              >
                <CubeBuilderPage />
              </DJClientContext.Provider>
            }
          />
        </Routes>
      </MemoryRouter>,
    );

    await waitFor(() =>
      expect(mockDjClient.getCubeForEditing).toHaveBeenCalled(),
    );
    fireEvent.click(screen.getByRole('button', { name: 'CreateCube' }));
    expect(
      await screen.findByText(/something exploded/, {}, { timeout: 1500 }),
    ).toBeInTheDocument();
  });

  // TODO: Update test for async metrics search
  it.skip('creates a new cube', async () => {
    render(
      <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
        <CubeBuilderPage />
      </DJClientContext.Provider>,
    );

    const selectMetrics = screen.getAllByTestId('select-metrics')[0];
    expect(selectMetrics).toBeDefined();
    expect(selectMetrics).not.toBeNull();

    // Type to search for metrics (async select requires typing)
    const metricsInput = selectMetrics.querySelector('input');
    fireEvent.change(metricsInput, { target: { value: 'default' } });

    // Advance timers to flush the 300ms debounce
    vi.advanceTimersByTime(400);

    // Wait for search results
    await waitFor(() => {
      expect(mockDjClient.searchMetrics).toHaveBeenCalled();
    });

    // Wait for options to appear and click each one
    for (const metric of mockMetrics) {
      await waitFor(() => {
        expect(screen.getByText(metric)).toBeInTheDocument();
      });
      fireEvent.click(screen.getByText(metric));
    }

    fireEvent.click(screen.getAllByText('Dimensions *')[0]);

    // Wait for commonDimensions to be called and state to update
    await waitFor(() => {
      expect(mockDjClient.commonDimensions).toHaveBeenCalled();
    });

    const selectDimensions = screen.getAllByTestId('select-dimensions')[0];
    expect(selectDimensions).toBeDefined();
    expect(selectDimensions).not.toBeNull();

    await waitFor(() => {
      expect(
        screen.getByText(
          'default.repair_order_details.repair_order_id → default.repair_order.hard_hat_id → default.hard_hat.birth_date',
        ),
      ).toBeInTheDocument();
    });

    const selectDimensionsDate = screen.getAllByTestId(
      'dimensions-default.date_dim',
    )[0];

    fireEvent.keyDown(selectDimensionsDate.firstChild, { key: 'ArrowDown' });
    await waitFor(() => {
      expect(screen.getByText('Day')).toBeInTheDocument();
    });
    fireEvent.click(screen.getByText('Day'));
    fireEvent.click(screen.getByText('Month'));
    fireEvent.click(screen.getByText('Year'));
    fireEvent.click(screen.getByText('Dateint'));

    // Save
    const createCube = screen.getAllByRole('button', {
      name: 'CreateCube',
    })[0];
    expect(createCube).toBeInTheDocument();

    fireEvent.click(createCube);

    await waitFor(() => {
      expect(mockDjClient.createCube).toHaveBeenCalledWith(
        '',
        '',
        '',
        'published',
        [
          'default.num_repair_orders',
          'default.avg_repair_price',
          'default.total_repair_cost',
        ],
        [
          'default.date_dim.day',
          'default.date_dim.month',
          'default.date_dim.year',
          'default.date_dim.dateint',
        ],
        [],
      );
    });
  });

  const renderEditNode = element => {
    return render(
      <MemoryRouter
        initialEntries={['/nodes/default.repair_orders_cube/edit-cube']}
      >
        <Routes>
          <Route path="nodes/:name/edit-cube" element={element} />
        </Routes>
      </MemoryRouter>,
    );
  };

  // TODO: Update test for async metrics search
  it.skip('updates an existing cube', async () => {
    renderEditNode(
      <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
        <CubeBuilderPage />
      </DJClientContext.Provider>,
    );
    expect(screen.getAllByText('Edit')[0]).toBeInTheDocument();
    await waitFor(() => {
      expect(mockDjClient.getCubeForEditing).toHaveBeenCalled();
    });

    // In edit mode, existing metrics are pre-populated from cube data
    const selectMetrics = screen.getAllByTestId('select-metrics')[0];
    expect(selectMetrics).toBeDefined();
    expect(selectMetrics).not.toBeNull();

    // Wait for cube metrics to be loaded
    await waitFor(() => {
      expect(screen.getByText('default.num_repair_orders')).toBeInTheDocument();
    });

    fireEvent.click(screen.getAllByText('Dimensions *')[0]);

    // Wait for commonDimensions to be called and state to update
    await waitFor(() => {
      expect(mockDjClient.commonDimensions).toHaveBeenCalled();
    });

    const selectDimensions = screen.getAllByTestId('select-dimensions')[0];
    expect(selectDimensions).toBeDefined();
    expect(selectDimensions).not.toBeNull();

    await waitFor(() => {
      expect(
        screen.getByText(
          'default.repair_order_details.repair_order_id → default.repair_order.hard_hat_id → default.hard_hat.birth_date',
        ),
      ).toBeInTheDocument();
    });

    const selectDimensionsDate = screen.getAllByTestId(
      'dimensions-default.date_dim',
    )[0];

    fireEvent.keyDown(selectDimensionsDate.firstChild, { key: 'ArrowDown' });
    await waitFor(() => {
      expect(screen.getByText('Day')).toBeInTheDocument();
    });
    fireEvent.click(screen.getByText('Day'));
    fireEvent.click(screen.getByText('Month'));
    fireEvent.click(screen.getByText('Year'));
    fireEvent.click(screen.getByText('Dateint'));

    // Save
    const createCube = screen.getAllByRole('button', {
      name: 'CreateCube',
    })[0];
    expect(createCube).toBeInTheDocument();

    fireEvent.click(createCube);

    await waitFor(() => {
      expect(mockDjClient.patchCube).toHaveBeenCalledWith(
        'default.repair_orders_cube',
        'Default: Repair Orders Cube',
        'Repairs cube',
        'draft',
        ['default.total_repair_cost', 'default.num_repair_orders'],
        [
          'default.date_dim.day',
          'default.date_dim.month',
          'default.date_dim.year',
          'default.date_dim.dateint',
        ],
        [],
        [],
      );
    });
  });
});
