/**
 * EventForm tests — Task 2 (TDD RED → GREEN)
 *
 * Covers:
 *   - All required fields render (title, all-day, start/end date/time, recurrence, location, description)
 *   - All-day toggle hides time inputs; un-toggling shows them
 *   - Calendar picker absent when writable-calendars returns 1 calendar (D-02)
 *   - Calendar picker present when writable-calendars returns 2 calendars (D-02)
 *   - Empty title shows "Title is required" error
 *   - End-before-start shows "End time must be after start" error
 *   - Submit in create mode calls createEvent mutation
 *   - Submit in edit mode calls updateEvent mutation
 *   - Escape key closes the form
 *   - Backdrop click closes the form
 *   - role="dialog" aria-modal="true"
 *   - No dangerouslySetInnerHTML usage (security)
 */

import 'temporal-polyfill/global'
import React from 'react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

// ── Module mocks ──────────────────────────────────────────────────────────────
// vi.hoisted() is required for variables used inside vi.mock() factory functions
// to avoid TDZ (temporal dead zone) issues — decision D-03-04-hoisting.

const {
  mockSetEventForm,
  mockSetLastSyncedUid,
  mockCreateEvent,
  mockUpdateEvent,
  mockFetchWritableCalendars,
} = vi.hoisted(() => ({
  mockSetEventForm: vi.fn(),
  mockSetLastSyncedUid: vi.fn(),
  mockCreateEvent: vi.fn().mockResolvedValue({ uid: 'new-uid-123' }),
  mockUpdateEvent: vi.fn().mockResolvedValue({ uid: 'edit-uid-456' }),
  mockFetchWritableCalendars: vi.fn(),
}))

let mockEventFormOpen = true
let mockEventFormMode: 'create' | 'edit' = 'create'
let mockEventFormUid: string | null = null

vi.mock('../store/calendarStore.js', () => ({
  useCalendarStore: vi.fn((selector?: (s: Record<string, unknown>) => unknown) => {
    const state = {
      eventFormOpen: mockEventFormOpen,
      eventFormMode: mockEventFormMode,
      eventFormUid: mockEventFormUid,
      setEventForm: mockSetEventForm,
      setLastSyncedUid: mockSetLastSyncedUid,
    }
    // Support both selector form and plain call form
    if (typeof selector === 'function') return selector(state)
    return state
  }),
}))

vi.mock('../api/client.js', () => ({
  createEvent: mockCreateEvent,
  updateEvent: mockUpdateEvent,
  fetchWritableCalendars: mockFetchWritableCalendars,
}))

// ── Fixtures ───────────────────────────────────────────────────────────────────

import type { CalendarOccurrence } from '../api/client.js'
import type { WritableCalendar } from '../api/client.js'

const ONE_CALENDAR: WritableCalendar[] = [
  { url: 'https://caldav.fastmail.com/cal1', displayName: 'My Calendar', color: '#4A90D9', isShared: false },
]

const TWO_CALENDARS: WritableCalendar[] = [
  { url: 'https://caldav.fastmail.com/cal1', displayName: 'My Calendar', color: '#4A90D9', isShared: false },
  { url: 'https://caldav.fastmail.com/cal2', displayName: 'Family', color: '#F25C7A', isShared: true },
]

const EDIT_OCCURRENCE: CalendarOccurrence = {
  id: 'edit-uid-456::2026-06-15T10:00:00',
  uid: 'edit-uid-456',
  calendarId: 1,
  calendarName: 'My Calendar',
  ownerUserId: 1,
  ownerName: 'Alice',
  color: '#4A90D9',
  isShared: false,
  title: 'Existing Meeting',
  start: '2026-06-15T10:00:00-04:00',
  end: '2026-06-15T11:00:00-04:00',
  allDay: false,
  location: 'Office',
  description: 'Weekly sync',
}

// ── Import component (after mocks) ────────────────────────────────────────────

import { EventForm } from './EventForm.js'
import { useCalendarStore } from '../store/calendarStore.js'

// ── Helpers ───────────────────────────────────────────────────────────────────

function renderForm(options: {
  mode?: 'create' | 'edit'
  uid?: string | null
  calendars?: WritableCalendar[]
  eventOccurrence?: CalendarOccurrence
} = {}) {
  const { mode = 'create', uid = null, calendars = ONE_CALENDAR, eventOccurrence } = options

  mockEventFormOpen = true
  mockEventFormMode = mode
  mockEventFormUid = uid
  ;(useCalendarStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(
    (selector?: (s: Record<string, unknown>) => unknown) => {
      const state = {
        eventFormOpen: true,
        eventFormMode: mode,
        eventFormUid: uid,
        setEventForm: mockSetEventForm,
        setLastSyncedUid: mockSetLastSyncedUid,
      }
      if (typeof selector === 'function') return selector(state)
      return state
    },
  )

  mockFetchWritableCalendars.mockResolvedValue(calendars)

  const client = new QueryClient({
    defaultOptions: { queries: { retry: false, staleTime: Infinity } },
  })

  // Pre-populate the events cache for edit mode pre-population
  if (eventOccurrence) {
    client.setQueryData(['events'], { occurrences: [eventOccurrence] })
  }
  // Pre-populate writable calendars cache
  client.setQueryData(['writableCalendars'], calendars)

  return render(
    <QueryClientProvider client={client}>
      <EventForm />
    </QueryClientProvider>,
  )
}

// ── Tests ─────────────────────────────────────────────────────────────────────

describe('EventForm', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockEventFormOpen = true
    mockEventFormMode = 'create'
    mockEventFormUid = null
  })

  // ── Role and accessibility ─────────────────────────────────────────────────

  it('has role="dialog" and aria-modal="true"', () => {
    renderForm()
    const dialog = screen.getByRole('dialog')
    expect(dialog).toBeDefined()
    expect(dialog.getAttribute('aria-modal')).toBe('true')
  })

  it('has aria-label "New Event" in create mode', () => {
    renderForm({ mode: 'create' })
    const dialog = screen.getByRole('dialog')
    expect(dialog.getAttribute('aria-label')).toBe('New Event')
  })

  it('has aria-label "Edit Event" in edit mode', () => {
    renderForm({ mode: 'edit', uid: 'edit-uid-456', eventOccurrence: EDIT_OCCURRENCE })
    const dialog = screen.getByRole('dialog')
    expect(dialog.getAttribute('aria-label')).toBe('Edit Event')
  })

  // ── Required fields ────────────────────────────────────────────────────────

  it('renders title input with placeholder "Event title"', () => {
    renderForm()
    const titleInput = screen.getByPlaceholderText('Event title')
    expect(titleInput).toBeDefined()
  })

  it('renders All-day toggle', () => {
    renderForm()
    expect(screen.getByText('All day')).toBeDefined()
  })

  it('renders start date input', () => {
    renderForm()
    // Start date input should exist
    const dateInputs = document.querySelectorAll('input[type="date"]')
    expect(dateInputs.length).toBeGreaterThanOrEqual(1)
  })

  it('renders recurrence picker labeled "Repeat"', () => {
    renderForm()
    expect(screen.getByText('Repeat')).toBeDefined()
  })

  it('renders recurrence options: None, Daily, Weekly, Monthly, Yearly', () => {
    renderForm()
    expect(screen.getByText('None')).toBeDefined()
    expect(screen.getByText('Daily')).toBeDefined()
    expect(screen.getByText('Weekly')).toBeDefined()
    expect(screen.getByText('Monthly')).toBeDefined()
    expect(screen.getByText('Yearly')).toBeDefined()
  })

  it('renders location input with placeholder "Add location"', () => {
    renderForm()
    expect(screen.getByPlaceholderText('Add location')).toBeDefined()
  })

  it('renders description textarea with placeholder "Add description"', () => {
    renderForm()
    expect(screen.getByPlaceholderText('Add description')).toBeDefined()
  })

  // ── All-day toggle behavior ────────────────────────────────────────────────

  it('toggling All-day ON hides time inputs', () => {
    renderForm()
    const timeInputsBefore = document.querySelectorAll('input[type="time"]')
    expect(timeInputsBefore.length).toBeGreaterThan(0)

    const allDaySwitch = screen.getByRole('switch')
    fireEvent.click(allDaySwitch)

    const timeInputsAfter = document.querySelectorAll('input[type="time"]')
    expect(timeInputsAfter.length).toBe(0)
  })

  it('toggling All-day OFF shows time inputs with default 09:00 / 10:00', () => {
    renderForm()
    // Toggle on then off
    const allDaySwitch = screen.getByRole('switch')
    fireEvent.click(allDaySwitch) // ON - hides time
    fireEvent.click(allDaySwitch) // OFF - shows time with defaults

    const timeInputs = document.querySelectorAll('input[type="time"]')
    expect(timeInputs.length).toBeGreaterThan(0)
  })

  // ── Calendar picker D-02 ───────────────────────────────────────────────────

  it('calendar picker is absent when fetchWritableCalendars returns 1 calendar (D-02)', () => {
    renderForm({ calendars: ONE_CALENDAR })
    // Should not show "Calendar" label when only 1 writable calendar
    const calendarLabel = screen.queryByText('Calendar')
    expect(calendarLabel).toBeNull()
  })

  it('calendar picker is present when fetchWritableCalendars returns 2 calendars (D-02)', () => {
    renderForm({ calendars: TWO_CALENDARS })
    // Should show "Calendar" label when >1 writable calendar
    expect(screen.getByText('Calendar')).toBeDefined()
  })

  // ── Validation ─────────────────────────────────────────────────────────────

  it('shows "Title is required" when submitting with empty title', async () => {
    renderForm()
    const saveButton = screen.getByText('Create Event')
    fireEvent.click(saveButton)
    await waitFor(() => {
      expect(screen.getByText('Title is required')).toBeDefined()
    })
  })

  it('shows "End time must be after start" when end is before start', async () => {
    renderForm()
    // Fill a title so title validation passes
    fireEvent.change(screen.getByPlaceholderText('Event title'), {
      target: { value: 'Valid Title' },
    })
    // Set start date to today
    const dateInputs = document.querySelectorAll('input[type="date"]')
    // Set end date to before start
    const today = new Date()
    const tomorrow = new Date(today)
    tomorrow.setDate(today.getDate() + 1)
    const todayStr = today.toISOString().slice(0, 10)
    const yesterdayStr = new Date(today.setDate(today.getDate() - 1)).toISOString().slice(0, 10)

    if (dateInputs.length >= 2) {
      fireEvent.change(dateInputs[0], { target: { value: todayStr } })
      // Set time inputs: start time later than end time
      const timeInputs = document.querySelectorAll('input[type="time"]')
      if (timeInputs.length >= 2) {
        fireEvent.change(timeInputs[0], { target: { value: '15:00' } })
        fireEvent.change(timeInputs[1], { target: { value: '10:00' } })
      }
      fireEvent.change(dateInputs[1], { target: { value: todayStr } })
    }

    const saveButton = screen.getByText('Create Event')
    fireEvent.click(saveButton)

    await waitFor(() => {
      expect(screen.getByText('End time must be after start')).toBeDefined()
    })
  })

  // ── Form submission ────────────────────────────────────────────────────────

  it('submitting in create mode calls createEvent mutation', async () => {
    renderForm({ mode: 'create' })

    fireEvent.change(screen.getByPlaceholderText('Event title'), {
      target: { value: 'New Meeting' },
    })

    const saveButton = screen.getByText('Create Event')
    fireEvent.click(saveButton)

    await waitFor(() => {
      expect(mockCreateEvent).toHaveBeenCalled()
    })
  })

  it('submitting in edit mode calls updateEvent mutation', async () => {
    renderForm({
      mode: 'edit',
      uid: 'edit-uid-456',
      eventOccurrence: EDIT_OCCURRENCE,
    })

    // Pre-populated form — just click save
    const saveButton = screen.getByText('Save Changes')
    fireEvent.click(saveButton)

    await waitFor(() => {
      expect(mockUpdateEvent).toHaveBeenCalled()
    })
  })

  it('successful create closes the form via setEventForm(false)', async () => {
    renderForm({ mode: 'create' })

    fireEvent.change(screen.getByPlaceholderText('Event title'), {
      target: { value: 'Test Event' },
    })
    fireEvent.click(screen.getByText('Create Event'))

    await waitFor(() => {
      expect(mockSetEventForm).toHaveBeenCalledWith(false)
    })
  })

  // ── Close behaviors ────────────────────────────────────────────────────────

  it('pressing Escape closes the form', () => {
    renderForm()
    fireEvent.keyDown(document, { key: 'Escape' })
    expect(mockSetEventForm).toHaveBeenCalledWith(false)
  })

  it('clicking the backdrop closes the form', () => {
    renderForm()
    const backdrop = screen.getByTestId('event-form-backdrop')
    fireEvent.click(backdrop)
    expect(mockSetEventForm).toHaveBeenCalledWith(false)
  })

  it('clicking Cancel closes the form', () => {
    renderForm()
    fireEvent.click(screen.getByText('Cancel'))
    expect(mockSetEventForm).toHaveBeenCalledWith(false)
  })

  // ── Edit mode pre-population ───────────────────────────────────────────────

  it('edit mode pre-populates title from TanStack Query cache', () => {
    renderForm({
      mode: 'edit',
      uid: 'edit-uid-456',
      eventOccurrence: EDIT_OCCURRENCE,
    })

    const titleInput = screen.getByPlaceholderText('Event title') as HTMLInputElement
    expect(titleInput.value).toBe('Existing Meeting')
  })
})
