import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import ScheduleModal from '../components/shared/ScheduleModal.vue'
import type { ChildTask, ChoreSchedule, RoutineSchedule } from '../common/models'

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------
const mockSetChoreSchedule = vi.fn()
const mockDeleteChoreSchedule = vi.fn()
const mockSetRoutineSchedule = vi.fn()
const mockDeleteRoutineSchedule = vi.fn()

vi.mock('@/common/api', () => ({
  setChoreSchedule: (...args: unknown[]) => mockSetChoreSchedule(...args),
  deleteChoreSchedule: (...args: unknown[]) => mockDeleteChoreSchedule(...args),
  setRoutineSchedule: (...args: unknown[]) => mockSetRoutineSchedule(...args),
  deleteRoutineSchedule: (...args: unknown[]) => mockDeleteRoutineSchedule(...args),
  parseErrorResponse: vi.fn().mockResolvedValue({ msg: 'error', code: 'ERR' }),
}))

// Stubs that render their default slot so form content is accessible
const ModalDialogStub = {
  template: '<div><slot /></div>',
  props: ['imageUrl', 'title', 'subtitle'],
}
const TimePickerPopoverStub = {
  template: '<div class="time-picker-popover-stub" />',
  props: ['modelValue'],
  emits: ['update:modelValue'],
}
const DateInputFieldStub = {
  template:
    '<input class="date-input-field-stub" type="date" :value="modelValue" :min="min" @change="$emit(\'update:modelValue\', $event.target.value)" />',
  props: ['modelValue', 'min'],
  emits: ['update:modelValue'],
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const TASK: ChildTask = { id: 'task-1', name: 'Clean Room', type: 'chore', points: 5, image_id: '' }
const CHILD_ID = 'child-1'

function mountModal(schedule: ChoreSchedule | RoutineSchedule | null = null) {
  return mount(ScheduleModal, {
    props: { entity: TASK, entityType: 'task', childId: CHILD_ID, schedule },
    global: {
      stubs: {
        ModalDialog: ModalDialogStub,
        TimePickerPopover: TimePickerPopoverStub,
        DateInputField: DateInputFieldStub,
      },
    },
  })
}

beforeEach(() => {
  mockSetChoreSchedule.mockReset()
  mockDeleteChoreSchedule.mockReset()
  mockSetRoutineSchedule.mockReset()
  mockDeleteRoutineSchedule.mockReset()
  mockSetChoreSchedule.mockResolvedValue({ ok: true })
  mockDeleteChoreSchedule.mockResolvedValue({ ok: true })
  mockSetRoutineSchedule.mockResolvedValue({ ok: true })
  mockDeleteRoutineSchedule.mockResolvedValue({ ok: true })
})
// ---------------------------------------------------------------------------
// Mode toggle
// ---------------------------------------------------------------------------
describe('ScheduleModal mode toggle', () => {
  it('defaults to Specific Days mode', () => {
    const w = mountModal()
    expect(w.find('.days-form').exists()).toBe(true)
    expect(w.find('.interval-form').exists()).toBe(false)
  })

  it('switches to Every X Days on button click', async () => {
    const w = mountModal()
    const modeBtns = w.findAll('.mode-btn')
    await modeBtns[1].trigger('click') // "Every X Days"
    expect(w.find('.interval-form').exists()).toBe(true)
    expect(w.find('.days-form').exists()).toBe(false)
  })

  it('switches back to Specific Days', async () => {
    const w = mountModal()
    const modeBtns = w.findAll('.mode-btn')
    await modeBtns[1].trigger('click') // switch to interval
    await modeBtns[0].trigger('click') // switch back
    expect(w.find('.days-form').exists()).toBe(true)
    expect(w.find('.interval-form').exists()).toBe(false)
  })

  it('marks "Specific Days" button active by default', () => {
    const w = mountModal()
    const modeBtns = w.findAll('.mode-btn')
    expect(modeBtns[0].classes()).toContain('active')
    expect(modeBtns[1].classes()).not.toContain('active')
  })

  it('marks "Every X Days" button active after switching', async () => {
    const w = mountModal()
    const modeBtns = w.findAll('.mode-btn')
    await modeBtns[1].trigger('click')
    expect(modeBtns[1].classes()).toContain('active')
    expect(modeBtns[0].classes()).not.toContain('active')
  })
})

// ---------------------------------------------------------------------------
// Specific Days form — chip toggles
// ---------------------------------------------------------------------------
describe('ScheduleModal Specific Days form', () => {
  it('renders 7 day chips', () => {
    const w = mountModal()
    expect(w.findAll('.chip').length).toBe(7)
  })

  it('no chips are active by default (no existing schedule)', () => {
    const w = mountModal()
    const chips = w.findAll('.chip')
    expect(chips.every((c) => !c.classes().includes('active'))).toBe(true)
  })

  it('clicking a chip makes it active', async () => {
    const w = mountModal()
    const chips = w.findAll('.chip')
    await chips[1].trigger('click') // Monday
    expect(chips[1].classes()).toContain('active')
  })

  it('clicking an active chip deactivates it', async () => {
    const w = mountModal()
    const chips = w.findAll('.chip')
    await chips[1].trigger('click') // activate
    await chips[1].trigger('click') // deactivate
    expect(chips[1].classes()).not.toContain('active')
  })

  it('selecting a chip shows the default-deadline-row', async () => {
    const w = mountModal()
    expect(w.find('.default-deadline-row').exists()).toBe(false)
    await w.findAll('.chip')[0].trigger('click')
    expect(w.find('.default-deadline-row').exists()).toBe(true)
  })

  it('exposes the enable-toggle row for the tutorial anchor', () => {
    const w = mountModal()
    const toggleRow = w.find('.schedule-toggle-row')
    expect(toggleRow.exists()).toBe(true)
    expect(toggleRow.attributes('data-tutorial')).toBe('schedule-enable-toggle')
  })

  it('Save is disabled when no days selected (isDirty is false)', () => {
    const w = mountModal()
    const saveBtn = w.find('.btn-primary')
    expect((saveBtn.element as HTMLButtonElement).disabled).toBe(true)
  })

  it('Save is enabled after selecting at least one day', async () => {
    const w = mountModal()
    await w.findAll('.chip')[2].trigger('click') // Tuesday
    await nextTick()
    const saveBtn = w.find('.btn-primary')
    expect((saveBtn.element as HTMLButtonElement).disabled).toBe(false)
  })

  it('pre-populates active chips from an existing schedule', () => {
    const existing: ChoreSchedule = {
      id: 's1',
      child_id: CHILD_ID,
      task_id: TASK.id,
      mode: 'days',
      day_configs: [
        { day: 1, hour: 8, minute: 0 },
        { day: 4, hour: 9, minute: 30 },
      ],
      default_hour: 8,
      default_minute: 0,
      interval_days: 1,
      anchor_date: '',
      interval_has_deadline: true,
      interval_hour: 0,
      interval_minute: 0,
      created_at: 0,
      updated_at: 0,
    }
    const w = mountModal(existing)
    const chips = w.findAll('.chip')
    expect(chips[1].classes()).toContain('active') // Monday idx 1
    expect(chips[4].classes()).toContain('active') // Thursday idx 4
  })
})

// ---------------------------------------------------------------------------
// Save emits correct ChoreSchedule shape — days mode
// ---------------------------------------------------------------------------
describe('ScheduleModal save — days mode', () => {
  it('calls setChoreSchedule with correct days payload', async () => {
    const w = mountModal()
    // Select Monday (chip idx 1)
    await w.findAll('.chip')[1].trigger('click')
    await nextTick()

    await w.find('.btn-primary').trigger('click')
    await nextTick()

    expect(mockSetChoreSchedule).toHaveBeenCalledWith(
      CHILD_ID,
      TASK.id,
      expect.objectContaining({
        mode: 'days',
        day_configs: [{ day: 1, hour: expect.any(Number), minute: expect.any(Number) }],
      }),
    )
  })

  it('calls deleteChoreSchedule when 0 days selected on an existing schedule', async () => {
    const existing: ChoreSchedule = {
      id: 's1',
      child_id: CHILD_ID,
      task_id: TASK.id,
      mode: 'days',
      day_configs: [{ day: 2, hour: 8, minute: 0 }],
      default_hour: 8,
      default_minute: 0,
      interval_days: 1,
      anchor_date: '',
      interval_has_deadline: true,
      interval_hour: 0,
      interval_minute: 0,
      created_at: 0,
      updated_at: 0,
    }
    const w = mountModal(existing)
    await w.findAll('.chip')[2].trigger('click') // deselect Wednesday
    await nextTick()
    await w.find('.btn-primary').trigger('click')
    await nextTick()
    await nextTick()
    expect(mockDeleteChoreSchedule).toHaveBeenCalledWith(CHILD_ID, TASK.id)
  })

  it('emits "saved" after successful save', async () => {
    const w = mountModal()
    await w.findAll('.chip')[0].trigger('click') // Sunday
    await nextTick()

    await w.find('.btn-primary').trigger('click')
    await nextTick()
    await nextTick()

    expect(w.emitted('saved')).toBeTruthy()
  })

  it('does not emit "saved" on API error', async () => {
    mockSetChoreSchedule.mockResolvedValue({ ok: false })
    const w = mountModal()
    await w.findAll('.chip')[0].trigger('click')
    await nextTick()

    await w.find('.btn-primary').trigger('click')
    await nextTick()
    await nextTick()

    expect(w.emitted('saved')).toBeFalsy()
  })
})

// ---------------------------------------------------------------------------
// Interval form — stepper
// ---------------------------------------------------------------------------
describe('ScheduleModal interval form — stepper', () => {
  async function openInterval(schedule: ChoreSchedule | null = null) {
    const w = mountModal(schedule)
    await w.findAll('.mode-btn')[1].trigger('click')
    return w
  }

  it('default stepper value is 1', async () => {
    const w = await openInterval()
    expect(w.find('.stepper-value').text()).toBe('1')
  })

  it('increment button increases the value', async () => {
    const w = await openInterval()
    await w.findAll('.stepper-btn')[1].trigger('click')
    expect(w.find('.stepper-value').text()).toBe('2')
  })

  it('decrement button is disabled when value is 1', async () => {
    const w = await openInterval()
    const decrementBtn = w.findAll('.stepper-btn')[0]
    expect((decrementBtn.element as HTMLButtonElement).disabled).toBe(true)
  })

  it('increment button is disabled when value is 7', async () => {
    const w = await openInterval()
    const plusBtn = w.findAll('.stepper-btn')[1]
    for (let i = 0; i < 6; i++) await plusBtn.trigger('click')
    expect((plusBtn.element as HTMLButtonElement).disabled).toBe(true)
  })

  it('value does not go above 7', async () => {
    const w = await openInterval()
    const plusBtn = w.findAll('.stepper-btn')[1]
    for (let i = 0; i < 10; i++) await plusBtn.trigger('click')
    expect(w.find('.stepper-value').text()).toBe('7')
  })

  it('pre-populates interval_days from existing schedule', async () => {
    const existing: ChoreSchedule = {
      id: 's2',
      child_id: CHILD_ID,
      task_id: TASK.id,
      mode: 'interval',
      day_configs: [],
      interval_days: 4,
      anchor_date: '2026-03-10',
      interval_has_deadline: true,
      interval_hour: 0,
      interval_minute: 0,
      created_at: 0,
      updated_at: 0,
    }
    const w = await openInterval(existing)
    expect(w.find('.stepper-value').text()).toBe('4')
  })
})

// ---------------------------------------------------------------------------
// Interval form — Anytime toggle
// ---------------------------------------------------------------------------
describe('ScheduleModal interval form — Anytime toggle', () => {
  async function openInterval() {
    const w = mountModal()
    await w.findAll('.mode-btn')[1].trigger('click')
    return w
  }

  it('shows TimePickerPopover by default (hasDeadline=true)', async () => {
    const w = await openInterval()
    expect(w.find('.time-picker-popover-stub').exists()).toBe(true)
    expect(w.find('.anytime-label').exists()).toBe(false)
  })

  it('shows "Clear (Anytime)" link by default', async () => {
    const w = await openInterval()
    const link = w.find('.interval-time-row .link-btn')
    expect(link.text()).toContain('Clear')
  })

  it('clicking "Clear (Anytime)" hides TimePickerPopover and shows anytime label', async () => {
    const w = await openInterval()
    await w.find('.interval-time-row .link-btn').trigger('click')
    await nextTick()
    expect(w.find('.time-picker-popover-stub').exists()).toBe(false)
    expect(w.find('.anytime-label').exists()).toBe(true)
  })

  it('clicking "Set deadline" after clearing restores TimePickerPopover', async () => {
    const w = await openInterval()
    await w.find('.interval-time-row .link-btn').trigger('click')
    await nextTick()
    await w.find('.interval-time-row .link-btn').trigger('click')
    await nextTick()
    expect(w.find('.time-picker-popover-stub').exists()).toBe(true)
    expect(w.find('.anytime-label').exists()).toBe(false)
  })

  it('pre-populates interval_has_deadline=false from existing schedule', () => {
    const existing: ChoreSchedule = {
      id: 's3',
      child_id: CHILD_ID,
      task_id: TASK.id,
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '',
      interval_has_deadline: false,
      interval_hour: 0,
      interval_minute: 0,
      created_at: 0,
      updated_at: 0,
    }
    const w = mountModal(existing)
    expect(w.find('.anytime-label').exists()).toBe(true)
    expect(w.find('.time-picker-popover-stub').exists()).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// Save emits correct ChoreSchedule shape — interval mode
// ---------------------------------------------------------------------------
describe('ScheduleModal save — interval mode', () => {
  it('calls setChoreSchedule with anchor_date and interval_has_deadline in payload', async () => {
    const w = mountModal()
    await w.findAll('.mode-btn')[1].trigger('click')
    const plusBtn = w.findAll('.stepper-btn')[1]
    await plusBtn.trigger('click') // interval_days = 2
    await plusBtn.trigger('click') // interval_days = 3

    await w.find('.btn-primary').trigger('click')
    await nextTick()

    expect(mockSetChoreSchedule).toHaveBeenCalledWith(
      CHILD_ID,
      TASK.id,
      expect.objectContaining({
        mode: 'interval',
        interval_days: 3,
        anchor_date: expect.any(String),
        interval_has_deadline: true,
        interval_hour: expect.any(Number),
        interval_minute: expect.any(Number),
      }),
    )
  })

  it('payload has interval_has_deadline=false when Anytime toggle is active', async () => {
    const w = mountModal()
    await w.findAll('.mode-btn')[1].trigger('click')
    await w.find('.interval-time-row .link-btn').trigger('click') // Clear
    await nextTick()
    await w.find('.btn-primary').trigger('click')
    await nextTick()
    expect(mockSetChoreSchedule).toHaveBeenCalledWith(
      CHILD_ID,
      TASK.id,
      expect.objectContaining({ interval_has_deadline: false }),
    )
  })

  it('Save enabled by default in interval mode', async () => {
    const w = mountModal()
    await w.findAll('.mode-btn')[1].trigger('click')
    const saveBtn = w.find('.btn-primary')
    expect((saveBtn.element as HTMLButtonElement).disabled).toBe(false)
  })

  it('pre-populates anchor_date in the DateInputField stub', () => {
    const existing: ChoreSchedule = {
      id: 's4',
      child_id: CHILD_ID,
      task_id: TASK.id,
      mode: 'interval',
      day_configs: [],
      interval_days: 2,
      anchor_date: '2026-04-01',
      interval_has_deadline: true,
      interval_hour: 8,
      interval_minute: 0,
      created_at: 0,
      updated_at: 0,
    }
    const w = mountModal(existing)
    const dateInput = w.find('.date-input-field-stub')
    expect((dateInput.element as HTMLInputElement).value).toBe('2026-04-01')
  })
})

// ---------------------------------------------------------------------------
// Interval form — next occurrence label
// ---------------------------------------------------------------------------
describe('ScheduleModal interval form — next occurrence', () => {
  it('shows next occurrence label in interval mode', async () => {
    const w = mountModal()
    await w.findAll('.mode-btn')[1].trigger('click')
    await nextTick()
    expect(w.find('.next-occurrence-label').exists()).toBe(true)
  })

  it('next occurrence label contains "Next occurrence:"', async () => {
    const w = mountModal()
    await w.findAll('.mode-btn')[1].trigger('click')
    await nextTick()
    expect(w.find('.next-occurrence-label').text()).toContain('Next occurrence:')
  })
})

// ---------------------------------------------------------------------------
// Cancel
// ---------------------------------------------------------------------------
describe('ScheduleModal cancel', () => {
  it('emits "cancelled" when Cancel is clicked', async () => {
    const w = mountModal()
    await w.find('.btn-secondary').trigger('click')
    expect(w.emitted('cancelled')).toBeTruthy()
  })
})

// ---------------------------------------------------------------------------
// Backdrop click
// ---------------------------------------------------------------------------
describe('ScheduleModal backdrop', () => {
  it('does not emit cancelled when ModalDialog fires backdrop-click', async () => {
    const w = mountModal()
    const dialog = w.findComponent(ModalDialogStub)
    dialog.vm.$emit('backdrop-click')
    await nextTick()
    expect(w.emitted('cancelled')).toBeFalsy()
  })
})

// ---------------------------------------------------------------------------
// Entity type routing
// ---------------------------------------------------------------------------
describe('ScheduleModal entityType routing', () => {
  it('uses routine schedule API when entityType is routine', async () => {
    const routineEntity = { ...TASK, id: 'routine-1', items: [] }
    const w = mount(ScheduleModal, {
      props: {
        entity: routineEntity,
        entityType: 'routine',
        childId: CHILD_ID,
        schedule: null,
      },
      global: {
        stubs: {
          ModalDialog: ModalDialogStub,
          TimePickerPopover: TimePickerPopoverStub,
          DateInputField: DateInputFieldStub,
        },
      },
    })

    await w.findAll('.chip')[1].trigger('click')
    await nextTick()
    await w.find('.btn-primary').trigger('click')
    await nextTick()

    expect(mockSetRoutineSchedule).toHaveBeenCalledWith(
      CHILD_ID,
      'routine-1',
      expect.objectContaining({ mode: 'days' }),
    )
    expect(mockSetChoreSchedule).not.toHaveBeenCalled()
  })
})
