/**
 * EventDetailPopover tests — Task 1 (TDD RED → GREEN)
 *
 * Guards:
 *   - T-02e-01: Title containing HTML-looking strings renders as escaped text (XSS guard)
 *   - Escape key closes the popover and clears openEventId
 *   - All event fields (title, location, description, calendar name) render as text
 *   - Close button has aria-label="Close"
 *   - Backdrop click closes the popover
 */

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

// window.matchMedia polyfilled in test-setup.ts

// ── Module mocks ──────────────────────────────────────────────────────────────

const {
  mockSetOpenEventId,
  mockSetEventForm,
  mockSetDeleteDialog,
} = vi.hoisted(() => ({
  mockSetOpenEventId: vi.fn(),
  mockSetEventForm: vi.fn(),
  mockSetDeleteDialog: vi.fn(),
}))
let mockOpenEventId: string | null = null

vi.mock('../store/calendarStore.js', () => ({
  useCalendarStore: vi.fn((selector?: (s: Record<string, unknown>) => unknown) => {
    const state = {
      openEventId: mockOpenEventId,
      setOpenEventId: mockSetOpenEventId,
      setEventForm: mockSetEventForm,
      setDeleteDialog: mockSetDeleteDialog,
    }
    if (typeof selector === 'function') return selector(state)
    return state
  }),
}))

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

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

const TIMED_OCCURRENCE: CalendarOccurrence = {
  id: 'test-uid::2026-06-15T10:00:00',
  uid: 'test-uid',
  calendarId: 1,
  calendarName: 'My Calendar',
  ownerUserId: 1,
  ownerName: 'Alice',
  color: '#4A90D9',
  isShared: false,
  title: 'Team Standup',
  start: '2026-06-15T10:00:00-04:00[America/New_York]',
  end: '2026-06-15T10:30:00-04:00[America/New_York]',
  allDay: false,
  location: 'Conference Room B',
  description: 'Daily team sync meeting',
  hasRrule: false,
}

const OCCURRENCE_WITH_HTML: CalendarOccurrence = {
  ...TIMED_OCCURRENCE,
  id: 'xss-uid::2026-06-15T10:00:00',
  uid: 'xss-uid',
  title: '<script>alert("xss")</script>Team Meeting',
  description: '<b>Bold</b> description',
  location: '<img src=x onerror=alert(1)>Room',
}

const ALLDAY_OCCURRENCE: CalendarOccurrence = {
  id: 'allday-uid::2026-06-20',
  uid: 'allday-uid',
  calendarId: 2,
  calendarName: 'Shared Calendar',
  ownerUserId: 1,
  ownerName: 'Alice',
  color: '#F25C7A',
  isShared: true,
  title: 'Birthday Party',
  start: '2026-06-20',
  end: '2026-06-20',
  allDay: true,
  location: null,
  description: null,
  hasRrule: false,
}

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

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

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

function renderPopover(occurrence = TIMED_OCCURRENCE) {
  mockOpenEventId = occurrence.id
  ;(useCalendarStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(
    (selector?: (s: Record<string, unknown>) => unknown) => {
      const state = {
        openEventId: mockOpenEventId,
        setOpenEventId: mockSetOpenEventId,
        setEventForm: mockSetEventForm,
        setDeleteDialog: mockSetDeleteDialog,
      }
      if (typeof selector === 'function') return selector(state)
      return state
    },
  )

  const client = new QueryClient({
    defaultOptions: { queries: { retry: false, staleTime: Infinity } },
  })
  // Pre-populate the events cache so EventDetailPopover can resolve by id
  client.setQueryData(['events'], { occurrences: [occurrence] })

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

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

describe('EventDetailPopover', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockOpenEventId = null
  })

  it('renders the event title as a heading', () => {
    renderPopover(TIMED_OCCURRENCE)
    expect(screen.getByRole('heading')).toHaveTextContent('Team Standup')
  })

  it('renders location when present', () => {
    renderPopover(TIMED_OCCURRENCE)
    expect(screen.getByText(/Conference Room B/)).toBeDefined()
  })

  it('renders description when present', () => {
    renderPopover(TIMED_OCCURRENCE)
    expect(screen.getByText(/Daily team sync meeting/)).toBeDefined()
  })

  it('renders owner name in footer for personal events', () => {
    renderPopover(TIMED_OCCURRENCE)
    // TIMED_OCCURRENCE is personal (isShared:false) with ownerName:'Alice'
    expect(screen.getByText(/Alice/)).toBeDefined()
  })

  it('renders "Family" in footer for shared calendar events', () => {
    renderPopover(ALLDAY_OCCURRENCE)
    // ALLDAY_OCCURRENCE has isShared:true — footer must show 'Family'
    expect(screen.getByText('Family')).toBeDefined()
  })

  it('renders calendarName in footer when ownerName is null', () => {
    const noOwnerName: CalendarOccurrence = {
      ...TIMED_OCCURRENCE,
      id: 'no-owner-uid::2026-06-15T10:00:00',
      ownerName: null,
    }
    renderPopover(noOwnerName)
    expect(screen.getByText(/My Calendar/)).toBeDefined()
  })

  it('renders an all-day event without crashing', () => {
    renderPopover(ALLDAY_OCCURRENCE)
    expect(screen.getByRole('heading')).toHaveTextContent('Birthday Party')
  })

  it('close button has aria-label="Close"', () => {
    renderPopover(TIMED_OCCURRENCE)
    expect(screen.getByLabelText('Close')).toBeDefined()
  })

  it('pressing Escape calls setOpenEventId(null)', () => {
    renderPopover(TIMED_OCCURRENCE)
    fireEvent.keyDown(document, { key: 'Escape' })
    expect(mockSetOpenEventId).toHaveBeenCalledWith(null)
  })

  it('clicking the close button calls setOpenEventId(null)', () => {
    renderPopover(TIMED_OCCURRENCE)
    fireEvent.click(screen.getByLabelText('Close'))
    expect(mockSetOpenEventId).toHaveBeenCalledWith(null)
  })

  it('clicking the backdrop calls setOpenEventId(null)', () => {
    renderPopover(TIMED_OCCURRENCE)
    fireEvent.click(screen.getByTestId('popover-backdrop'))
    expect(mockSetOpenEventId).toHaveBeenCalledWith(null)
  })

  it('renders nothing when openEventId is null', () => {
    mockOpenEventId = null
    ;(useCalendarStore as unknown as ReturnType<typeof vi.fn>).mockImplementation(
      (selector?: (s: Record<string, unknown>) => unknown) => {
        const state = {
          openEventId: null,
          setOpenEventId: mockSetOpenEventId,
          setEventForm: mockSetEventForm,
          setDeleteDialog: mockSetDeleteDialog,
        }
        if (typeof selector === 'function') return selector(state)
        return state
      },
    )
    const client = new QueryClient({
      defaultOptions: { queries: { retry: false, staleTime: Infinity } },
    })
    const { container } = render(
      <QueryClientProvider client={client}>
        <EventDetailPopover />
      </QueryClientProvider>,
    )
    expect(container.firstChild).toBeNull()
  })

  it('XSS guard: HTML in title renders as escaped text, not as DOM elements', () => {
    renderPopover(OCCURRENCE_WITH_HTML)
    const heading = screen.getByRole('heading')
    // <script> must NOT be injected as a DOM element
    expect(heading.innerHTML).not.toContain('<script>')
    // The raw text including angle brackets must appear as literal text
    expect(heading.textContent).toContain('<script>alert("xss")</script>Team Meeting')
  })

  it('XSS guard: HTML in description renders as escaped text', () => {
    renderPopover(OCCURRENCE_WITH_HTML)
    const descEl = screen.getByTestId('event-description')
    // <b> must NOT be rendered as a bold element
    expect(descEl.innerHTML).not.toContain('<b>')
    expect(descEl.textContent).toContain('<b>Bold</b> description')
  })

  // ── Phase 3 footer: Edit/Delete actions ────────────────────────────────────

  it('footer renders an "Edit" button', () => {
    renderPopover(TIMED_OCCURRENCE)
    expect(screen.getByRole('button', { name: /edit/i })).toBeInTheDocument()
  })

  it('footer renders a "Delete" button', () => {
    renderPopover(TIMED_OCCURRENCE)
    expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument()
  })

  it('clicking "Edit" opens EventForm in edit mode and closes popover', () => {
    renderPopover(TIMED_OCCURRENCE)
    fireEvent.click(screen.getByRole('button', { name: /edit/i }))
    expect(mockSetEventForm).toHaveBeenCalledWith(true, 'edit', TIMED_OCCURRENCE.uid)
    expect(mockSetOpenEventId).toHaveBeenCalledWith(null)
  })

  it('clicking "Delete" opens DeleteConfirmationDialog (setDeleteDialog)', () => {
    renderPopover(TIMED_OCCURRENCE)
    fireEvent.click(screen.getByRole('button', { name: /delete/i }))
    expect(mockSetDeleteDialog).toHaveBeenCalledWith(true, TIMED_OCCURRENCE.uid)
  })

  it('BUG-3 regression: IANA-bracketed start/end does not produce "Invalid Date" in rendered output', () => {
    // Fastmail events are serialized with IANA bracket notation e.g. '2026-06-18T08:00:00-04:00[America/Toronto]'.
    // new Date() cannot parse the bracket, so the date/time line showed "Invalid Date, Invalid Date – Invalid Date".
    // After the fix, the bracket is stripped before parsing.
    const occurrence: CalendarOccurrence = {
      ...TIMED_OCCURRENCE,
      id: 'iana-bracket-uid::1718712000000',
      uid: 'iana-bracket-uid',
      start: '2026-06-18T08:00:00-04:00[America/Toronto]',
      end: '2026-06-18T09:00:00-04:00[America/Toronto]',
    }
    renderPopover(occurrence)

    // The date/time text must not contain 'Invalid Date'
    const dialogEl = screen.getByRole('dialog')
    expect(dialogEl.textContent).not.toContain('Invalid Date')

    // It must contain recognizable date content (month name or a digit)
    // toLocaleDateString output varies by locale; check for a digit at minimum
    const dateTimeText = dialogEl.textContent ?? ''
    expect(dateTimeText).toMatch(/\d/)
  })
})
