import { randomLcg } from 'd3-random';

import {
  FieldColorModeId,
  type FieldDTO,
  FieldType,
  MutableDataFrame,
  NodeGraphDataFrameFieldNames,
  type DataFrame,
  addRow,
} from '@grafana/data';

import * as serviceMapResponseSmall from './testData/serviceMapResponse';
import * as serviceMapResponsMedium from './testData/serviceMapResponseMedium';

export function generateRandomNodes(count = 10, seed?: number) {
  const nodes = [];
  const edges: string[] = [];
  const rand = randomLcg(seed);

  const root = {
    id: 'root',
    title: 'root',
    subTitle: 'client',
    success: 1,
    error: 0,
    stat1: Math.random(),
    stat2: Math.random(),
    edges,
  };
  nodes.push(root);
  const nodesWithoutMaxEdges = [root];

  const maxEdges = 3;

  for (let i = 1; i < count; i++) {
    const node = makeRandomNode(i);
    nodes.push(node);
    const sourceIndex = Math.floor(rand() * Math.floor(nodesWithoutMaxEdges.length - 1));
    const source = nodesWithoutMaxEdges[sourceIndex];
    source.edges.push(node.id);
    if (source.edges.length >= maxEdges) {
      nodesWithoutMaxEdges.splice(sourceIndex, 1);
    }
    nodesWithoutMaxEdges.push(node);
  }

  // Add some random edges to create possible cycle
  const additionalEdges = Math.floor(count / 2);
  for (let i = 0; i <= additionalEdges; i++) {
    const sourceIndex = Math.floor(rand() * Math.floor(nodes.length - 1));
    const targetIndex = Math.floor(rand() * Math.floor(nodes.length - 1));
    if (sourceIndex === targetIndex || nodes[sourceIndex].id === '0' || nodes[targetIndex].id === '0') {
      continue;
    }

    nodes[sourceIndex].edges.push(nodes[targetIndex].id);
  }

  const { nodesFields, nodesFrame, edgesFrame } = makeDataFrames();

  const edgesSet = new Set();
  for (const node of nodes) {
    nodesFields.id.values.push(node.id);
    nodesFields.title.values.push(node.title);
    nodesFields[NodeGraphDataFrameFieldNames.subTitle].values.push(node.subTitle);
    nodesFields[NodeGraphDataFrameFieldNames.mainStat].values.push(node.stat1);
    nodesFields[NodeGraphDataFrameFieldNames.secondaryStat].values.push(node.stat2);
    nodesFields.arc__success.values.push(node.success);
    nodesFields.arc__errors.values.push(node.error);
    const rnd = Math.random();
    nodesFields[NodeGraphDataFrameFieldNames.icon].values.push(rnd > 0.9 ? 'database' : rnd < 0.1 ? 'cloud' : '');
    nodesFields[NodeGraphDataFrameFieldNames.nodeRadius].values.push(Math.max(rnd * 100, 30)); // ensure a minimum radius of 30 or icons will not fit well in the node
    nodesFields[NodeGraphDataFrameFieldNames.highlighted].values.push(Math.random() > 0.5);

    for (const edge of node.edges) {
      const id = `${node.id}--${edge}`;
      // We can have duplicate edges when we added some more by random
      if (edgesSet.has(id)) {
        continue;
      }
      edgesSet.add(id);
      edgesFrame.fields[0].values.push(`${node.id}--${edge}`);
      edgesFrame.fields[1].values.push(node.id);
      edgesFrame.fields[2].values.push(edge);
      edgesFrame.fields[3].values.push(Math.random() * 100);
      edgesFrame.fields[4].values.push(Math.random() > 0.5);
      edgesFrame.fields[5].values.push(Math.ceil(Math.random() * 15));
    }
  }
  edgesFrame.length = edgesFrame.fields[0].values.length;

  return [nodesFrame, edgesFrame];
}

function makeRandomNode(index: number) {
  const success = Math.random();
  const error = 1 - success;
  return {
    id: `service:${index}`,
    title: `service:${index}`,
    subTitle: 'service',
    success,
    error,
    stat1: Math.random(),
    stat2: Math.random(),
    edges: [],
    highlighted: Math.random() > 0.5,
  };
}

export function savedNodesResponse(size: 'small' | 'medium'): [DataFrame, DataFrame] {
  const response = size === 'small' ? serviceMapResponseSmall : serviceMapResponsMedium;
  return [new MutableDataFrame(response.nodes), new MutableDataFrame(response.edges)];
}

// Generates node graph data but only returns the edges
export function generateRandomEdges(count = 10, seed = 1) {
  return generateRandomNodes(count, seed)[1];
}

function makeDataFrames(): {
  nodesFrame: DataFrame;
  edgesFrame: DataFrame;
  nodesFields: Record<string, Omit<FieldDTO, 'name'> & { values: unknown[] }>;
} {
  const nodesFields: Record<string, Omit<FieldDTO, 'name'> & { values: unknown[] }> = {
    [NodeGraphDataFrameFieldNames.id]: {
      values: [],
      type: FieldType.string,
      config: {
        links: [
          {
            title: 'test data link',
            url: '',
            internal: {
              query: { scenarioId: 'logs', alias: 'from service graph', stringInput: 'tes' },
              datasourceUid: 'gdev-testdata',
              datasourceName: 'gdev-testdata',
            },
          },
        ],
      },
    },
    [NodeGraphDataFrameFieldNames.title]: {
      values: [],
      type: FieldType.string,
    },
    [NodeGraphDataFrameFieldNames.subTitle]: {
      values: [],
      type: FieldType.string,
    },
    [NodeGraphDataFrameFieldNames.mainStat]: {
      values: [],
      type: FieldType.number,
      config: { displayName: 'Transactions per second' },
    },
    [NodeGraphDataFrameFieldNames.secondaryStat]: {
      values: [],
      type: FieldType.number,
      config: { displayName: 'Average duration' },
    },
    [NodeGraphDataFrameFieldNames.arc + 'success']: {
      values: [],
      type: FieldType.number,
      config: { color: { fixedColor: 'green', mode: FieldColorModeId.Fixed }, displayName: 'Success' },
    },
    [NodeGraphDataFrameFieldNames.arc + 'errors']: {
      values: [],
      type: FieldType.number,
      config: { color: { fixedColor: 'red', mode: FieldColorModeId.Fixed }, displayName: 'Errors' },
    },
    [NodeGraphDataFrameFieldNames.icon]: {
      values: [],
      type: FieldType.string,
    },
    [NodeGraphDataFrameFieldNames.nodeRadius]: {
      values: [],
      type: FieldType.number,
    },
    [NodeGraphDataFrameFieldNames.highlighted]: {
      values: [],
      type: FieldType.boolean,
    },

    [NodeGraphDataFrameFieldNames.detail + 'test_value']: {
      values: [],
      config: {
        displayName: 'Test value',
      },
      type: FieldType.number,
    },
  };

  const nodesFrame = new MutableDataFrame({
    name: 'nodes',
    fields: Object.keys(nodesFields).map((key) => ({
      ...nodesFields[key],
      name: key,
    })),
    meta: { preferredVisualisationType: 'nodeGraph' },
  });

  const edgesFrame: DataFrame = {
    name: 'edges',
    fields: [
      { name: NodeGraphDataFrameFieldNames.id, values: [], type: FieldType.string, config: {} },
      { name: NodeGraphDataFrameFieldNames.source, values: [], type: FieldType.string, config: {} },
      { name: NodeGraphDataFrameFieldNames.target, values: [], type: FieldType.string, config: {} },
      { name: NodeGraphDataFrameFieldNames.mainStat, values: [], type: FieldType.number, config: {} },
      { name: NodeGraphDataFrameFieldNames.highlighted, values: [], type: FieldType.boolean, config: {} },
      { name: NodeGraphDataFrameFieldNames.thickness, values: [], type: FieldType.number, config: {} },
      { name: NodeGraphDataFrameFieldNames.color, values: [], type: FieldType.string, config: {} },
      { name: NodeGraphDataFrameFieldNames.strokeDasharray, values: [], type: FieldType.string, config: {} },
    ],
    meta: { preferredVisualisationType: 'nodeGraph' },
    length: 0,
  };

  return { nodesFrame, edgesFrame, nodesFields };
}

export function generateShowcaseData() {
  const { nodesFrame, edgesFrame } = makeDataFrames();

  addRow(nodesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'root',
    [NodeGraphDataFrameFieldNames.title]: 'root',
    [NodeGraphDataFrameFieldNames.subTitle]: 'client',
    [NodeGraphDataFrameFieldNames.mainStat]: 1234,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 5678,
    arc__success: 0.5,
    arc__errors: 0.5,
    [NodeGraphDataFrameFieldNames.icon]: '',
    [NodeGraphDataFrameFieldNames.nodeRadius]: 40,
    [NodeGraphDataFrameFieldNames.highlighted]: false,
    [NodeGraphDataFrameFieldNames.detail + 'test_value']: 1,
  });

  addRow(nodesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'app_service',
    [NodeGraphDataFrameFieldNames.title]: 'app service',
    [NodeGraphDataFrameFieldNames.subTitle]: 'with icon',
    [NodeGraphDataFrameFieldNames.mainStat]: 1.2,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 2.3,
    arc__success: 1,
    arc__errors: 0,
    [NodeGraphDataFrameFieldNames.icon]: 'apps',
    [NodeGraphDataFrameFieldNames.nodeRadius]: 40,
    [NodeGraphDataFrameFieldNames.highlighted]: false,
    [NodeGraphDataFrameFieldNames.detail + 'test_value']: 42,
  });

  addRow(edgesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'root-app_service',
    [NodeGraphDataFrameFieldNames.source]: 'root',
    [NodeGraphDataFrameFieldNames.target]: 'app_service',
    [NodeGraphDataFrameFieldNames.mainStat]: 3.4,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 4.5,
    [NodeGraphDataFrameFieldNames.thickness]: 4,
    [NodeGraphDataFrameFieldNames.color]: '',
    [NodeGraphDataFrameFieldNames.strokeDasharray]: '',
  });

  addRow(nodesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'auth_service',
    [NodeGraphDataFrameFieldNames.title]: 'auth service',
    [NodeGraphDataFrameFieldNames.subTitle]: 'highlighted',
    [NodeGraphDataFrameFieldNames.mainStat]: 3.4,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 4.5,
    arc__success: 0,
    arc__errors: 1,
    [NodeGraphDataFrameFieldNames.icon]: '',
    [NodeGraphDataFrameFieldNames.nodeRadius]: 40,
    [NodeGraphDataFrameFieldNames.highlighted]: true,
  });

  addRow(edgesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'root-auth_service',
    [NodeGraphDataFrameFieldNames.source]: 'root',
    [NodeGraphDataFrameFieldNames.target]: 'auth_service',
    [NodeGraphDataFrameFieldNames.mainStat]: 113.4,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 4.511,
    [NodeGraphDataFrameFieldNames.thickness]: 8,
    [NodeGraphDataFrameFieldNames.color]: 'red',
    [NodeGraphDataFrameFieldNames.strokeDasharray]: '',
  });

  addRow(nodesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'db',
    [NodeGraphDataFrameFieldNames.title]: 'db',
    [NodeGraphDataFrameFieldNames.subTitle]: 'bigger size',
    [NodeGraphDataFrameFieldNames.mainStat]: 9876.123,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 123.9876,
    arc__success: 0.9,
    arc__errors: 0.1,
    [NodeGraphDataFrameFieldNames.icon]: 'database',
    [NodeGraphDataFrameFieldNames.nodeRadius]: 60,
    [NodeGraphDataFrameFieldNames.highlighted]: false,
    [NodeGraphDataFrameFieldNames.detail + 'test_value']: 1357,
  });

  addRow(edgesFrame, {
    [NodeGraphDataFrameFieldNames.id]: 'auth_service-db',
    [NodeGraphDataFrameFieldNames.source]: 'auth_service',
    [NodeGraphDataFrameFieldNames.target]: 'db',
    [NodeGraphDataFrameFieldNames.mainStat]: 1139.4,
    [NodeGraphDataFrameFieldNames.secondaryStat]: 477.511,
    [NodeGraphDataFrameFieldNames.thickness]: 2,
    [NodeGraphDataFrameFieldNames.color]: 'blue',
    [NodeGraphDataFrameFieldNames.strokeDasharray]: '2 2',
  });

  return [nodesFrame, edgesFrame];
}
