import { describe, expect, it } from 'vitest'

import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration'
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'

describe('NodeDef Migration', () => {
  it('should transform a plain object to V2 format', () => {
    const plainObject = {
      required: {
        intInput: ['INT', { min: 0, max: 100, default: 50 }],
        stringInput: ['STRING', { default: 'Hello', multiline: true }]
      },
      optional: {
        booleanInput: [
          'BOOLEAN',
          { default: true, labelOn: 'Yes', labelOff: 'No' }
        ],
        floatInput: ['FLOAT', { min: 0, max: 1, step: 0.1 }]
      },
      hidden: {
        someHiddenValue: 42
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: ['INT'],
      output_is_list: [false],
      output_name: ['intOutput'],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)

    expect(result).toBeDefined()
    expect(result.inputs).toBeDefined()
    expect(result.outputs).toBeDefined()
    expect(result.hidden).toBeDefined()
  })

  it('should correctly transform required input specs', () => {
    const plainObject = {
      required: {
        intInput: ['INT', { min: 0, max: 100, default: 50 }],
        stringInput: ['STRING', { default: 'Hello', multiline: true }]
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)

    const intInput = result.inputs['intInput']
    const stringInput = result.inputs['stringInput']

    expect(intInput.min).toBe(0)
    expect(intInput.max).toBe(100)
    expect(intInput.default).toBe(50)
    expect(intInput.name).toBe('intInput')
    expect(intInput.type).toBe('INT')
    expect(stringInput.default).toBe('Hello')
    expect(stringInput.multiline).toBe(true)
    expect(stringInput.name).toBe('stringInput')
    expect(stringInput.type).toBe('STRING')
  })

  it('should correctly transform optional input specs', () => {
    const plainObject = {
      optional: {
        booleanInput: [
          'BOOLEAN',
          { default: true, labelOn: 'Yes', labelOff: 'No' }
        ],
        floatInput: ['FLOAT', { min: 0, max: 1, step: 0.1 }]
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)

    const booleanInput = result.inputs['booleanInput']
    const floatInput = result.inputs['floatInput']

    expect(booleanInput.default).toBe(true)
    expect(booleanInput.labelOn).toBe('Yes')
    expect(booleanInput.labelOff).toBe('No')
    expect(booleanInput.type).toBe('BOOLEAN')
    expect(booleanInput.isOptional).toBe(true)
    expect(floatInput.min).toBe(0)
    expect(floatInput.max).toBe(1)
    expect(floatInput.step).toBe(0.1)
    expect(floatInput.type).toBe('FLOAT')
    expect(floatInput.isOptional).toBe(true)
  })

  it('should handle combo input specs', () => {
    const plainObject = {
      optional: {
        comboInput: [[1, 2, 3], { default: 2 }]
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)
    expect(result.inputs['comboInput'].type).toBe('COMBO')
    expect(result.inputs['comboInput'].default).toBe(2)
    expect(result.inputs['comboInput'].options).toEqual([1, 2, 3])
  })

  it('should handle combo input specs (auto-default)', () => {
    const plainObject = {
      optional: {
        comboInput: [[1, 2, 3], {}]
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)
    expect(result.inputs['comboInput'].type).toBe('COMBO')
    // Should pick the first choice as default
    expect(result.inputs['comboInput'].options).toEqual([1, 2, 3])
  })

  it('should handle custom input specs', () => {
    const plainObject = {
      optional: {
        customInput: ['CUSTOM_TYPE', { default: 'custom value' }]
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)
    expect(result.inputs['customInput'].type).toBe('CUSTOM_TYPE')
    expect(result.inputs['customInput'].default).toBe('custom value')
  })

  it('should not transform hidden fields', () => {
    const plainObject = {
      hidden: {
        someHiddenValue: 42,
        anotherHiddenValue: { nested: 'object' }
      }
    } as ComfyNodeDefV1['input']

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)

    // @ts-expect-error fixme ts strict error
    expect(result.hidden).toEqual(plainObject.hidden)
    expect(result.hidden?.someHiddenValue).toBe(42)
    expect(result.hidden?.anotherHiddenValue).toEqual({ nested: 'object' })
  })

  it('should handle empty or undefined fields', () => {
    const plainObject = {}

    const nodeDef: ComfyNodeDefV1 = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: plainObject,
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    }

    const result = transformNodeDefV1ToV2(nodeDef)

    expect(result).toBeDefined()
    expect(result.inputs).toEqual({})
    expect(result.hidden).toBeUndefined()
  })
})

describe('ComfyNodeDefImpl', () => {
  it('should transform a basic node definition correctly', () => {
    const plainObject = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: {
        required: {
          intInput: ['INT', { min: 0, max: 100, default: 50 }]
        }
      },
      output: ['INT'],
      output_is_list: [false],
      output_name: ['intOutput'],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)

    expect(result).toBeInstanceOf(ComfyNodeDefImpl)
    expect(result.name).toBe('TestNode')
    expect(result.display_name).toBe('Test Node')
    expect(result.category).toBe('Testing')
    expect(result.python_module).toBe('test_module')
    expect(result.description).toBe('A test node')
    expect(result.inputs).toBeDefined()
    expect(result.outputs).toEqual([
      {
        index: 0,
        name: 'intOutput',
        type: 'INT',
        is_list: false
      }
    ])
    expect(result.deprecated).toBe(false)
  })

  it('should transform a deprecated basic node definition', () => {
    const plainObject = {
      name: 'TestNode',
      display_name: 'Test Node',
      category: 'Testing',
      python_module: 'test_module',
      description: 'A test node',
      input: {
        required: {
          intInput: ['INT', { min: 0, max: 100, default: 50 }]
        }
      },
      output: ['INT'],
      output_is_list: [false],
      output_name: ['intOutput'],
      deprecated: true,
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)
    expect(result.deprecated).toBe(true)
  })

  // Legacy way of marking a node as deprecated
  it('should mark deprecated with empty category', () => {
    const plainObject = {
      name: 'TestNode',
      display_name: 'Test Node',
      // Empty category should be treated as deprecated
      category: '',
      python_module: 'test_module',
      description: 'A test node',
      input: {
        required: {
          intInput: ['INT', { min: 0, max: 100, default: 50 }]
        }
      },
      output: ['INT'],
      output_is_list: [false],
      output_name: ['intOutput'],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)
    expect(result.deprecated).toBe(true)
  })

  it('should handle multiple outputs including COMBO type', () => {
    const plainObject = {
      name: 'MultiOutputNode',
      display_name: 'Multi Output Node',
      category: 'Advanced',
      python_module: 'advanced_module',
      description: 'A node with multiple outputs',
      input: {},
      output: ['STRING', ['COMBO', 'option1', 'option2'], 'FLOAT'],
      output_is_list: [true, false, false],
      output_name: ['stringOutput', 'comboOutput', 'floatOutput'],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)

    expect(result.outputs).toEqual([
      {
        index: 0,
        name: 'stringOutput',
        type: 'STRING',
        is_list: true
      },
      {
        index: 1,
        name: 'comboOutput',
        type: 'COMBO',
        is_list: false,
        options: ['COMBO', 'option1', 'option2']
      },
      {
        index: 2,
        name: 'floatOutput',
        type: 'FLOAT',
        is_list: false
      }
    ])
  })

  it('should use index for output names if matches type', () => {
    const plainObject = {
      name: 'MissingNamesNode',
      display_name: 'Missing Names Node',
      category: 'Test',
      python_module: 'test_module',
      description: 'A node with missing output names',
      input: {},
      output: ['INT', 'FLOAT', 'FLOAT'],
      output_is_list: [false, true, true],
      output_name: ['INT', 'FLOAT', 'FLOAT'],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)

    expect(result.outputs).toEqual([
      {
        index: 0,
        name: 'INT',
        type: 'INT',
        is_list: false
      },
      {
        index: 1,
        name: 'FLOAT',
        type: 'FLOAT',
        is_list: true
      },
      {
        index: 2,
        name: 'FLOAT',
        type: 'FLOAT',
        is_list: true
      }
    ])
  })

  it('should handle duplicate output names', () => {
    const plainObject = {
      name: 'DuplicateOutputNode',
      display_name: 'Duplicate Output Node',
      category: 'Test',
      python_module: 'test_module',
      description: 'A node with duplicate output names',
      input: {},
      output: ['INT', 'FLOAT', 'STRING'],
      output_is_list: [false, false, false],
      output_name: ['output', 'output', 'uniqueOutput'],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)
    expect(result.outputs).toEqual([
      {
        index: 0,
        name: 'output',
        type: 'INT',
        is_list: false
      },
      {
        index: 1,
        name: 'output',
        type: 'FLOAT',
        is_list: false
      },
      {
        index: 2,
        name: 'uniqueOutput',
        type: 'STRING',
        is_list: false
      }
    ])
  })

  it('should handle empty output', () => {
    const plainObject = {
      name: 'EmptyOutputNode',
      display_name: 'Empty Output Node',
      category: 'Test',
      python_module: 'test_module',
      description: 'A node with no outputs',
      input: {},
      output: [],
      output_is_list: [],
      output_name: [],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)

    expect(result.outputs).toEqual([])
  })

  it('should handle undefined fields', () => {
    const plainObject = {
      name: 'EmptyOutputNode',
      display_name: 'Empty Output Node',
      category: 'Test',
      python_module: 'test_module',
      description: 'A node with no outputs',
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)
    expect(result.outputs).toEqual([])
    expect(Object.keys(result.inputs)).toHaveLength(0)
  })

  it('should handle complex input specifications', () => {
    const plainObject = {
      name: 'ComplexInputNode',
      display_name: 'Complex Input Node',
      category: 'Advanced',
      python_module: 'advanced_module',
      description: 'A node with complex input',
      input: {
        required: {
          intInput: ['INT', { min: 0, max: 100, default: 50 }],
          stringInput: ['STRING', { multiline: true }]
        },
        optional: {
          booleanInput: ['BOOLEAN', { default: true }],
          floatInput: ['FLOAT', { step: 0.1 }]
        }
      },
      output: ['INT'],
      output_is_list: [false],
      output_name: ['result'],
      output_node: false
    } as ComfyNodeDefV1

    const result = new ComfyNodeDefImpl(plainObject)

    expect(result.inputs).toBeDefined()
    expect(Object.keys(result.inputs)).toHaveLength(4)
    expect(result.inputs['intInput']).toBeDefined()
    expect(result.inputs['stringInput']).toBeDefined()
    expect(result.inputs['booleanInput']).toBeDefined()
    expect(result.inputs['floatInput']).toBeDefined()
  })

  it.for([
    { api_node: true, expected: true },
    { api_node: false, expected: false },
    { api_node: undefined, expected: false }
  ] as { api_node: boolean | undefined; expected: boolean }[])(
    'should handle api_node field: $api_node',
    ({ api_node, expected }) => {
      const result = new ComfyNodeDefImpl({
        name: 'ApiNode',
        display_name: 'API Node',
        category: 'Test',
        python_module: 'test_module',
        description: 'A node with API',
        api_node
      } as ComfyNodeDefV1)
      expect(result.api_node).toBe(expected)
    }
  )
})
