import { config } from '@grafana/runtime';
import {
  AdHocFiltersVariable,
  GroupByVariable,
  MultiValueVariable,
  sceneGraph,
  SceneRefreshPicker,
} from '@grafana/scenes';
import { Dashboard, VariableModel } from '@grafana/schema';
import {
  DashboardV2Spec,
  defaultDashboardV2Spec,
  defaultPanelSpec,
  defaultTimeSettingsSpec,
  GridLayoutKind,
  PanelSpec,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
import { AnnoKeyDashboardSnapshotOriginalUrl } from 'app/features/apiserver/types';
import { SaveDashboardAsOptions } from 'app/features/dashboard/components/SaveDashboard/types';
import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator';

import { buildPanelEditScene } from '../panel-edit/PanelEditor';
import { DashboardScene } from '../scene/DashboardScene';
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
import { findVizPanelByKey } from '../utils/utils';

import { V1DashboardSerializer, V2DashboardSerializer } from './DashboardSceneSerializer';
import { getPanelElement, transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene';
import { transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2';

jest.mock('@grafana/runtime', () => ({
  ...jest.requireActual('@grafana/runtime'),
  getDataSourceSrv: () => {
    return {
      getInstanceSettings: jest.fn(),
    };
  },
  config: {
    ...jest.requireActual('@grafana/runtime').config,
    bootData: {
      settings: {
        defaultDatasource: '-- Grafana --',
        datasources: {
          '-- Grafana --': {
            name: 'Grafana',
            meta: { id: 'grafana' },
            type: 'datasource',
          },
          prometheus: {
            name: 'prometheus',
            meta: { id: 'prometheus' },
            type: 'datasource',
          },
        },
      },
    },
  },
}));

describe('DashboardSceneSerializer', () => {
  describe('v1 schema', () => {
    beforeEach(() => {
      config.featureToggles.useV2DashboardsAPI = false;
    });

    it('Can detect no changes', () => {
      const dashboard = setup();
      const result = dashboard.getDashboardChanges(false);
      expect(result.hasChanges).toBe(false);
      expect(result.diffCount).toBe(0);
    });

    it('Can detect time changed', () => {
      const dashboard = setup();

      sceneGraph.getTimeRange(dashboard).setState({ from: 'now-1h', to: 'now' });

      const result = dashboard.getDashboardChanges(false);
      expect(result.hasChanges).toBe(false);
      expect(result.diffCount).toBe(0);
      expect(result.hasTimeChanges).toBe(true);
    });

    it('Can save time change', () => {
      const dashboard = setup();

      sceneGraph.getTimeRange(dashboard).setState({ from: 'now-1h', to: 'now' });

      const result = dashboard.getDashboardChanges(true);
      expect(result.hasChanges).toBe(true);
      expect(result.diffCount).toBe(1);
    });

    it('Can detect folder change', () => {
      const dashboard = setup();

      dashboard.state.meta.folderUid = 'folder-2';

      const result = dashboard.getDashboardChanges(false);
      expect(result.hasChanges).toBe(true);
      expect(result.diffCount).toBe(0); // Diff count is 0 because the diff contemplate only the model
      expect(result.hasFolderChanges).toBe(true);
    });

    it('Can detect refresh changed', () => {
      const dashboard = setup();

      const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker);
      if (refreshPicker instanceof SceneRefreshPicker) {
        refreshPicker.setState({ refresh: '5s' });
      }

      const result = dashboard.getDashboardChanges(false, false, false);
      expect(result.hasChanges).toBe(false);
      expect(result.diffCount).toBe(0);
      expect(result.hasRefreshChange).toBe(true);
    });

    it('Can save refresh change', () => {
      const dashboard = setup();

      const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker);
      if (refreshPicker instanceof SceneRefreshPicker) {
        refreshPicker.setState({ refresh: '5s' });
      }

      const result = dashboard.getDashboardChanges(false, false, true);
      expect(result.hasChanges).toBe(true);
      expect(result.diffCount).toBe(1);
    });

    describe('variable changes', () => {
      it('Can detect variable change', () => {
        const dashboard = setup();

        const appVar = sceneGraph.lookupVariable('app', dashboard) as MultiValueVariable;
        appVar.changeValueTo('app2');

        const result = dashboard.getDashboardChanges(false, false);

        expect(result.hasVariableValueChanges).toBe(true);
        expect(result.hasChanges).toBe(false);
        expect(result.diffCount).toBe(0);
      });

      it('Can save variable value change', () => {
        const dashboard = setup();

        const appVar = sceneGraph.lookupVariable('app', dashboard) as MultiValueVariable;
        appVar.changeValueTo('app2');

        const result = dashboard.getDashboardChanges(false, true);

        expect(result.hasVariableValueChanges).toBe(true);
        expect(result.hasChanges).toBe(true);
        expect(result.diffCount).toBe(2);
      });

      describe('Experimental variables', () => {
        beforeAll(() => {
          config.featureToggles.groupByVariable = true;
        });

        afterAll(() => {
          config.featureToggles.groupByVariable = false;
        });

        it('Can detect group by static options change', () => {
          const dashboard = transformSaveModelToScene({
            dashboard: {
              title: 'hello',
              uid: 'my-uid',
              schemaVersion: 30,
              panels: [
                {
                  id: 1,
                  title: 'Panel 1',
                  type: 'text',
                },
              ],
              version: 10,
              templating: {
                list: [
                  {
                    type: 'groupby',
                    datasource: {
                      type: 'ds',
                      uid: 'ds-uid',
                    },
                    name: 'GroupBy',
                    options: [
                      {
                        text: 'Host',
                        value: 'host',
                      },
                      {
                        text: 'Region',
                        value: 'region',
                      },
                    ],
                  },
                ],
              },
            },
            meta: {},
          });
          const initialSaveModel = transformSceneToSaveModel(dashboard);
          dashboard.setInitialSaveModel(initialSaveModel);

          const variable = sceneGraph.lookupVariable('GroupBy', dashboard) as GroupByVariable;
          variable.setState({ defaultOptions: [{ text: 'Host', value: 'host' }] });
          const result = dashboard.getDashboardChanges(false, true);

          expect(result.hasVariableValueChanges).toBe(false);
          expect(result.hasChanges).toBe(true);
          expect(result.diffCount).toBe(1);
        });

        it('Can detect adhoc filter static options change', () => {
          const adhocVar = {
            id: 'adhoc',
            name: 'adhoc',
            label: 'Adhoc Label',
            description: 'Adhoc Description',
            type: 'adhoc',
            datasource: {
              uid: 'gdev-prometheus',
              type: 'prometheus',
            },
            filters: [],
            baseFilters: [],
            defaultKeys: [
              {
                text: 'Host',
                value: 'host',
              },
              {
                text: 'Region',
                value: 'region',
              },
            ],
          } as VariableModel;

          const dashboard = transformSaveModelToScene({
            dashboard: {
              title: 'hello',
              uid: 'my-uid',
              schemaVersion: 30,
              panels: [
                {
                  id: 1,
                  title: 'Panel 1',
                  type: 'text',
                },
              ],
              version: 10,
              templating: {
                list: [adhocVar],
              },
            },
            meta: {},
          });

          const initialSaveModel = transformSceneToSaveModel(dashboard);
          dashboard.setInitialSaveModel(initialSaveModel);

          const variable = sceneGraph.lookupVariable('adhoc', dashboard) as AdHocFiltersVariable;
          variable.setState({ defaultKeys: [{ text: 'Host', value: 'host' }] });
          const result = dashboard.getDashboardChanges(false, false);

          expect(result.hasVariableValueChanges).toBe(false);
          expect(result.hasChanges).toBe(true);
          expect(result.diffCount).toBe(1);
        });
      });
    });

    describe('Saving from panel edit', () => {
      it('Should commit panel edit changes', () => {
        const dashboard = setup();
        const panel = findVizPanelByKey(dashboard, 'panel-1')!;
        const editScene = buildPanelEditScene(panel);

        dashboard.onEnterEditMode();
        dashboard.setState({ editPanel: editScene });

        editScene.state.panelRef.resolve().setState({ title: 'changed title' });

        const result = dashboard.getDashboardChanges(false, true);
        const panelSaveModel = (result.changedSaveModel as Dashboard).panels![0];
        expect(panelSaveModel.title).toBe('changed title');
      });
    });

    describe('tracking information', () => {
      it('provides dashboard tracking information with no initial save model', () => {
        const serializer = new V1DashboardSerializer();
        expect(serializer.getTrackingInformation()).toBe(undefined);
      });

      it('provides dashboard tracking information with from initial save model', () => {
        const dashboard = setup({
          schemaVersion: 30,
          version: 10,
          uid: 'my-uid',
          title: 'hello',
          liveNow: true,
          panels: [
            {
              type: 'text',
            },
            {
              type: 'text',
            },
            {
              type: 'timeseries',
            },
          ],

          templating: {
            list: [
              {
                type: 'query',
                name: 'server',
              },
              {
                type: 'query',
                name: 'host',
              },
              {
                type: 'textbox',
                name: 'search',
              },
            ],
          },
        });

        expect(dashboard.getTrackingInformation()).toEqual({
          uid: 'my-uid',
          title: 'hello',
          schemaVersion: DASHBOARD_SCHEMA_VERSION,
          panels_count: 3,
          panel_type_text_count: 2,
          panel_type_timeseries_count: 1,
          variable_type_query_count: 2,
          variable_type_textbox_count: 1,
          settings_nowdelay: undefined,
          settings_livenow: true,
        });
      });
    });

    it('should allow retrieving snapshot url', () => {
      const initialSaveModel: Dashboard = {
        snapshot: {
          originalUrl: 'originalUrl/snapshot',
          created: '2023-01-01T00:00:00Z',
          expires: '2023-12-31T23:59:59Z',
          external: false,
          externalUrl: '',
          id: 1,
          key: 'snapshot-key',
          name: 'snapshot-name',
          orgId: 1,
          updated: '2023-01-01T00:00:00Z',
          userId: 1,
        },
        title: 'hello',
        uid: 'my-uid',
        schemaVersion: 30,
        version: 10,
      };

      const serializer = new V1DashboardSerializer();
      serializer.initialSaveModel = initialSaveModel;

      expect(serializer.getSnapshotUrl()).toBe('originalUrl/snapshot');
    });

    describe('panel mapping methods', () => {
      let serializer: V1DashboardSerializer;

      beforeEach(() => {
        serializer = new V1DashboardSerializer();
      });

      it('should initialize panel mapping correctly', () => {
        const saveModel: Dashboard = {
          title: 'hello',
          uid: 'my-uid',
          schemaVersion: 30,
          panels: [
            { id: 1, title: 'Panel 1', type: 'text' },
            { id: 2, title: 'Panel 2', type: 'text' },
          ],
        };

        serializer.initializeMapping(saveModel);
        const mapping = serializer.getElementPanelMapping();

        expect(mapping.size).toBe(2);
        expect(mapping.get('panel-1')).toBe(1);
        expect(mapping.get('panel-2')).toBe(2);
      });

      it('should handle empty or undefined panels in initializeMapping', () => {
        serializer.initializeMapping(undefined);
        expect(serializer.getElementPanelMapping().size).toBe(0);

        serializer.initializeMapping({
          title: 'hello',
          uid: 'my-uid',
          schemaVersion: 30,
          panels: undefined,
        });
        expect(serializer.getElementPanelMapping().size).toBe(0);
      });

      it('should get panel id for element correctly', () => {
        const saveModel: Dashboard = {
          title: 'hello',
          uid: 'my-uid',
          schemaVersion: 30,
          panels: [
            { id: 1, title: 'Panel 1', type: 'text' },
            { id: 2, title: 'Panel 2', type: 'text' },
          ],
        };

        serializer.initializeMapping(saveModel);

        expect(serializer.getPanelIdForElement('panel-1')).toBe(1);
        expect(serializer.getPanelIdForElement('panel-2')).toBe(2);
        expect(serializer.getPanelIdForElement('non-existent')).toBeUndefined();
      });

      it('should get element id for panel correctly', () => {
        const saveModel: Dashboard = {
          title: 'hello',
          uid: 'my-uid',
          schemaVersion: 30,
          panels: [
            { id: 1, title: 'Panel 1', type: 'text' },
            { id: 2, title: 'Panel 2', type: 'text' },
          ],
        };
        serializer.initializeMapping(saveModel);

        expect(serializer.getElementIdForPanel(1)).toBe('panel-1');
        expect(serializer.getElementIdForPanel(2)).toBe('panel-2');
        // Should return default panel key for non-existent panel
        expect(serializer.getElementIdForPanel(3)).toBe('panel-3');
      });
    });
  });

  describe('v2 schema', () => {
    beforeEach(() => {
      config.featureToggles.useV2DashboardsAPI = true;
    });

    it('Can detect no changes', () => {
      const dashboard = setupV2();
      const result = dashboard.getDashboardChanges(false);
      expect(result.hasChanges).toBe(false);
      expect(result.diffCount).toBe(0);
    });

    it('Can detect time changed', () => {
      const dashboard = setupV2();

      sceneGraph.getTimeRange(dashboard).setState({ from: 'now-10h', to: 'now' });

      const result = dashboard.getDashboardChanges(false);
      expect(result.hasChanges).toBe(false);
      expect(result.diffCount).toBe(0);
      expect(result.hasTimeChanges).toBe(true);
    });

    it('Can save time change', () => {
      const dashboard = setupV2();

      sceneGraph.getTimeRange(dashboard).setState({ from: 'now-10h', to: 'now' });

      const result = dashboard.getDashboardChanges(true);
      expect(result.hasChanges).toBe(true);
      expect(result.diffCount).toBe(1);
    });

    it('Can detect folder change', () => {
      const dashboard = setupV2();

      dashboard.state.meta.folderUid = 'folder-2';

      const result = dashboard.getDashboardChanges(false);
      expect(result.hasChanges).toBe(true);
      expect(result.diffCount).toBe(0); // Diff count is 0 because the diff contemplate only the model
      expect(result.hasFolderChanges).toBe(true);
    });

    it('Can detect refresh changed', () => {
      const dashboard = setupV2();

      const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker);
      if (refreshPicker instanceof SceneRefreshPicker) {
        refreshPicker.setState({ refresh: '10m' });
      }

      const result = dashboard.getDashboardChanges(false, false, false);
      expect(result.hasChanges).toBe(false);
      expect(result.diffCount).toBe(0);
      expect(result.hasRefreshChange).toBe(true);
    });

    it('Can save refresh change', () => {
      const dashboard = setupV2();

      const refreshPicker = sceneGraph.findObject(dashboard, (obj) => obj instanceof SceneRefreshPicker);
      if (refreshPicker instanceof SceneRefreshPicker) {
        refreshPicker.setState({ refresh: '10m' });
      }

      const result = dashboard.getDashboardChanges(false, false, true);
      expect(result.hasChanges).toBe(true);
      expect(result.diffCount).toBe(1);
    });

    describe('variable changes', () => {
      it('Can detect variable change', () => {
        const dashboard = setupV2();

        const appVar = sceneGraph.lookupVariable('app', dashboard) as MultiValueVariable;
        appVar.changeValueTo('app2');

        const result = dashboard.getDashboardChanges(false, false);

        expect(result.hasVariableValueChanges).toBe(true);
        expect(result.hasChanges).toBe(false);
        expect(result.diffCount).toBe(0);
      });

      it('Can save variable value change', () => {
        const dashboard = setupV2();

        const appVar = sceneGraph.lookupVariable('app', dashboard) as MultiValueVariable;
        appVar.changeValueTo('app2');

        const result = dashboard.getDashboardChanges(false, true);

        expect(result.hasVariableValueChanges).toBe(true);
        expect(result.hasChanges).toBe(true);
        expect(result.diffCount).toBe(2);
      });

      describe('Experimental variables', () => {
        beforeAll(() => {
          config.featureToggles.groupByVariable = true;
        });

        afterAll(() => {
          config.featureToggles.groupByVariable = false;
        });

        it('Can detect group by static options change', () => {
          const dashboard = setupV2({
            variables: [
              {
                kind: 'GroupByVariable',
                spec: {
                  current: {
                    text: 'Host',
                    value: 'host',
                  },
                  datasource: {
                    type: 'ds',
                    uid: 'ds-uid',
                  },
                  name: 'GroupBy',
                  options: [
                    {
                      text: 'Host',
                      value: 'host',
                    },
                    {
                      text: 'Region',
                      value: 'region',
                    },
                  ],
                  multi: false,
                  hide: 'dontHide',
                  skipUrlSync: false,
                },
              },
            ],
          });

          const variable = sceneGraph.lookupVariable('GroupBy', dashboard) as GroupByVariable;
          variable.setState({ defaultOptions: [{ text: 'Host', value: 'host' }] });
          const result = dashboard.getDashboardChanges(false, true);

          expect(result.hasVariableValueChanges).toBe(false);
          expect(result.hasChanges).toBe(true);
          expect(result.diffCount).toBe(1);
        });

        it('Can detect adhoc filter static options change', () => {
          const dashboard = setupV2({
            variables: [
              {
                kind: 'AdhocVariable',
                spec: {
                  name: 'adhoc',
                  label: 'Adhoc Label',
                  description: 'Adhoc Description',
                  datasource: {
                    uid: 'gdev-prometheus',
                    type: 'prometheus',
                  },
                  hide: 'dontHide',
                  skipUrlSync: false,
                  filters: [],
                  baseFilters: [],
                  defaultKeys: [
                    {
                      text: 'Host',
                      value: 'host',
                    },
                    {
                      text: 'Region',
                      value: 'region',
                    },
                  ],
                },
              },
            ],
          });

          const variable = sceneGraph.lookupVariable('adhoc', dashboard) as AdHocFiltersVariable;
          variable.setState({ defaultKeys: [{ text: 'Host', value: 'host' }] });
          const result = dashboard.getDashboardChanges(false, false);

          expect(result.hasVariableValueChanges).toBe(false);
          expect(result.hasChanges).toBe(true);
          expect(result.diffCount).toBe(1);
        });
      });
    });

    describe('Saving from panel edit', () => {
      it('Should commit panel edit changes', () => {
        const dashboard = setupV2();
        const panel = findVizPanelByKey(dashboard, 'panel-1')!;
        const editScene = buildPanelEditScene(panel);

        dashboard.onEnterEditMode();
        dashboard.setState({ editPanel: editScene });

        editScene.state.panelRef.resolve().setState({ title: 'changed title' });

        const result = dashboard.getDashboardChanges(false, true);
        const panelSaveModel = getPanelElement(result.changedSaveModel as DashboardV2Spec, 'panel-1')!;

        expect(panelSaveModel.spec.title).toBe('changed title');
      });
    });

    describe('tracking information', () => {
      it('provides dashboard tracking information with no initial save model', () => {
        const dashboard = setupV2();
        const serializer = new V2DashboardSerializer();
        expect(serializer.getTrackingInformation(dashboard)).toBe(undefined);
      });

      it('provides dashboard tracking information with from initial save model', () => {
        const dashboard = setupV2({
          timeSettings: {
            nowDelay: '10s',
            from: '',
            to: '',
            autoRefresh: '',
            autoRefreshIntervals: [],
            hideTimepicker: false,
            fiscalYearStartMonth: 0,
            timezone: '',
          },
          liveNow: true,
        });

        expect(dashboard.getTrackingInformation()).toEqual({
          uid: 'dashboard-test',
          title: 'hello',
          panels_count: 1,
          panel_type__count: 1,
          variable_type_custom_count: 1,
          settings_nowdelay: undefined,
          settings_livenow: true,
          schemaVersion: DASHBOARD_SCHEMA_VERSION,
        });
      });
    });

    describe('getSaveAsModel', () => {
      let serializer: V2DashboardSerializer;
      let dashboard: DashboardScene;
      let baseOptions: SaveDashboardAsOptions;

      beforeEach(() => {
        serializer = new V2DashboardSerializer();
        dashboard = setupV2();
        baseOptions = {
          title: 'I am a new dashboard',
          description: 'description goes here',
          isNew: true,
          copyTags: true,
        };
      });

      it('should set basic dashboard properties correctly', () => {
        const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);

        expect(saveAsModel).toMatchObject({
          title: baseOptions.title,
          description: baseOptions.description,
          editable: true,
          annotations: [],
          cursorSync: 'Off',
          liveNow: false,
          preload: false,
          tags: [],
        });
      });

      it('should handle time settings correctly', () => {
        const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);

        expect(saveAsModel.timeSettings).toEqual({
          autoRefresh: '10s',
          autoRefreshIntervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'],
          fiscalYearStartMonth: 0,
          from: 'now-1h',
          hideTimepicker: false,
          nowDelay: undefined,
          timezone: 'browser',
          to: 'now',
        });
      });

      it('should correctly serialize panel elements', () => {
        const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);

        expect(saveAsModel.elements['panel-1']).toMatchObject({
          kind: 'Panel',
          spec: {
            data: {
              kind: 'QueryGroup',
              spec: {
                queries: [],
                queryOptions: {},
                transformations: [],
              },
            },
            description: '',
            id: 1,
            links: [],
            title: 'Panel 1',
          },
        });
      });

      it('should correctly serialize layout configuration', () => {
        const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);

        expect(saveAsModel.layout).toEqual({
          kind: 'GridLayout',
          spec: {
            items: [
              {
                kind: 'GridLayoutItem',
                spec: {
                  element: {
                    kind: 'ElementReference',
                    name: 'panel-1',
                  },
                  height: 8,
                  width: 12,
                  x: 0,
                  y: 0,
                },
              },
            ],
          },
        });
      });

      it('should correctly serialize variables', () => {
        const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);

        expect(saveAsModel.variables).toEqual([
          {
            kind: 'CustomVariable',
            spec: {
              allValue: undefined,
              current: {
                text: 'app1',
                value: 'app1',
              },
              description: 'A query variable',
              hide: 'dontHide',
              includeAll: false,
              label: 'Query Variable',
              multi: false,
              name: 'app',
              options: [],
              query: 'app1',
              skipUrlSync: false,
            },
          },
        ]);
      });

      it('should handle empty dashboard state', () => {
        const emptyDashboard = setupV2({
          elements: {},
          layout: { kind: 'GridLayout', spec: { items: [] } },
          variables: [],
        });

        const saveAsModel = serializer.getSaveAsModel(emptyDashboard, baseOptions);

        expect(saveAsModel.elements).toEqual({});
        expect(saveAsModel.layout.kind).toBe('GridLayout');
        expect((saveAsModel.layout as GridLayoutKind).spec.items).toEqual([]);
        expect(saveAsModel.variables).toEqual([]);
      });

      it('should preserve visualization config', () => {
        const dashboardWithVizConfig = setupV2({
          elements: {
            'panel-1': {
              kind: 'Panel',
              spec: {
                ...defaultPanelSpec(),
                id: 1,
                title: 'Panel 1',
                vizConfig: {
                  kind: 'graph',
                  spec: {
                    fieldConfig: {
                      defaults: { custom: { lineWidth: 2 } },
                      overrides: [],
                    },
                    options: { legend: { show: true } },
                    pluginVersion: '1.0.0',
                  },
                },
              },
            },
          },
        });

        const saveAsModel = serializer.getSaveAsModel(dashboardWithVizConfig, baseOptions);

        const panelSpec = saveAsModel.elements['panel-1'].spec as PanelSpec;
        expect(panelSpec.vizConfig).toMatchObject({
          kind: 'graph',
          spec: {
            fieldConfig: {
              defaults: { custom: { lineWidth: 2 } },
              overrides: [],
            },
            options: { legend: { show: true } },
            pluginVersion: '1.0.0',
          },
        });
      });
    });

    describe('panel mapping methods', () => {
      let serializer: V2DashboardSerializer;
      let saveModel: DashboardV2Spec;

      beforeEach(() => {
        serializer = new V2DashboardSerializer();
        saveModel = {
          ...defaultDashboardV2Spec(),
          elements: {
            'element-panel-a': {
              kind: 'Panel',
              spec: { ...defaultPanelSpec(), id: 1, title: 'Panel A' },
            },
            'element-panel-b': {
              kind: 'Panel',
              spec: { ...defaultPanelSpec(), id: 2, title: 'Panel B' },
            },
          },
          layout: {
            kind: 'GridLayout',
            spec: {
              items: [
                {
                  kind: 'GridLayoutItem',
                  spec: {
                    x: 0,
                    y: 0,
                    width: 12,
                    height: 8,
                    element: {
                      kind: 'ElementReference',
                      name: 'element-panel-a',
                    },
                  },
                },
                {
                  kind: 'GridLayoutItem',
                  spec: {
                    x: 0,
                    y: 0,
                    width: 12,
                    height: 8,
                    element: {
                      kind: 'ElementReference',
                      name: 'element-panel-b',
                    },
                  },
                },
              ],
            },
          },
        };
      });

      it('should initialize panel mapping correctly', () => {
        serializer.initializeMapping(saveModel);
        const mapping = serializer.getElementPanelMapping();

        expect(mapping.size).toBe(2);
        expect(mapping.get('element-panel-a')).toBe(1);
        expect(mapping.get('element-panel-b')).toBe(2);
      });

      it('should handle empty or undefined elements in initializeMapping', () => {
        serializer.initializeMapping({} as DashboardV2Spec);
        expect(serializer.getElementPanelMapping().size).toBe(0);

        serializer.initializeMapping({ elements: {} } as DashboardV2Spec);
        expect(serializer.getElementPanelMapping().size).toBe(0);
      });

      it('should get panel id for element correctly', () => {
        serializer.initializeMapping(saveModel);

        expect(serializer.getPanelIdForElement('element-panel-a')).toBe(1);
        expect(serializer.getPanelIdForElement('element-panel-b')).toBe(2);
        expect(serializer.getPanelIdForElement('non-existent')).toBeUndefined();
      });

      it('should get element id for panel correctly', () => {
        serializer.initializeMapping(saveModel);

        expect(serializer.getElementIdForPanel(1)).toBe('element-panel-a');
        expect(serializer.getElementIdForPanel(2)).toBe('element-panel-b');
        // Should return default panel key for non-existent panel
        expect(serializer.getElementIdForPanel(3)).toBe('panel-3');
      });
    });
  });

  describe('onSaveComplete', () => {
    it('should set the initialSaveModel correctly', () => {
      const serializer = new V2DashboardSerializer();
      const saveModel = defaultDashboardV2Spec();
      const response = {
        id: 1,
        uid: 'aa',
        slug: 'slug',
        url: 'url',
        version: 2,
        status: 'status',
      };

      serializer.onSaveComplete(saveModel, response);

      expect(serializer.initialSaveModel).toEqual({
        ...saveModel,
      });
    });

    it('should allow retrieving snapshot url', () => {
      const serializer = new V2DashboardSerializer();
      serializer.metadata = {
        name: 'dashboard-test',
        resourceVersion: '1',
        creationTimestamp: '2023-01-01T00:00:00Z',
        annotations: {
          [AnnoKeyDashboardSnapshotOriginalUrl]: 'originalUrl/snapshot',
        },
      };

      expect(serializer.getSnapshotUrl()).toBe('originalUrl/snapshot');
    });
  });
});

function setup(override: Partial<Dashboard> = {}) {
  const dashboard = transformSaveModelToScene({
    dashboard: {
      title: 'hello',
      uid: 'my-uid',
      schemaVersion: 30,
      panels: [
        {
          id: 1,
          title: 'Panel 1',
          type: 'text',
        },
      ],
      version: 10,
      templating: {
        list: [
          {
            name: 'app',
            type: 'custom',
            current: {
              text: 'app1',
              value: 'app1',
            },
          },
        ],
      },
      ...override,
    },
    meta: {},
  });

  const initialSaveModel = transformSceneToSaveModel(dashboard);
  dashboard.setInitialSaveModel(initialSaveModel);

  return dashboard;
}

function setupV2(spec?: Partial<DashboardV2Spec>) {
  const dashboard = transformSaveModelSchemaV2ToScene({
    kind: 'DashboardWithAccessInfo',
    spec: {
      ...defaultDashboardV2Spec(),
      title: 'hello',
      timeSettings: {
        ...defaultTimeSettingsSpec(),
        autoRefresh: '10s',
        from: 'now-1h',
        to: 'now',
      },
      elements: {
        'panel-1': {
          kind: 'Panel',
          spec: {
            ...defaultPanelSpec(),
            id: 1,
            title: 'Panel 1',
          },
        },
      },
      layout: {
        kind: 'GridLayout',
        spec: {
          items: [
            {
              kind: 'GridLayoutItem',
              spec: {
                x: 0,
                y: 0,
                width: 12,
                height: 8,
                element: {
                  kind: 'ElementReference',
                  name: 'panel-1',
                },
              },
            },
          ],
        },
      },
      variables: [
        {
          kind: 'CustomVariable',
          spec: {
            name: 'app',
            label: 'Query Variable',
            description: 'A query variable',
            skipUrlSync: false,
            hide: 'dontHide',
            options: [],
            multi: false,
            current: {
              text: 'app1',
              value: 'app1',
            },
            query: 'app1',
            allValue: '',
            includeAll: false,
          },
        },
      ],
      ...spec,
    },
    apiVersion: 'v1',
    metadata: {
      name: 'dashboard-test',
      resourceVersion: '1',
      creationTimestamp: '2023-01-01T00:00:00Z',
    },
    access: {
      canEdit: true,
      canSave: true,
      canStar: true,
      canShare: true,
    },
  });

  const initialSaveModel = transformSceneToSaveModelSchemaV2(dashboard);
  dashboard.setInitialSaveModel(initialSaveModel);

  return dashboard;
}
