import AnnotationCanvas from '../src/AnnotationCanvas'
import { describe, expect, test, it, jest, beforeEach, afterEach, } from '@jest/globals'


describe('AnnotationCanvas', () => {
  let annotationCanvas
  let documentBody
  let canvasMock
  let contextMock

  beforeEach(() => {
    canvasMock = {
      width: 0,
      height: 0,
      style: {},
      getContext: jest.fn(() => contextMock as unknown as HTMLCanvasElement),
      parentNode: document,
      setAttribute: jest.fn()
    }

    contextMock = {
      globalAlpha: 1.0,
      beginPath: jest.fn(),
      moveTo: jest.fn(),
      lineTo: jest.fn(),
      lineWidth: 8,
      lineCap: 'round',
      lineJoin: 'round',
      strokeStyle: 'red',
      stroke: jest.fn(),
      globalCompositeOperation: '',
      fillStyle: '',
      fillRect: jest.fn(),
      clearRect: jest.fn(),
    }

    documentBody = document.body
    // @ts-ignore
    document['removeChild'] = (el) => jest.fn(el)
    // @ts-ignore
    document['createElement'] = () => canvasMock

    jest.spyOn(documentBody, 'appendChild').mockImplementation(jest.fn())
    jest.spyOn(documentBody, 'removeChild').mockImplementation(jest.fn())
    jest.spyOn(window, 'addEventListener').mockImplementation(jest.fn())
    jest.spyOn(window, 'removeEventListener').mockImplementation(jest.fn())
    annotationCanvas = new AnnotationCanvas()
  })

  afterEach(() => {
    jest.restoreAllMocks()
  })

  it('should create a canvas element with correct styles when initialized', () => {
    const createElSpy = jest.spyOn(document, 'createElement')
    annotationCanvas = new AnnotationCanvas()
    expect(createElSpy).toHaveBeenCalledWith('canvas')
    expect(canvasMock.style.position).toBe('fixed')
    expect(canvasMock.style.left).toBe(0)
    expect(canvasMock.style.top).toBe(0)
    expect(canvasMock.style.pointerEvents).toBe('none')
    expect(canvasMock.style.zIndex).toBe(2147483647 - 2)
  })

  it('should resize the canvas when calling resizeCanvas method', () => {
    annotationCanvas.resizeCanvas()

    expect(canvasMock.width).toBe(window.innerWidth)
    expect(canvasMock.height).toBe(window.innerHeight)
  })

  it('should start painting and set the last position when calling start method', () => {
    const position = [10, 20,]

    annotationCanvas.start(position)

    expect(annotationCanvas.painting).toBe(true)
    expect(annotationCanvas.clrTmID).toBeNull()
    expect(annotationCanvas.lastPosition).toEqual(position)
  })

  it('should stop painting and call fadeOut method when calling stop method', () => {
    annotationCanvas.painting = true
    const fadeOutSpy = jest.spyOn(annotationCanvas, 'fadeOut')

    annotationCanvas.stop()

    expect(annotationCanvas.painting).toBe(false)
    expect(fadeOutSpy).toHaveBeenCalled()
  })

  it('should not stop painting or call fadeOut method when calling stop method while not painting', () => {
    annotationCanvas.painting = false
    const fadeOutSpy = jest.spyOn(annotationCanvas, 'fadeOut')
    annotationCanvas.stop()

    expect(fadeOutSpy).not.toHaveBeenCalled()
  })

  it('should draw a line on the canvas when calling move method', () => {
    annotationCanvas.painting = true
    annotationCanvas.ctx = contextMock
    const initialLastPosition = [0, 0,]
    const position = [10, 20,]

    annotationCanvas.move(position)

    expect(contextMock.globalAlpha).toBe(1.0)
    expect(contextMock.beginPath).toHaveBeenCalled()
    expect(contextMock.moveTo).toHaveBeenCalledWith(initialLastPosition[0], initialLastPosition[1])
    expect(contextMock.lineTo).toHaveBeenCalledWith(position[0], position[1])
    expect(contextMock.stroke).toHaveBeenCalled()
    expect(annotationCanvas.lastPosition).toEqual(position)
  })

  it('should not draw a line on the canvas when calling move method while not painting', () => {
    annotationCanvas.painting = false
    annotationCanvas.ctx = contextMock
    const position = [10, 20,]

    annotationCanvas.move(position)

    expect(contextMock.beginPath).not.toHaveBeenCalled()
    expect(contextMock.stroke).not.toHaveBeenCalled()
    expect(annotationCanvas.lastPosition).toEqual([0, 0,])
  })

  it('should fade out the canvas when calling fadeOut method', () => {
    annotationCanvas.ctx = contextMock
    jest.useFakeTimers()
    const timerSpy = jest.spyOn(window, 'setTimeout')
    annotationCanvas.fadeOut()

    expect(timerSpy).toHaveBeenCalledTimes(2)
    expect(contextMock.globalCompositeOperation).toBe('source-over')
    expect(contextMock.fillStyle).toBe('rgba(255, 255, 255, 0.1)')
    expect(contextMock.fillRect).toHaveBeenCalledWith(0, 0, canvasMock.width, canvasMock.height)
    jest.runOnlyPendingTimers()
    expect(contextMock.clearRect).toHaveBeenCalledWith(0, 0, canvasMock.width, canvasMock.height)
  })

  it('should remove the canvas element when calling remove method', () => {
    const spyOnRemove = jest.spyOn(document, 'removeChild')
    annotationCanvas.remove()

    expect(spyOnRemove).toHaveBeenCalledWith(canvasMock)
    expect(window.removeEventListener).toHaveBeenCalledWith('resize', annotationCanvas.resizeCanvas)
  })
})