import { createInjector } from '@furystack/inject'
import { usingAsync } from '@furystack/utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ScreenService, ScreenSizes } from './screen-service.js'

describe('ScreenService', () => {
  beforeEach(() => {
    document.body.innerHTML = '<div id="root"></div>'
  })
  afterEach(() => {
    document.body.innerHTML = ''
    vi.restoreAllMocks()
  })

  it('Should be constructed', async () => {
    await usingAsync(createInjector(), async (i) => {
      const s = i.get(ScreenService)
      expect(s).toBeDefined()
      expect(s.breakpoints).toBeDefined()
    })
  })

  describe('breakpoints', () => {
    it('Should have correct breakpoint definitions', async () => {
      await usingAsync(createInjector(), async (i) => {
        const s = i.get(ScreenService)

        expect(s.breakpoints.xs.minSize).toBe(0)
        expect(s.breakpoints.sm.minSize).toBe(600)
        expect(s.breakpoints.md.minSize).toBe(960)
        expect(s.breakpoints.lg.minSize).toBe(1280)
        expect(s.breakpoints.xl.minSize).toBe(1920)
      })
    })
  })

  describe('screenSize.atLeast', () => {
    it('Should have observable for each screen size', async () => {
      await usingAsync(createInjector(), async (i) => {
        const s = i.get(ScreenService)

        for (const size of ScreenSizes) {
          expect(s.screenSize.atLeast[size]).toBeDefined()
          expect(typeof s.screenSize.atLeast[size].getValue()).toBe('boolean')
        }
      })
    })

    it('Should return true for xs on any screen size', async () => {
      await usingAsync(createInjector(), async (i) => {
        const s = i.get(ScreenService)
        // xs has minSize 0, so it should always be true
        expect(s.screenSize.atLeast.xs.getValue()).toBe(true)
      })
    })

    it('Should update screenSize observables on window resize', async () => {
      await usingAsync(createInjector(), async (i) => {
        const s = i.get(ScreenService)

        // Mock window.innerWidth to simulate a large screen
        vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1920)

        // Trigger resize event
        window.dispatchEvent(new Event('resize'))

        // All breakpoints should be true for 1920px width
        expect(s.screenSize.atLeast.xs.getValue()).toBe(true)
        expect(s.screenSize.atLeast.sm.getValue()).toBe(true)
        expect(s.screenSize.atLeast.md.getValue()).toBe(true)
        expect(s.screenSize.atLeast.lg.getValue()).toBe(true)
        expect(s.screenSize.atLeast.xl.getValue()).toBe(true)

        // Mock a small screen
        vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(500)
        window.dispatchEvent(new Event('resize'))

        // Only xs should be true for 500px width
        expect(s.screenSize.atLeast.xs.getValue()).toBe(true)
        expect(s.screenSize.atLeast.sm.getValue()).toBe(false)
        expect(s.screenSize.atLeast.md.getValue()).toBe(false)
        expect(s.screenSize.atLeast.lg.getValue()).toBe(false)
        expect(s.screenSize.atLeast.xl.getValue()).toBe(false)
      })
    })
  })

  describe('orientation', () => {
    it('Should have an orientation observable', async () => {
      await usingAsync(createInjector(), async (i) => {
        const s = i.get(ScreenService)
        const orientation = s.orientation.getValue()
        expect(['landscape', 'portrait']).toContain(orientation)
      })
    })

    it('Should update orientation on resize', async () => {
      // Mock matchMedia before creating the service
      const matchMediaMock = vi.fn()
      window.matchMedia = matchMediaMock

      await usingAsync(createInjector(), async (i) => {
        // Set initial orientation to landscape
        matchMediaMock.mockReturnValue({ matches: true })
        const s = i.get(ScreenService)

        // Verify initial landscape
        window.dispatchEvent(new Event('resize'))
        expect(s.orientation.getValue()).toBe('landscape')

        // Change to portrait
        matchMediaMock.mockReturnValue({ matches: false })
        window.dispatchEvent(new Event('resize'))
        expect(s.orientation.getValue()).toBe('portrait')
      })
    })
  })

  describe('disposal', () => {
    it('Should remove resize event listener on dispose', async () => {
      const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener')

      await usingAsync(createInjector(), async (i) => {
        i.get(ScreenService)
      })

      expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function))
    })
  })
})
