import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import ManageImageSheet from '@/components/ManageImageSheet.vue'
import type { Device, Image } from '@/types'

function makeDevice(over: Partial<Device> = {}): Device {
  return {
    id: 1,
    mac: 'AA:BB:CC:DD:EE:01',
    name: 'Living Room',
    model: 'v1',
    orientation: 'landscape',
    rotationIntervalMinutes: 60,
    wakeTimes: [],
    timezone: 'UTC',
    uniquenessWindow: 0,
    rotationMode: 'random',
    prioritizeNeverShown: false,
    linkedAt: new Date().toISOString(),
    lastSeenAt: null,
    nextPollExpectedAt: null,
    lockedImageId: null,
    currentImageId: null,
    ...over,
  }
}

function makeImage(over: Partial<Image> = {}): Image {
  return {
    id: 1,
    originalFilename: 'a.jpg',
    thumbnailUrl: '',
    originalUrl: '',
    uploadedAt: new Date().toISOString(),
    approvedDeviceIds: [],
    cropParams: null,
    stickerState: null,
    cropOrientation: null,
    ...over,
  }
}

function mountSheet(opts: { image: Image | null; devices: Device[] }) {
  return mount(ManageImageSheet, {
    props: { modelValue: true, image: opts.image, devices: opts.devices },
    global: {
      stubs: {
        BaseBottomSheet: {
          name: 'BaseBottomSheet',
          template: '<div><slot/></div>',
        },
        BaseButton: { template: '<button><slot/></button>' },
      },
    },
  })
}

describe('ManageImageSheet', () => {
  beforeEach(() => { vi.clearAllMocks() })

  it('releases pendingApproval after the 200ms debounce so the toggle is tappable again', async () => {
    // The component sets a brief pending lock to avoid double-fire on a
    // fast double-tap; the timer that clears it is its own function and
    // we need to advance time to exercise it.
    vi.useFakeTimers()
    try {
      const w = mountSheet({
        image: makeImage({ id: 7, approvedDeviceIds: [] }),
        devices: [makeDevice({ id: 4 })],
      })
      const toggle = w.find('.manage__toggle')
      await toggle.trigger('click')
      // Immediately after the click the toggle is disabled (pending).
      expect((toggle.element as HTMLButtonElement).disabled).toBe(true)
      // Advance past the debounce and let the watcher flush.
      vi.advanceTimersByTime(250)
      await w.vm.$nextTick()
      expect((toggle.element as HTMLButtonElement).disabled).toBe(false)
    } finally {
      vi.useRealTimers()
    }
  })

  it('releases pendingLock after the 200ms debounce so the lock pill is tappable again', async () => {
    vi.useFakeTimers()
    try {
      const w = mountSheet({
        image: makeImage({ id: 7, approvedDeviceIds: [4] }),
        devices: [makeDevice({ id: 4 })],
      })
      const lock = w.find('.manage__lock')
      await lock.trigger('click')
      expect((lock.element as HTMLButtonElement).disabled).toBe(true)
      vi.advanceTimersByTime(250)
      await w.vm.$nextTick()
      expect((lock.element as HTMLButtonElement).disabled).toBe(false)
    } finally {
      vi.useRealTimers()
    }
  })

  it('renders a row per device', () => {
    const w = mountSheet({
      image: makeImage(),
      devices: [makeDevice({ id: 1 }), makeDevice({ id: 2, name: 'Kitchen' })],
    })
    expect(w.findAll('.manage__row').length).toBe(2)
  })

  it('shows the approval toggle ON for approved devices, OFF for others', () => {
    const w = mountSheet({
      image: makeImage({ approvedDeviceIds: [1] }),
      devices: [makeDevice({ id: 1 }), makeDevice({ id: 2 })],
    })
    const toggles = w.findAll('.manage__toggle')
    expect(toggles[0].classes()).toContain('manage__toggle--on')
    expect(toggles[1].classes()).not.toContain('manage__toggle--on')
  })

  it('emits approval=true when tapping an off toggle', async () => {
    const w = mountSheet({
      image: makeImage({ id: 7, approvedDeviceIds: [] }),
      devices: [makeDevice({ id: 4 })],
    })
    await w.find('.manage__toggle').trigger('click')
    expect(w.emitted('approval')![0][0]).toEqual({ imageId: 7, deviceId: 4, approved: true })
  })

  it('emits approval=false when tapping an on toggle', async () => {
    const w = mountSheet({
      image: makeImage({ id: 7, approvedDeviceIds: [4] }),
      devices: [makeDevice({ id: 4 })],
    })
    await w.find('.manage__toggle').trigger('click')
    expect(w.emitted('approval')![0][0]).toEqual({ imageId: 7, deviceId: 4, approved: false })
  })

  it('hides the lock pill when the image is not approved on the device', () => {
    // The pill can't apply when the photo isn't approved on the frame —
    // hiding (rather than disabling) keeps the row visually clean.
    const w = mountSheet({
      image: makeImage({ approvedDeviceIds: [] }),
      devices: [makeDevice({ id: 1 })],
    })
    expect(w.find('.manage__lock').exists()).toBe(false)
  })

  it('shows the lock pill in --on state when the device is locked to this image', () => {
    const w = mountSheet({
      image: makeImage({ id: 7, approvedDeviceIds: [1] }),
      devices: [makeDevice({ id: 1, lockedImageId: 7 })],
    })
    expect(w.find('.manage__lock').classes()).toContain('manage__lock--on')
  })

  it('emits lock=true when tapping an unlocked lock pill on an approved device', async () => {
    const w = mountSheet({
      image: makeImage({ id: 7, approvedDeviceIds: [4] }),
      devices: [makeDevice({ id: 4, lockedImageId: null })],
    })
    await w.find('.manage__lock').trigger('click')
    expect(w.emitted('lock')![0][0]).toEqual({ imageId: 7, deviceId: 4, locked: true })
  })

  it('emits lock=false when tapping an already-locked lock pill', async () => {
    const w = mountSheet({
      image: makeImage({ id: 7, approvedDeviceIds: [4] }),
      devices: [makeDevice({ id: 4, lockedImageId: 7 })],
    })
    await w.find('.manage__lock').trigger('click')
    expect(w.emitted('lock')![0][0]).toEqual({ imageId: 7, deviceId: 4, locked: false })
  })

  it('renders an empty state when the user has no devices', () => {
    const w = mountSheet({ image: makeImage(), devices: [] })
    expect(w.find('.manage__empty').exists()).toBe(true)
    expect(w.findAll('.manage__row').length).toBe(0)
  })

  it('does nothing if image is null (defensive)', async () => {
    const w = mountSheet({ image: null, devices: [makeDevice({ id: 4 })] })
    await w.find('.manage__toggle').trigger('click')
    expect(w.emitted('approval')).toBeUndefined()
  })

  it('emits update:modelValue=false when the Done button is clicked', async () => {
    // The Done button is the user's primary way to close the sheet without
    // toggling anything else — line 73 in the template.
    const w = mountSheet({
      image: makeImage(),
      devices: [makeDevice({ id: 1 })],
    })
    await w.find('.manage__done').trigger('click')
    expect(w.emitted('update:modelValue')).toBeTruthy()
    expect(w.emitted('update:modelValue')![0]).toEqual([false])
  })

  it('onLockClick early-returns on a stale-DOM click after image becomes null (defensive)', async () => {
    // The lock button is gated by v-if on isApproved(), which in turn reads
    // props.image. In normal use these early-return guards are unreachable.
    // We exercise them by capturing the live DOM node, asynchronously
    // nulling the image prop (Vue tears the node out on next tick), and
    // dispatching click on the detached node — its listener still runs and
    // sees the now-null props.image.
    const w = mountSheet({
      image: makeImage({ id: 7, approvedDeviceIds: [4] }),
      devices: [makeDevice({ id: 4 })],
    })
    const lockEl = w.find('.manage__lock').element as HTMLButtonElement
    expect(lockEl).toBeTruthy()
    await w.setProps({ image: null })
    // Node is now detached from the live tree; firing click on it still runs
    // the handler bound by Vue at mount time, which now reads props.image
    // as null and bails out at the first guard.
    lockEl.dispatchEvent(new Event('click'))
    expect(w.emitted('lock')).toBeUndefined()
  })

  it('onLockClick early-returns on a stale-DOM click after device loses approval (defensive)', async () => {
    // Same trick for the second guard. props.image stays, but the device
    // now has no approval — isApproved() returns false and we exit.
    const image = makeImage({ id: 7, approvedDeviceIds: [4] })
    const w = mountSheet({
      image,
      devices: [makeDevice({ id: 4 })],
    })
    const lockEl = w.find('.manage__lock').element as HTMLButtonElement
    expect(lockEl).toBeTruthy()
    await w.setProps({ image: { ...image, approvedDeviceIds: [] } })
    lockEl.dispatchEvent(new Event('click'))
    expect(w.emitted('lock')).toBeUndefined()
  })

  it('forwards update:modelValue from the underlying BaseBottomSheet', async () => {
    // BaseBottomSheet emits update:modelValue (e.g. backdrop tap, drag-down);
    // we need to re-emit so the parent's v-model stays in sync.
    const w = mount(ManageImageSheet, {
      props: { modelValue: true, image: makeImage(), devices: [makeDevice({ id: 1 })] },
      global: {
        stubs: {
          BaseBottomSheet: {
            name: 'BaseBottomSheet',
            template: '<div><slot/></div>',
            props: ['modelValue', 'label'],
            emits: ['update:modelValue'],
          },
          BaseButton: { template: '<button><slot/></button>' },
        },
      },
    })
    await w.findComponent({ name: 'BaseBottomSheet' }).vm.$emit('update:modelValue', false)
    expect(w.emitted('update:modelValue')).toEqual([[false]])
  })

})
