import {type Connection, type Node} from '@xyflow/react'
import {uuid} from 'licia'
import {expect, test} from 'vitest'
import {
  atLeastOneEndNodeError,
  atLeastOneStartNodeError,
  checkAddConnection,
  checkAddNode,
  checkSave,
  hasCycleError,
  hasRedundantEdgeError,
  multiEndNodeError,
  multiStartNodeError,
  nodeToSelfError,
  sourceNodeNotFoundError,
  startNodeToEndNodeError,
  targetNodeNotFoundError,
} from './FlowChecker.tsx'

const createNode = (id: string, type: string): Node => {
  return {
    data: {},
    position: {
      x: 0,
      y: 0
    },
    id,
    type,
  }
}

const createStartNode = (id: string): Node => createNode(id, 'start-node')
const createEndNode = (id: string): Node => createNode(id, 'end-node')

const createConnection = function (source: string, target: string, sourceHandle: string | null = null, targetHandle: string | null = null): Connection {
  return {
    source,
    target,
    sourceHandle,
    targetHandle,
  }
}

/* check add node */

test(multiStartNodeError().message, () => {
  expect(() => checkAddNode('start-node', [createStartNode(uuid())], [])).toThrowError(multiStartNodeError())
})

test(multiEndNodeError().message, () => {
  expect(() => checkAddNode('end-node', [createEndNode(uuid())], [])).toThrowError(multiEndNodeError())
})

/* check add connection */
test(sourceNodeNotFoundError().message, () => {
  expect(() => checkAddConnection(createConnection('a', 'b'), [], []))
})

test(targetNodeNotFoundError().message, () => {
  expect(() => checkAddConnection(createConnection('a', 'b'), [createStartNode('a')], []))
})

test(startNodeToEndNodeError().message, () => {
  expect(() => checkAddConnection(
    createConnection('a', 'b'),
    [createStartNode('a'), createEndNode('b')],
    []
  ))
})

test(nodeToSelfError().message, () => {
  expect(() => {
    // language=JSON
    const {
      nodes,
      edges
    } = JSON.parse('{\n  "nodes": [\n    {\n      "id": "P14abHl4uY",\n      "type": "start-node",\n      "position": {\n        "x": 100,\n        "y": 100\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 82\n      }\n    },\n    {\n      "id": "3YDRebKqCX",\n      "type": "end-node",\n      "position": {\n        "x": 773.3027344262372,\n        "y": 101.42648884412338\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 74\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "YXJ91nHVaz",\n      "type": "llm-node",\n      "position": {\n        "x": 430.94541183662506,\n        "y": 101.42648884412338\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 74\n      },\n      "selected": true,\n      "dragging": false\n    }\n  ],\n  "edges": [\n    {\n      "source": "P14abHl4uY",\n      "target": "YXJ91nHVaz",\n      "id": "xy-edge__P14abHl4uY-YXJ91nHVaz"\n    },\n    {\n      "source": "YXJ91nHVaz",\n      "target": "3YDRebKqCX",\n      "id": "xy-edge__YXJ91nHVaz-3YDRebKqCX"\n    }\n  ],\n  "data": {}\n}')
    checkAddConnection(createConnection('YXJ91nHVaz', 'YXJ91nHVaz'), nodes, edges)
  }).toThrowError(nodeToSelfError())
})

test(hasCycleError().message, () => {
  expect(() => {
    // language=JSON
    const {
      nodes,
      edges,
    } = JSON.parse('{\n  "nodes": [\n    {\n      "id": "-DKfXm7r3f",\n      "type": "start-node",\n      "position": {\n        "x": -75.45812782717618,\n        "y": 14.410669352596976\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 82\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "2uL3Hw2CAW",\n      "type": "end-node",\n      "position": {\n        "x": 734.7875356349059,\n        "y": -1.2807079327602473\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 74\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "yp-yYfKUzC",\n      "type": "llm-node",\n      "position": {\n        "x": 338.2236369686051,\n        "y": -92.5759939566568\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 74\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "N4HQPN-NYZ",\n      "type": "llm-node",\n      "position": {\n        "x": 332.51768159211156,\n        "y": 114.26488844123382\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 74\n      },\n      "selected": true,\n      "dragging": false\n    }\n  ],\n  "edges": [\n    {\n      "source": "-DKfXm7r3f",\n      "target": "yp-yYfKUzC",\n      "id": "xy-edge__-DKfXm7r3f-yp-yYfKUzC"\n    },\n    {\n      "source": "yp-yYfKUzC",\n      "target": "2uL3Hw2CAW",\n      "id": "xy-edge__yp-yYfKUzC-2uL3Hw2CAW"\n    },\n    {\n      "source": "-DKfXm7r3f",\n      "target": "N4HQPN-NYZ",\n      "id": "xy-edge__-DKfXm7r3f-N4HQPN-NYZ"\n    },\n    {\n      "source": "N4HQPN-NYZ",\n      "target": "yp-yYfKUzC",\n      "id": "xy-edge__N4HQPN-NYZ-yp-yYfKUzC"\n    }\n  ],\n  "data": {}\n}')
    // language=JSON
    checkAddConnection(JSON.parse('{\n  "source": "yp-yYfKUzC",\n  "sourceHandle": null,\n  "target": "N4HQPN-NYZ",\n  "targetHandle": null\n}'), nodes, edges)
  }).toThrowError(hasCycleError())
})

test(hasRedundantEdgeError().message, () => {
  expect(() => {
    // language=JSON
    const {
      nodes,
      edges,
    } = JSON.parse('{\n  "nodes": [\n    {\n      "id": "TCxPixrdkI",\n      "type": "start-node",\n      "position": {\n        "x": -256,\n        "y": 109.5\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 83\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "tGs78_ietp",\n      "type": "llm-node",\n      "position": {\n        "x": 108,\n        "y": -2.5\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 105\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "OeZdaU7LpY",\n      "type": "llm-node",\n      "position": {\n        "x": 111,\n        "y": 196\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 105\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "LjfoCYZo-E",\n      "type": "knowledge-node",\n      "position": {\n        "x": 497.62196259607214,\n        "y": -10.792497317791003\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 75\n      },\n      "selected": true,\n      "dragging": false\n    },\n    {\n      "id": "sQM_22GYB5",\n      "type": "end-node",\n      "position": {\n        "x": 874.3164534765615,\n        "y": 151.70316541496913\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 75\n      },\n      "selected": false,\n      "dragging": false\n    },\n    {\n      "id": "KpMH_xc3ZZ",\n      "type": "llm-node",\n      "position": {\n        "x": 529.6286840434341,\n        "y": 150.4721376669937\n      },\n      "data": {},\n      "measured": {\n        "width": 256,\n        "height": 75\n      },\n      "selected": false,\n      "dragging": false\n    }\n  ],\n  "edges": [\n    {\n      "source": "TCxPixrdkI",\n      "sourceHandle": "source",\n      "target": "tGs78_ietp",\n      "targetHandle": "target",\n      "id": "xy-edge__TCxPixrdkIsource-tGs78_ietptarget"\n    },\n    {\n      "source": "TCxPixrdkI",\n      "sourceHandle": "source",\n      "target": "OeZdaU7LpY",\n      "targetHandle": "target",\n      "id": "xy-edge__TCxPixrdkIsource-OeZdaU7LpYtarget"\n    },\n    {\n      "source": "tGs78_ietp",\n      "sourceHandle": "source",\n      "target": "LjfoCYZo-E",\n      "targetHandle": "target",\n      "id": "xy-edge__tGs78_ietpsource-LjfoCYZo-Etarget"\n    },\n    {\n      "source": "LjfoCYZo-E",\n      "sourceHandle": "source",\n      "target": "KpMH_xc3ZZ",\n      "targetHandle": "target",\n      "id": "xy-edge__LjfoCYZo-Esource-KpMH_xc3ZZtarget"\n    },\n    {\n      "source": "OeZdaU7LpY",\n      "sourceHandle": "source",\n      "target": "KpMH_xc3ZZ",\n      "targetHandle": "target",\n      "id": "xy-edge__OeZdaU7LpYsource-KpMH_xc3ZZtarget"\n    },\n    {\n      "source": "KpMH_xc3ZZ",\n      "sourceHandle": "source",\n      "target": "sQM_22GYB5",\n      "targetHandle": "target",\n      "id": "xy-edge__KpMH_xc3ZZsource-sQM_22GYB5target"\n    }\n  ],\n  "data": {\n    "tGs78_ietp": {\n      "model": "qwen3",\n      "outputs": {\n        "text": {\n          "type": "string"\n        }\n      },\n      "systemPrompt": "你是个聪明人"\n    },\n    "OeZdaU7LpY": {\n      "model": "qwen3",\n      "outputs": {\n        "text": {\n          "type": "string"\n        }\n      },\n      "systemPrompt": "你也是个聪明人"\n    }\n  }\n}')
    // language=JSON
    checkAddConnection(JSON.parse('{\n  "source": "OeZdaU7LpY",\n  "sourceHandle": "source",\n  "target": "LjfoCYZo-E",\n  "targetHandle": "target"\n}'), nodes, edges)
  }).toThrowError(hasRedundantEdgeError())
})

/* check save */
test(atLeastOneStartNodeError().message, () => {
  expect(() => checkSave([], [], {})).toThrowError(atLeastOneStartNodeError())
})

test(atLeastOneEndNodeError().message, () => {
  expect(() => checkSave([createStartNode(uuid())], [], {})).toThrowError(atLeastOneEndNodeError())
})
