import { expect } from 'chai'
import sinon from 'sinon'
import { mount, flushPromises } from '@vue/test-utils'
import Graph from '@/components/Graph/index.vue'
import { nextTick } from 'vue'

function getPixels(canvas) {
  const context = canvas.getContext('webgl2')
  const width = context.canvas.width
  const height = context.canvas.height

  // Create arrays to hold the pixel data
  const pixels = new Uint8Array(width * height * 4)

  // Read pixels from canvas
  context.readPixels(
    0,
    0,
    width,
    height,
    context.RGBA,
    context.UNSIGNED_BYTE,
    pixels
  )
  return pixels.join(' ')
}

describe('Graph.vue', () => {
  const $store = { state: { isWorkspaceVisible: true } }

  afterEach(() => {
    sinon.restore()
  })

  it('shows message when no data', () => {
    const wrapper = mount(Graph, {
      global: {
        stubs: {
          GraphEditor: true
        }
      },
      attachTo: document.body
    })
    expect(wrapper.find('.no-data').isVisible()).to.equal(true)
    expect(wrapper.find('.invalid-data').isVisible()).to.equal(false)
    wrapper.unmount()
  })

  it('shows message when data is invalid', () => {
    const wrapper = mount(Graph, {
      props: {
        dataSources: {
          column1: ['value1', 'value2']
        }
      },
      global: {
        stubs: {
          GraphEditor: true
        }
      },
      attachTo: document.body
    })
    expect(wrapper.find('.no-data').isVisible()).to.equal(false)
    expect(wrapper.find('.invalid-data').isVisible()).to.equal(true)
    wrapper.unmount()
  })

  it('emits update when graph editor updates', async () => {
    const wrapper = mount(Graph, {
      global: {
        stubs: {
          GraphEditor: true
        }
      }
    })
    wrapper.findComponent({ ref: 'graphEditor' }).vm.$emit('update')
    expect(wrapper.emitted('update')).to.have.lengthOf(1)
  })

  it('the graph resizes when the container resizes', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        }
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    const container =
      wrapper.find('.graph-container').wrapperElement.parentElement
    const canvas = wrapper.find('canvas.sigma-nodes').wrapperElement

    const initialContainerWidth = container.scrollWidth
    const initialContainerHeight = container.scrollHeight

    const initialCanvasWidth = canvas.scrollWidth
    const initialCanvasHeight = canvas.scrollHeight

    const newContainerWidth = initialContainerWidth * 2 || 1000
    const newContainerHeight = initialContainerHeight * 2 || 2000

    container.style.width = `${newContainerWidth}px`
    container.style.height = `${newContainerHeight}px`

    await flushPromises()

    expect(canvas.scrollWidth).not.to.equal(initialCanvasWidth)
    expect(canvas.scrollHeight).not.to.equal(initialCanvasHeight)

    wrapper.unmount()
  })

  it('the graph resizes when node viewer visibillity togglles', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        },
        showValueViewer: false
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    const canvas = wrapper.find('canvas.sigma-nodes').wrapperElement
    const initialCanvasWidth = canvas.scrollWidth

    await wrapper.setProps({ showValueViewer: true })
    await flushPromises()

    expect(canvas.scrollWidth).not.to.equal(initialCanvasWidth)

    await wrapper.setProps({ showValueViewer: false })
    await flushPromises()

    expect(canvas.scrollWidth).to.equal(initialCanvasWidth)

    wrapper.unmount()
  })

  it('the graph resizes when the split pane resizes', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        },
        showValueViewer: true
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    const container =
      wrapper.find('.graph-container').wrapperElement.parentElement
    const canvas = wrapper.find('canvas.sigma-nodes').wrapperElement

    const initialContainerWidth = container.scrollWidth
    const initialCanvasWidth = canvas.scrollWidth
    await wrapper.find('.splitpanes-splitter').trigger('mousedown')
    document.dispatchEvent(
      new MouseEvent('mousemove', {
        clientX: initialContainerWidth / 2,
        clientY: 80
      })
    )
    document.dispatchEvent(new MouseEvent('mouseup'))
    await nextTick()
    await nextTick()

    await flushPromises()

    expect(canvas.scrollWidth).not.to.equal(initialCanvasWidth)

    wrapper.unmount()
  })

  it('opens and closes node viewer', async () => {
    const wrapper = mount(Graph, {
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        },
        showValueViewer: false
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    expect(wrapper.text()).not.contain('No node or edge selected to view')
    await wrapper.setProps({ showValueViewer: true })
    expect(wrapper.text()).contains('No node or edge selected to view')
  })

  it('passes selected item to node viewer', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        },
        showValueViewer: true
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    expect(wrapper.find('.value-viewer .value-body').exists()).to.equal(false)
    await wrapper
      .findComponent({ ref: 'graphEditor' })
      .vm.$emit('selectItem', { object_type: 0, node_id: 'Gryffindor' })

    expect(wrapper.find('.value-viewer .value-body').text()).contains(
      '"object_type": 0,'
    )
    expect(wrapper.find('.value-viewer .value-body').text()).contains(
      '"node_id": "Gryffindor"'
    )

    await wrapper
      .findComponent({ ref: 'graphEditor' })
      .vm.$emit('clearSelection')
    expect(wrapper.find('.value-viewer .value-body').exists()).to.equal(false)

    wrapper.unmount()
  })

  it('nodes and edges are rendered', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        showViewSettings: true,
        dataSources: {
          doc: [
            '{"object_type": 0, "node_id": 1}',
            '{"object_type": 0, "node_id": 2}',
            '{"object_type": 1, "source": 1, "target": 2}'
          ]
        }
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    const container =
      wrapper.find('.graph-container').wrapperElement.parentElement
    container.style.height = '400px'

    await wrapper
      .find('.test_object_type_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    let options = wrapper.findAll('.Select__menu .Select__option')

    await options[0].trigger('click')

    const nodeCanvasPixelsBefore = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
    )
    const edgeCanvasPixelsBefore = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
    )

    await wrapper
      .find('.test_node_id_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    options = wrapper.findAll('.Select__menu .Select__option')
    await options[1].trigger('click')

    await wrapper
      .find('.test_edge_source_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    options = wrapper.findAll('.Select__menu .Select__option')
    await options[2].trigger('click')

    await wrapper
      .find('.test_edge_target_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    options = wrapper.findAll('.Select__menu .Select__option')
    await options[3].trigger('click')

    const nodeCanvasPixelsAfter = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
    )
    const edgeCanvasPixelsAfter = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
    )

    expect(nodeCanvasPixelsBefore).not.equal(nodeCanvasPixelsAfter)
    expect(edgeCanvasPixelsBefore).not.equal(edgeCanvasPixelsAfter)

    wrapper.unmount()
  })

  it('rerenders when dataSource changes but does not rerender if dataSources is empty', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        showViewSettings: true,
        dataSources: {
          doc: [
            '{"object_type": 0, "node_id": 1}',
            '{"object_type": 0, "node_id": 2}',
            '{"object_type": 1, "source": 1, "target": 2}'
          ]
        }
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    const container =
      wrapper.find('.graph-container').wrapperElement.parentElement
    container.style.height = '400px'

    await wrapper
      .find('.test_object_type_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    let options = wrapper.findAll('.Select__menu .Select__option')

    await options[0].trigger('click')

    await wrapper
      .find('.test_node_id_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    options = wrapper.findAll('.Select__menu .Select__option')
    await options[1].trigger('click')

    await wrapper
      .find('.test_edge_source_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    options = wrapper.findAll('.Select__menu .Select__option')
    await options[2].trigger('click')

    await wrapper
      .find('.test_edge_target_select.dropdown-container .Select__indicator')
      .wrapperElement.dispatchEvent(
        new MouseEvent('mousedown', { bubbles: true })
      )

    options = wrapper.findAll('.Select__menu .Select__option')
    await options[3].trigger('click')

    const nodeCanvasPixelsBefore = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
    )
    const edgeCanvasPixelsBefore = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
    )
    await wrapper.setProps({
      dataSources: {
        doc: [
          '{"object_type": 0, "node_id": 1}',
          '{"object_type": 0, "node_id": 2}',
          '{"object_type": 0, "node_id": 3}',
          '{"object_type": 1, "source": 1, "target": 2}',
          '{"object_type": 1, "source": 1, "target": 3}'
        ]
      }
    })

    const nodeCanvasPixelsAfter = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
    )
    const edgeCanvasPixelsAfter = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
    )

    expect(nodeCanvasPixelsBefore).not.equal(nodeCanvasPixelsAfter)
    expect(edgeCanvasPixelsBefore).not.equal(edgeCanvasPixelsAfter)

    await wrapper.setProps({
      dataSources: null
    })

    const nodeCanvasPixelsAfterEmtyData = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
    )
    const edgeCanvasPixelsAfterEmtyData = getPixels(
      wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
    )

    expect(nodeCanvasPixelsAfterEmtyData).equal(nodeCanvasPixelsAfter)
    expect(edgeCanvasPixelsAfterEmtyData).equal(edgeCanvasPixelsAfter)

    wrapper.unmount()
  })

  it('saveAsPng', async () => {
    const wrapper = mount(Graph, {
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        }
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })
    sinon.stub(wrapper.vm.$refs.graphEditor, 'saveAsPng')
    await wrapper.vm.saveAsPng()
    expect(wrapper.emitted().loadingImageCompleted.length).to.equal(1)
  })

  it('hides and shows controls depending on showViewSettings and resizes the graph', async () => {
    const wrapper = mount(Graph, {
      attachTo: document.body,
      props: {
        dataSources: {
          doc: [
            '{"object_type":0,"node_id":"Gryffindor"}',
            '{"object_type":0,"node_id":"Hufflepuff"}'
          ]
        },
        initOptions: {
          structure: {
            nodeId: 'node_id',
            objectType: 'object_type',
            edgeSource: 'source',
            edgeTarget: 'target'
          },
          style: {
            backgroundColor: 'white',
            nodes: {
              size: {
                type: 'constant',
                value: 10
              },
              color: {
                type: 'constant',
                value: '#1F77B4'
              },
              label: {
                source: null,
                color: '#444444'
              }
            },
            edges: {
              showDirection: true,
              size: {
                type: 'constant',
                value: 2
              },
              color: {
                type: 'constant',
                value: '#a2b1c6'
              },
              label: {
                source: null,
                color: '#a2b1c6'
              }
            }
          },
          layout: {
            type: 'circular',
            options: null
          }
        }
      },
      global: {
        mocks: { $store },
        provide: {
          tabLayout: { dataView: 'above' }
        }
      }
    })

    const canvas = wrapper.find('canvas.sigma-nodes').wrapperElement

    const initialPlotWidth = canvas.scrollWidth
    const initialPlotHeight = canvas.scrollHeight

    expect(
      wrapper.find('.plotly_editor .editor_controls').isVisible()
    ).to.equal(false)

    await wrapper.setProps({ showViewSettings: true })

    await flushPromises()

    expect(canvas.scrollWidth).not.to.equal(initialPlotWidth)
    expect(canvas.scrollHeight).to.equal(initialPlotHeight)
    expect(
      wrapper.find('.plotly_editor .editor_controls').isVisible()
    ).to.equal(true)
    wrapper.unmount()
  })
})
