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

// Mock the CSS import
vi.mock('../../../../styles/preaggregations.css', () => ({}));

// Mock cronstrue - it's imported via require() in the component
vi.mock('cronstrue', () => {
  const mock = {
    toString: cron => {
      if (cron === '0 0 * * *') return 'At 12:00 AM';
      if (cron === '0 * * * *') return 'Every hour';
      return cron || 'Not scheduled';
    },
  };
  return { ...mock, default: mock };
});

// Mock labelize from utils/form
vi.mock('../../../../utils/form', () => ({
  labelize: str => {
    if (!str) return '';
    // Convert snake_case/SCREAMING_SNAKE to Title Case
    return str
      .toLowerCase()
      .replace(/_/g, ' ')
      .replace(/\b\w/g, c => c.toUpperCase());
  },
}));

const mockNode = {
  name: 'default.orders_fact',
  version: 'v1.0',
  type: 'transform',
};

const mockPreaggs = {
  items: [
    {
      id: 1,
      node_revision_id: 1,
      node_name: 'default.orders_fact',
      node_version: 'v1.0',
      grain_columns: [
        'default.date_dim.date_id',
        'default.customer_dim.customer_id',
      ],
      measures: [
        {
          name: 'total_revenue',
          expression: 'revenue',
          aggregation: 'SUM',
          merge: 'SUM',
          rule: { type: 'full' },
          used_by_metrics: [
            { name: 'default.revenue_metric', display_name: 'Revenue' },
          ],
        },
        {
          name: 'order_count',
          expression: '*',
          aggregation: 'COUNT',
          merge: 'SUM',
          rule: { type: 'full' },
          used_by_metrics: [
            { name: 'default.order_count_metric', display_name: 'Order Count' },
          ],
        },
      ],
      sql: 'SELECT date_id, customer_id, SUM(revenue), COUNT(*) FROM orders GROUP BY 1, 2',
      grain_group_hash: 'abc123',
      strategy: 'full',
      schedule: '0 0 * * *',
      lookback_window: null,
      workflow_urls: [
        { label: 'scheduled', url: 'http://scheduler/workflow/123.main' },
      ],
      workflow_status: 'active',
      status: 'active',
      materialized_table_ref: 'analytics.preaggs.orders_fact_abc123',
      max_partition: ['2024', '01', '15'],
      related_metrics: ['default.revenue_metric', 'default.order_count_metric'],
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-15T00:00:00Z',
    },
    {
      id: 2,
      node_revision_id: 1,
      node_name: 'default.orders_fact',
      node_version: 'v1.0',
      grain_columns: ['default.product_dim.category'],
      measures: [
        {
          name: 'total_quantity',
          expression: 'quantity',
          aggregation: 'SUM',
          merge: 'SUM',
          rule: { type: 'full' },
          used_by_metrics: null,
        },
      ],
      sql: 'SELECT category, SUM(quantity) FROM orders GROUP BY 1',
      grain_group_hash: 'def456',
      strategy: 'incremental_time',
      schedule: '0 * * * *',
      lookback_window: '3 days',
      workflow_urls: null,
      workflow_status: null,
      status: 'pending',
      materialized_table_ref: null,
      max_partition: null,
      related_metrics: null,
      created_at: '2024-01-10T00:00:00Z',
      updated_at: null,
    },
  ],
  total: 2,
  limit: 50,
  offset: 0,
};

const mockPreaggsWithStale = {
  items: [
    ...mockPreaggs.items,
    {
      id: 3,
      node_revision_id: 0,
      node_name: 'default.orders_fact',
      node_version: 'v0.9', // Stale version
      grain_columns: ['default.date_dim.date_id'],
      measures: [
        {
          name: 'old_revenue',
          expression: 'revenue',
          aggregation: 'SUM',
          merge: 'SUM',
          rule: { type: 'full' },
          used_by_metrics: null,
        },
      ],
      sql: 'SELECT date_id, SUM(revenue) FROM orders GROUP BY 1',
      grain_group_hash: 'old123',
      strategy: 'full',
      schedule: '0 0 * * *',
      workflow_urls: [
        { label: 'scheduled', url: 'http://scheduler/workflow/old.main' },
      ],
      workflow_status: 'active',
      status: 'active',
      materialized_table_ref: 'analytics.preaggs.orders_fact_old',
      max_partition: ['2024', '01', '10'],
      related_metrics: null,
      created_at: '2023-12-01T00:00:00Z',
      updated_at: '2024-01-10T00:00:00Z',
    },
  ],
  total: 3,
  limit: 50,
  offset: 0,
};

const createMockDjClient = (preaggs = mockPreaggs) => ({
  DataJunctionAPI: {
    listPreaggs: vi.fn().mockResolvedValue(preaggs),
    deactivatePreaggWorkflow: vi.fn().mockResolvedValue({ status: 'none' }),
    bulkDeactivatePreaggWorkflows: vi.fn().mockResolvedValue({
      deactivated_count: 1,
      deactivated: [{ id: 3 }],
    }),
  },
});

const renderWithContext = (component, djClient) => {
  return render(
    <MemoryRouter>
      <DJClientContext.Provider value={djClient}>
        {component}
      </DJClientContext.Provider>
    </MemoryRouter>,
  );
};

describe('<NodePreAggregationsTab />', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('Loading and Empty States', () => {
    it('shows loading state initially', () => {
      const djClient = createMockDjClient();
      // Make the promise never resolve to keep loading state
      djClient.DataJunctionAPI.listPreaggs.mockReturnValue(
        new Promise(() => {}),
      );

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      expect(
        screen.getByText('Loading pre-aggregations...'),
      ).toBeInTheDocument();
    });

    it('shows empty state when no pre-aggregations exist', async () => {
      const djClient = createMockDjClient({
        items: [],
        total: 0,
        limit: 50,
        offset: 0,
      });

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(
          screen.getByText('No pre-aggregations found for this node.'),
        ).toBeInTheDocument();
      });
    });

    it('shows error state when API fails', async () => {
      const djClient = createMockDjClient();
      djClient.DataJunctionAPI.listPreaggs.mockResolvedValue({
        _error: true,
        message: 'Failed to fetch',
      });

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(
          screen.getByText(/Error loading pre-aggregations/),
        ).toBeInTheDocument();
      });
    });
  });

  describe('Section Headers', () => {
    it('renders "Current Pre-Aggregations" section header with version', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(
          screen.getByText('Current Pre-Aggregations (v1.0)'),
        ).toBeInTheDocument();
      });
    });

    it('renders "Stale Pre-Aggregations" section when stale preaggs exist', async () => {
      const djClient = createMockDjClient(mockPreaggsWithStale);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(
          screen.getByText('Stale Pre-Aggregations (1)'),
        ).toBeInTheDocument();
      });
    });

    it('does not render stale section when no stale preaggs exist', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(
          screen.getByText('Current Pre-Aggregations (v1.0)'),
        ).toBeInTheDocument();
      });

      expect(
        screen.queryByText(/Stale Pre-Aggregations/),
      ).not.toBeInTheDocument();
    });
  });

  describe('Pre-agg Row Header', () => {
    it('renders grain columns as chips', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        // Should show short names from grain columns
        expect(screen.getByText('date_id')).toBeInTheDocument();
        expect(screen.getByText('customer_id')).toBeInTheDocument();
      });
    });

    it('renders measure count', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('2 measures')).toBeInTheDocument();
        expect(screen.getByText('1 measure')).toBeInTheDocument();
      });
    });

    it('renders metric count badge when related metrics exist', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('2 metrics')).toBeInTheDocument();
      });
    });

    it('renders status badges', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('Active')).toBeInTheDocument();
        expect(screen.getByText('Pending')).toBeInTheDocument();
      });
    });

    it('renders schedule in human-readable format', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        // Schedule appears in both header and config section, so use getAllByText
        const scheduleElements = screen.getAllByText(/at 12:00 am/i);
        expect(scheduleElements.length).toBeGreaterThan(0);
      });
    });
  });

  describe('Expanded Details', () => {
    it('expands first pre-agg by default', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        // Config section should be visible for the first expanded preagg
        expect(screen.getByText('Config')).toBeInTheDocument();
        expect(screen.getByText('Grain')).toBeInTheDocument();
        expect(screen.getByText('Measures')).toBeInTheDocument();
      });
    });

    it('shows Config section with strategy and schedule', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('Strategy')).toBeInTheDocument();
        expect(screen.getByText('Full')).toBeInTheDocument();
        expect(screen.getByText('Schedule')).toBeInTheDocument();
      });
    });

    it('shows Grain section with dimension links', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        // Full grain column names should appear in expanded section
        const grainBadges = screen.getAllByText(
          /default\.(date_dim|customer_dim)\./,
        );
        expect(grainBadges.length).toBeGreaterThan(0);
      });
    });

    it('shows Measures table with aggregation and merge info', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('total_revenue')).toBeInTheDocument();
        expect(screen.getByText('SUM(revenue)')).toBeInTheDocument();
      });
    });

    it('shows metrics that use each measure', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        // Display names should appear
        expect(screen.getByText('Revenue')).toBeInTheDocument();
        expect(screen.getByText('Order Count')).toBeInTheDocument();
      });
    });

    it('toggles expansion when clicking row header', async () => {
      // Use single preagg to simplify test
      const singlePreagg = {
        items: [mockPreaggs.items[0]],
        total: 1,
        limit: 50,
        offset: 0,
      };
      const djClient = createMockDjClient(singlePreagg);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      // Wait for initial render with expanded state
      await waitFor(() => {
        expect(screen.getByText('Config')).toBeInTheDocument();
      });

      // Find and click the row header to collapse it
      const measureText = screen.getByText('2 measures');
      fireEvent.click(measureText.closest('.preagg-row-header'));

      // After collapse, Config should no longer be visible
      await waitFor(
        () => {
          expect(screen.queryByText('Config')).not.toBeInTheDocument();
        },
        { timeout: 2000 },
      );
    });
  });

  describe('Workflow Actions', () => {
    it('renders workflow button with capitalized label', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        const scheduledBtn = screen.getByText('Scheduled');
        expect(scheduledBtn).toBeInTheDocument();
        expect(scheduledBtn.closest('a')).toHaveAttribute(
          'href',
          'http://scheduler/workflow/123.main',
        );
      });
    });

    it('renders deactivate button for active workflows', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('Deactivate')).toBeInTheDocument();
      });
    });

    it('calls deactivatePreaggWorkflow when deactivate is clicked', async () => {
      const djClient = createMockDjClient();
      window.confirm = vi.fn(() => true);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(
        () => {
          expect(screen.getByText('Deactivate')).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      fireEvent.click(screen.getByText('Deactivate'));

      await waitFor(
        () => {
          expect(
            djClient.DataJunctionAPI.deactivatePreaggWorkflow,
          ).toHaveBeenCalledWith(1);
        },
        { timeout: 2000 },
      );
    });
  });

  describe('Stale Pre-aggregations', () => {
    it('shows stale version warning in row header', async () => {
      const djClient = createMockDjClient(mockPreaggsWithStale);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('was v0.9')).toBeInTheDocument();
      });
    });

    it('shows "Deactivate All Stale" button when active stale workflows exist', async () => {
      const djClient = createMockDjClient(mockPreaggsWithStale);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(screen.getByText('Deactivate All Stale')).toBeInTheDocument();
      });
    });

    it('calls bulkDeactivatePreaggWorkflows when "Deactivate All Stale" is clicked', async () => {
      const djClient = createMockDjClient(mockPreaggsWithStale);
      window.confirm = vi.fn(() => true);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(
        () => {
          expect(screen.getByText('Deactivate All Stale')).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      fireEvent.click(screen.getByText('Deactivate All Stale'));

      await waitFor(
        () => {
          expect(
            djClient.DataJunctionAPI.bulkDeactivatePreaggWorkflows,
          ).toHaveBeenCalledWith('default.orders_fact', true);
        },
        { timeout: 2000 },
      );
    });
  });

  describe('Grain Truncation', () => {
    it('shows "+N more" button when grain has more than MAX_VISIBLE_GRAIN columns', async () => {
      const manyGrainPreaggs = {
        items: [
          {
            ...mockPreaggs.items[0],
            grain_columns: [
              'default.dim1.col1',
              'default.dim2.col2',
              'default.dim3.col3',
              'default.dim4.col4',
              'default.dim5.col5',
              'default.dim6.col6',
              'default.dim7.col7',
              'default.dim8.col8',
              'default.dim9.col9',
              'default.dim10.col10',
              'default.dim11.col11',
              'default.dim12.col12',
            ],
          },
        ],
        total: 1,
        limit: 50,
        offset: 0,
      };
      const djClient = createMockDjClient(manyGrainPreaggs);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        // Should show "+N more" since there are 12 columns and MAX_VISIBLE_GRAIN is 10
        expect(screen.getByText('+2 more')).toBeInTheDocument();
      });
    });

    it('shows "Show less" after expanding grain list', async () => {
      const manyGrainPreaggs = {
        items: [
          {
            ...mockPreaggs.items[0],
            grain_columns: [
              'default.dim1.col1',
              'default.dim2.col2',
              'default.dim3.col3',
              'default.dim4.col4',
              'default.dim5.col5',
              'default.dim6.col6',
              'default.dim7.col7',
              'default.dim8.col8',
              'default.dim9.col9',
              'default.dim10.col10',
              'default.dim11.col11',
              'default.dim12.col12',
            ],
          },
        ],
        total: 1,
        limit: 50,
        offset: 0,
      };
      const djClient = createMockDjClient(manyGrainPreaggs);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(
        () => {
          expect(screen.getByText('+2 more')).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      // Click to expand
      fireEvent.click(screen.getByText('+2 more'));

      await waitFor(
        () => {
          expect(screen.getByText('Show less')).toBeInTheDocument();
          // All columns should now be visible
          expect(screen.getByText('default.dim12.col12')).toBeInTheDocument();
        },
        { timeout: 2000 },
      );
    });
  });

  describe('API Integration', () => {
    it('calls listPreaggs with include_stale=true', async () => {
      const djClient = createMockDjClient();

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(() => {
        expect(djClient.DataJunctionAPI.listPreaggs).toHaveBeenCalledWith({
          node_name: 'default.orders_fact',
          include_stale: true,
        });
      });
    });

    it('refreshes list after deactivate action', async () => {
      const djClient = createMockDjClient();
      window.confirm = vi.fn(() => true);

      renderWithContext(<NodePreAggregationsTab node={mockNode} />, djClient);

      await waitFor(
        () => {
          expect(screen.getByText('Deactivate')).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      fireEvent.click(screen.getByText('Deactivate'));

      await waitFor(
        () => {
          // Should be called twice: initial load + refresh after deactivate
          expect(djClient.DataJunctionAPI.listPreaggs).toHaveBeenCalledTimes(2);
        },
        { timeout: 2000 },
      );
    });
  });
});
