/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { bindActionCreators } from 'redux';

import {
  fireEvent,
  render,
  waitFor,
  within,
} from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';

import * as saveModalActions from 'src/explore/actions/saveModalActions';
import SaveModal, { PureSaveModal } from 'src/explore/components/SaveModal';
import * as dashboardStateActions from 'src/dashboard/actions/dashboardState';

jest.mock('@superset-ui/core/components/Select', () => ({
  ...jest.requireActual('@superset-ui/core/components/Select/AsyncSelect'),
  AsyncSelect: ({ onChange }) => (
    <input
      data-test="mock-async-select"
      onChange={({ target: { value } }) => onChange({ label: value, value })}
    />
  ),
}));

const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const initialState = {
  chart: {},
  saveModal: {
    dashboards: [],
    isVisible: true,
  },
  explore: {
    datasource: {},
    slice: {
      slice_id: 1,
      slice_name: 'title',
      owners: [1],
    },
    alert: null,
  },
  user: {
    userId: 1,
  },
};

const initialStore = mockStore(initialState);

const defaultProps = {
  addDangerToast: jest.fn(),
  onHide: () => ({}),
  actions: bindActionCreators(saveModalActions, arg => {
    if (typeof arg === 'function') {
      return arg(jest.fn);
    }
    return arg;
  }),
  form_data: { datasource: '107__table', url_params: { foo: 'bar' } },
};

const mockEvent = {
  target: {
    value: 'mock event target',
  },
  value: 10,
};

const mockDashboardData = {
  pks: ['id'],
  result: [{ id: 'id', dashboard_title: 'dashboard title' }],
};

const queryStore = mockStore({
  chart: {},
  saveModal: {
    dashboards: [],
    isVisible: true,
  },
  explore: {
    datasource: { name: 'test', type: 'query' },
    slice: null,
    alert: null,
  },
  user: {
    userId: 1,
  },
});

const fetchDashboardsEndpoint = `glob:*/dashboardasync/api/read?_flt_0_owners=${1}`;
const fetchChartEndpoint = `glob:*/api/v1/chart/${1}*`;
const fetchDashboardEndpoint = `glob:*/api/v1/dashboard/*`;

beforeAll(() => {
  fetchMock.get(fetchDashboardsEndpoint, mockDashboardData);
  fetchMock.get(fetchChartEndpoint, { id: 1, dashboards: [1] });
  fetchMock.get(fetchDashboardEndpoint, {
    result: [{ id: 'id', dashboard_title: 'dashboard title' }],
  });
});

afterAll(() => fetchMock.restore());

const setup = (props = defaultProps, store = initialStore) =>
  render(<SaveModal {...props} />, {
    useRouter: true,
    store,
  });

test('renders a Modal with the right set of components', () => {
  const { getByRole, getByTestId } = setup();
  expect(getByRole('dialog', { name: 'Save chart' })).toBeInTheDocument();
  expect(getByRole('radio', { name: 'Save (Overwrite)' })).toBeInTheDocument();
  expect(getByRole('radio', { name: 'Save as...' })).toBeInTheDocument();
  expect(
    within(getByTestId('save-modal-footer')).getAllByRole('button'),
  ).toHaveLength(3);
});

test('renders the right footer buttons', () => {
  const { getByTestId } = setup();
  expect(
    within(getByTestId('save-modal-footer')).getByRole('button', {
      name: 'Cancel',
    }),
  ).toBeInTheDocument();
  expect(
    within(getByTestId('save-modal-footer')).getByRole('button', {
      name: 'Save & go to dashboard',
    }),
  ).toBeInTheDocument();
  expect(
    within(getByTestId('save-modal-footer')).getByRole('button', {
      name: 'Save',
    }),
  ).toBeInTheDocument();
});

test('does not render a message when overriding', () => {
  const { getByRole, queryByRole } = setup();

  fireEvent.click(getByRole('radio', { name: 'Save (Overwrite)' }));
  expect(
    queryByRole('alert', { name: 'A new chart will be created.' }),
  ).not.toBeInTheDocument();
});

test('renders a message when saving as', () => {
  const { getByRole } = setup(
    {},
    mockStore({
      ...initialState,
      explore: {
        ...initialState.explore,
        slice: {
          ...initialState.explore.slice,
          is_managed_externally: true,
        },
      },
    }),
  );
  fireEvent.click(getByRole('radio', { name: 'Save as...' }));
  expect(getByRole('alert')).toHaveTextContent('A new chart will be created.');
});

test('renders a message when a new dashboard is selected', async () => {
  const { getByRole, getByTestId } = setup();

  const selection = getByTestId('mock-async-select');
  fireEvent.change(selection, { target: { value: 'Test new dashboard' } });

  expect(getByRole('alert')).toHaveTextContent(
    'A new dashboard will be created.',
  );
});

test('renders a message when saving as with new dashboard', () => {
  const { getByRole, getByTestId } = setup(
    {},
    mockStore({
      ...initialState,
      explore: {
        ...initialState.explore,
        slice: {
          ...initialState.explore.slice,
          is_managed_externally: true,
        },
      },
    }),
  );
  fireEvent.click(getByRole('radio', { name: 'Save as...' }));
  const selection = getByTestId('mock-async-select');
  fireEvent.change(selection, { target: { value: 'Test new dashboard' } });

  expect(getByRole('alert')).toHaveTextContent(
    'A new chart and dashboard will be created.',
  );
});

test('disables overwrite option for new slice', () => {
  const { getByRole } = setup(
    {},
    mockStore({
      ...initialState,
      explore: {
        ...initialState.explore,
        slice: null,
      },
    }),
  );
  expect(getByRole('radio', { name: 'Save (Overwrite)' })).toBeDisabled();
});

test('disables overwrite option for non-owner', () => {
  const { getByRole } = setup(
    {},
    mockStore({
      ...initialState,
      user: { userId: 2 },
    }),
  );
  expect(getByRole('radio', { name: 'Save (Overwrite)' })).toBeDisabled();
});

test('updates slice name and selected dashboard', async () => {
  const dashboardId = mockEvent.value;
  const saveDataset = jest.fn().mockResolvedValue();
  const createDashboard = jest.fn().mockResolvedValue({ id: dashboardId });
  const saveSliceFailed = jest.fn();
  const setFormData = jest.fn();
  const createSlice = jest.fn().mockResolvedValue({ id: 1 });

  const { getByRole, getByTestId } = setup(
    {
      actions: {
        saveDataset,
        createDashboard,
        saveSliceFailed,
        setFormData,
        createSlice,
      },
    },
    queryStore,
  );

  fireEvent.change(getByTestId('new-chart-name'), mockEvent);
  fireEvent.change(getByTestId('new-dataset-name'), mockEvent);
  const selection = getByTestId('mock-async-select');
  fireEvent.change(selection, { target: { value: dashboardId } });

  expect(getByRole('button', { name: 'Save' })).toBeEnabled();

  fireEvent.click(getByRole('button', { name: 'Save' }));
  expect(saveDataset).toHaveBeenCalledWith(
    expect.objectContaining({
      datasourceName: mockEvent.target.value,
    }),
  );
  await waitFor(() =>
    expect(fetchMock.calls(fetchDashboardEndpoint)).toHaveLength(1),
  );
  expect(fetchMock.calls(fetchDashboardEndpoint)[0][0]).toEqual(
    expect.stringContaining(`dashboard/${dashboardId}`),
  );
  expect(createSlice).toHaveBeenCalledWith(
    mockEvent.target.value,
    expect.anything(),
    expect.anything(),
  );
});

test('set dataset name when chart source is query', () => {
  const { getByTestId } = setup({}, queryStore);
  expect(getByTestId('new-dataset-name')).toHaveValue('test');
});

test('make sure slice_id in the URLSearchParams before the redirect', () => {
  const myProps = {
    ...defaultProps,
    slice: { slice_id: 1, slice_name: 'title', owners: [1] },
    actions: {
      setFormData: jest.fn(),
      updateSlice: jest.fn(() => Promise.resolve({ id: 1 })),
      getSliceDashboards: jest.fn(),
    },
    user: { userId: 1 },
    history: {
      replace: jest.fn(),
    },
    dispatch: jest.fn(),
  };

  const saveModal = new PureSaveModal(myProps);
  const result = saveModal.handleRedirect(
    'https://example.com/?name=John&age=30',
    { id: 1 },
  );
  expect(result.get('slice_id')).toEqual('1');
});

test('removes form_data_key from URL parameters after save', () => {
  const myProps = {
    ...defaultProps,
    slice: { slice_id: 1, slice_name: 'title', owners: [1] },
    actions: {
      setFormData: jest.fn(),
      updateSlice: jest.fn(() => Promise.resolve({ id: 1 })),
      getSliceDashboards: jest.fn(),
    },
    user: { userId: 1 },
    history: {
      replace: jest.fn(),
    },
    dispatch: jest.fn(),
  };

  const saveModal = new PureSaveModal(myProps);

  // Test with form_data_key in the URL
  const urlWithFormDataKey = '?form_data_key=12345&other_param=value';
  const result = saveModal.handleRedirect(urlWithFormDataKey, { id: 1 });

  // form_data_key should be removed
  expect(result.has('form_data_key')).toBe(false);
  // other parameters should remain
  expect(result.get('other_param')).toEqual('value');
  expect(result.get('slice_id')).toEqual('1');
  expect(result.get('save_action')).toEqual('overwrite');
});

test('dispatches removeChartState when saving and going to dashboard', async () => {
  // Spy on the removeChartState action creator
  const removeChartStateSpy = jest.spyOn(
    dashboardStateActions,
    'removeChartState',
  );

  // Mock the dashboard API response
  const dashboardId = 123;
  const dashboardUrl = '/superset/dashboard/test-dashboard/';
  fetchMock.get(
    `glob:*/api/v1/dashboard/${dashboardId}*`,
    {
      result: {
        id: dashboardId,
        dashboard_title: 'Test Dashboard',
        url: dashboardUrl,
      },
    },
    { overwriteRoutes: true },
  );

  const mockDispatch = jest.fn();
  const mockHistory = {
    push: jest.fn(),
    replace: jest.fn(),
  };
  const chartId = 42;
  const mockUpdateSlice = jest.fn(() => Promise.resolve({ id: chartId }));
  const mockSetFormData = jest.fn();

  const myProps = {
    ...defaultProps,
    slice: { slice_id: 1, slice_name: 'title', owners: [1] },
    actions: {
      setFormData: mockSetFormData,
      updateSlice: mockUpdateSlice,
      getSliceDashboards: jest.fn(() => Promise.resolve([])),
      saveSliceFailed: jest.fn(),
    },
    user: { userId: 1 },
    history: mockHistory,
    dispatch: mockDispatch,
  };

  const saveModal = new PureSaveModal(myProps);
  saveModal.state = {
    action: 'overwrite',
    newSliceName: 'test chart',
    datasetName: 'test dataset',
    dashboard: { label: 'Test Dashboard', value: dashboardId },
    saveStatus: null,
  };

  // Mock onHide to prevent errors
  saveModal.onHide = jest.fn();

  // Trigger save and go to dashboard (gotodash = true)
  await saveModal.saveOrOverwrite(true);

  // Wait for async operations
  await waitFor(() => {
    expect(mockUpdateSlice).toHaveBeenCalled();
    expect(mockSetFormData).toHaveBeenCalled();
  });

  // Verify removeChartState was called with the correct chart ID
  expect(removeChartStateSpy).toHaveBeenCalledWith(chartId);

  // Verify the action was dispatched (check the action object directly)
  expect(mockDispatch).toHaveBeenCalled();
  expect(mockDispatch).toHaveBeenCalledWith({
    type: 'REMOVE_CHART_STATE',
    chartId,
  });

  // Verify navigation happened
  expect(mockHistory.push).toHaveBeenCalled();

  // Clean up
  removeChartStateSpy.mockRestore();
});
