import * as appHooks from '@/app/hooks'
import { AppDispatch } from '@/app/store'
import CalendarApp from '@/components/Calendar/Calendar'
import {
  createEventHandlers,
  EventHandlersProps
} from '@/components/Calendar/handlers/eventHandlers'
import * as eventThunks from '@/features/Calendars/services'
import EventUpdateModal from '@/features/Events/EventUpdateModal'
import { CalendarApi } from '@fullcalendar/core'
import { jest } from '@jest/globals'
import '@testing-library/jest-dom'
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
import { renderWithProviders } from '../utils/Renderwithproviders'

describe('CalendarApp integration', () => {
  const today = new Date()
  const start = new Date(today)
  start.setHours(10, 0, 0, 0)
  const end = new Date(today)
  end.setHours(11, 0, 0, 0)

  beforeEach(() => {
    jest.clearAllMocks()
  })

  const renderCalendar = () => {
    const preloadedState = {
      user: {
        userData: {
          sub: 'test',
          email: 'test@test.com',
          sid: 'mockSid',
          openpaasId: '667037022b752d0026472254'
        },
        tokens: { accessToken: 'token' } // required to avoid redirect
      },
      calendars: {
        list: {
          '667037022b752d0026472254/cal1': {
            name: 'Calendar 1',
            id: '667037022b752d0026472254/cal1',
            color: { light: '#FFFFFF', dark: '#000000' },
            owner: { emails: ['alice@example.com'] },
            events: {
              event1: {
                id: 'event1',
                calId: '667037022b752d0026472254/cal1',
                uid: 'event1',
                title: 'Test Event',
                start: start.toISOString(),
                end: end.toISOString(),
                partstat: 'ACCEPTED',
                organizer: {
                  cn: 'Alice',
                  cal_address: 'alice@example.com'
                },
                attendee: [
                  {
                    cn: 'Alice',
                    partstat: 'ACCEPTED',
                    rsvp: 'TRUE',
                    role: 'REQ-PARTICIPANT',
                    cutype: 'INDIVIDUAL',
                    cal_address: 'alice@example.com'
                  }
                ]
              }
            }
          }
        },
        pending: false
      }
    }

    const mockCalendarRef = { current: null }
    renderWithProviders(
      <CalendarApp
        setCurrentView={jest.fn()}
        onViewChange={jest.fn()}
        openSidebar={false}
        onCloseSidebar={jest.fn()}
        currentView="timeGridWeek"
        calendarRef={mockCalendarRef}
      />,
      preloadedState
    )
  }

  it('renders the event on the calendar and calendarRef works', async () => {
    const dispatch = jest.fn().mockReturnValue(
      Object.assign(Promise.resolve({}), {
        unwrap: () => Promise.resolve({})
      })
    ) as unknown as AppDispatch
    jest.spyOn(appHooks, 'useAppDispatch').mockReturnValue(dispatch)

    renderCalendar()
    const calendarRef: React.RefObject<CalendarApi | null> = (window as any)
      .__calendarRef

    const calendarApi = calendarRef.current

    // Wait for the FullCalendar DOM to populate
    const eventEl = await screen.findByText('Test Event', {}, { timeout: 3000 })
    expect(eventEl).toBeInTheDocument()
    act(() => {
      if (calendarApi) {
        const fcEvent = calendarApi.getEventById('event1')
        expect(fcEvent?.title).toBe('Test Event')
        const oldEnd = new Date(today.getTime() + 3600000) // +1 hour
        const newEnd = new Date(oldEnd.getTime() + 1800000) // +30 min

        fcEvent?.setEnd(newEnd)

        waitFor(() => expect(dispatch).toHaveBeenCalled())
      }
    })
  })

  const createPreloadedState = (eventProps = {}) => ({
    user: {
      userData: {
        sub: 'test',
        email: 'test@test.com',
        sid: 'mockSid',
        openpaasId: '667037022b752d0026472254'
      },
      tokens: {
        accessToken: 'token'
      }
    },
    calendars: {
      list: {
        '667037022b752d0026472254/cal1': {
          name: 'Calendar 1',
          id: '667037022b752d0026472254/cal1',
          color: { light: '#FFFFFF', dark: '#000000' },
          owner: { emails: ['alice@example.com'] },
          events: {
            event1: {
              id: 'event1',
              calId: '667037022b752d0026472254/cal1',
              uid: 'event1',
              start: new Date().toISOString(),
              end: new Date(Date.now() + 3600000).toISOString(),
              partstat: 'ACCEPTED',
              organizer: {
                cn: 'Alice',
                cal_address: 'alice@example.com'
              },
              attendee: [
                {
                  cn: 'Alice',
                  partstat: 'ACCEPTED',
                  rsvp: 'TRUE',
                  role: 'REQ-PARTICIPANT',
                  cutype: 'INDIVIDUAL',
                  cal_address: 'alice@example.com'
                }
              ],
              ...eventProps
            }
          }
        }
      },
      pending: false
    }
  })

  it('renders lock icon for private events on the calendar', async () => {
    Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
      configurable: true,
      value: 120
    })
    const preloadedState = createPreloadedState({
      class: 'PRIVATE',
      title: 'Private Event'
    })
    const mockCalendarRef = { current: null }
    renderWithProviders(
      <CalendarApp
        setCurrentView={jest.fn()}
        onViewChange={jest.fn()}
        openSidebar={false}
        onCloseSidebar={jest.fn()}
        currentView="timeGridWeek"
        calendarRef={mockCalendarRef}
      />,
      preloadedState
    )
    const card = screen.getByTestId('event-card-event1')
    const lockIcon = within(card).getByTestId('LockOutlineIcon')
    expect(lockIcon).toBeInTheDocument()
  })

  it('renders lock icon for confidential events on the calendar', async () => {
    Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
      configurable: true,
      value: 120
    })
    const preloadedState = createPreloadedState({
      class: 'CONFIDENTIAL',
      title: 'Confidential Event'
    })
    const mockCalendarRef = { current: null }
    renderWithProviders(
      <CalendarApp
        setCurrentView={jest.fn()}
        onViewChange={jest.fn()}
        openSidebar={false}
        onCloseSidebar={jest.fn()}
        currentView="timeGridWeek"
        calendarRef={mockCalendarRef}
      />,
      preloadedState
    )

    const card = screen.getByTestId('event-card-event1')
    const lockIcon = within(card).getByTestId('LockOutlineIcon')
    expect(lockIcon).toBeInTheDocument()
  })

  it('does NOT render a lock icon for public events on the calendar', async () => {
    Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
      configurable: true,
      value: 120
    })
    const preloadedState = createPreloadedState({
      class: 'PUBLIC',
      title: 'Public Event'
    })
    const mockCalendarRef = { current: null }
    renderWithProviders(
      <CalendarApp
        setCurrentView={jest.fn()}
        onViewChange={jest.fn()}
        openSidebar={false}
        onCloseSidebar={jest.fn()}
        currentView="timeGridWeek"
        calendarRef={mockCalendarRef}
      />,
      preloadedState
    )

    const card = screen.getByTestId('event-card-event1')
    const lockIcon = within(card).queryByTestId('LockOutlineIcon')
    expect(lockIcon).not.toBeInTheDocument()
  })

  it('does render a title for events without any attendees or user as organizer', async () => {
    const mockCalendarRef = { current: null }
    renderWithProviders(
      <CalendarApp
        setCurrentView={jest.fn()}
        onViewChange={jest.fn()}
        openSidebar={false}
        onCloseSidebar={jest.fn()}
        currentView="timeGridWeek"
        calendarRef={mockCalendarRef}
      />,
      {
        user: {
          userData: {
            sub: 'test',
            email: 'test@test.com',
            sid: 'mockSid',
            openpaasId: '667037022b752d0026472254'
          },
          tokens: {
            accessToken: 'token'
          }
        },
        calendars: {
          list: {
            '667037022b752d0026472254/cal1': {
              name: 'Calendar 1',
              id: '667037022b752d0026472254/cal1',
              color: { light: '#FF0000', dark: '#000' },
              owner: { emails: ['alice@example.com'] },
              events: {
                event1: {
                  id: 'event1',
                  calId: '667037022b752d0026472254/cal1',
                  uid: 'event1',
                  start: new Date().toISOString(),
                  end: new Date(Date.now() + 3600000).toISOString(),
                  partstat: 'ACCEPTED',
                  organizer: {
                    cn: 'Alice',
                    cal_address: 'alice@example.com'
                  },
                  class: 'PUBLIC',
                  title: 'Public Event'
                }
              }
            }
          },
          pending: false
        }
      }
    )

    expect(screen.getByText('Public Event')).toBeInTheDocument()
  })
  describe('BUGFIX', () => {
    const preloadedState = {
      user: {
        userData: {
          sub: 'test',
          email: 'test@test.com',
          sid: 'mockSid',
          openpaasId: '667037022b752d0026472254'
        },
        tokens: {
          accessToken: 'token'
        }
      },
      calendars: {
        list: {
          '667037022b752d0026472254/cal1': {
            name: 'Calendar 1',
            id: '667037022b752d0026472254/cal1',
            color: { light: '#FFFFFF', dark: '#000000' },
            owner: { emails: ['alice@example.com'] },
            events: {
              event1: {
                id: 'event1',
                calId: '667037022b752d0026472254/cal1',
                uid: 'event1',
                title: 'Original Event',
                start: new Date('2025-11-14T10:31:00.000Z').toISOString(),
                end: new Date('2025-11-14T11:31:00.000Z').toISOString(),
                class: 'PUBLIC',
                partstat: 'ACCEPTED',
                sequence: 2,
                organizer: {
                  cn: 'Alice',
                  cal_address: 'alice@example.com'
                },
                attendee: [
                  {
                    cn: 'Alice',
                    partstat: 'ACCEPTED',
                    rsvp: 'TRUE',
                    role: 'CHAIR',
                    cutype: 'INDIVIDUAL',
                    cal_address: 'alice@example.com'
                  },
                  {
                    cn: 'Bob',
                    partstat: 'ACCEPTED',
                    rsvp: 'TRUE',
                    role: 'REQ-PARTICIPANT',
                    cutype: 'INDIVIDUAL',
                    cal_address: 'bob@example.com'
                  }
                ]
              }
            }
          }
        },
        pending: false
      }
    }

    it('keeps all attendees event participation on title update', async () => {
      const updateSpy = jest
        .spyOn(eventThunks, 'putEventAsync')
        .mockImplementation(payload => {
          const promise = Promise.resolve(payload)
          ;(promise as any).unwrap = () => promise
          return () => promise as any
        })
      const onClose = jest.fn()
      renderWithProviders(
        <EventUpdateModal
          eventId="event1"
          calId="667037022b752d0026472254/cal1"
          open={true}
          onClose={onClose}
          typeOfAction="solo"
        />,
        preloadedState
      )

      const titleInput = await screen.findByDisplayValue('Original Event')

      await act(async () => {
        fireEvent.change(titleInput, 'Updated Event')
      })

      const saveButton = screen.getByRole('button', { name: /save/i })
      await act(async () => {
        saveButton.click()
      })

      await waitFor(() => expect(updateSpy).toHaveBeenCalled())

      const dispatchedCalls = updateSpy.mock.calls
      expect(dispatchedCalls.length).toBeGreaterThan(0)
      const updatedEvent = dispatchedCalls[0][0].newEvent

      // Ensure organizer attendee info is preserved
      const organizerAttendee = updatedEvent?.attendee?.find(
        (a: any) => a.cal_address === 'alice@example.com'
      )

      expect(organizerAttendee).toBeTruthy()
      expect(organizerAttendee?.partstat).toBe('ACCEPTED')
      expect(organizerAttendee?.role).toBe('CHAIR')

      // Ensure normal attendee info is preserved too
      const normalAttendee = updatedEvent?.attendee?.find(
        (a: any) => a.cal_address === 'bob@example.com'
      )

      expect(normalAttendee).toBeTruthy()
      expect(normalAttendee?.partstat).toBe('ACCEPTED')
      expect(normalAttendee?.role).toBe('REQ-PARTICIPANT')

      // Verify SEQUENCE is incremented
      expect(updatedEvent?.sequence).toBe(3) // 2 + 1
    })

    it('changes normal attendee to need action on time update and no organizer changes', async () => {
      const updateSpy = jest
        .spyOn(eventThunks, 'putEventAsync')
        .mockImplementation(payload => {
          const promise = Promise.resolve(payload)
          ;(promise as any).unwrap = () => promise
          return () => promise as any
        })
      const onClose = jest.fn()
      renderWithProviders(
        <EventUpdateModal
          eventId="event1"
          calId="667037022b752d0026472254/cal1"
          open={true}
          onClose={onClose}
          typeOfAction="solo"
        />,
        preloadedState
      )

      // Expand to show date/time inputs (normal mode shows DateTimeSummary)
      fireEvent.click(
        screen.getByRole('button', { name: 'common.moreOptions' })
      )
      const startDateInput = await screen.findByTestId('start-time-input')

      await act(async () => {
        fireEvent.change(startDateInput, {
          target: { value: '08:00' }
        })
        fireEvent.blur(startDateInput)
      })

      // Wait for blur handler to complete
      await act(async () => {
        await new Promise(resolve => setTimeout(resolve, 150))
      })

      const saveButton = screen.getByRole('button', { name: /save/i })
      await act(async () => {
        saveButton.click()
      })

      await waitFor(() => expect(updateSpy).toHaveBeenCalled())

      const dispatchedCalls = updateSpy.mock.calls
      expect(dispatchedCalls.length).toBeGreaterThan(0)
      const updatedEvent = dispatchedCalls[0][0].newEvent

      // Ensure organizer attendee info is preserved
      const organizerAttendee = updatedEvent?.attendee?.find(
        (a: any) => a.cal_address === 'alice@example.com'
      )

      expect(organizerAttendee).toBeTruthy()
      expect(organizerAttendee?.partstat).toBe('ACCEPTED')
      expect(organizerAttendee?.role).toBe('CHAIR')

      // Ensure normal attendee partstat is updated
      const normalAttendee = updatedEvent?.attendee?.find(
        (a: any) => a.cal_address === 'bob@example.com'
      )

      expect(normalAttendee).toBeTruthy()
      expect(normalAttendee?.partstat).toBe('NEEDS-ACTION')
      expect(normalAttendee?.role).toBe('REQ-PARTICIPANT')

      // Verify SEQUENCE is incremented
      expect(updatedEvent?.sequence).toBe(3) // 2 + 1
    })

    it('update event attendees on drag', async () => {
      // Mock dispatch locally — this test calls createEventHandlers directly
      // and does not go through the Redux store or useAppDispatch.
      const mockDispatch = jest.fn().mockReturnValue(
        Object.assign(Promise.resolve({}), {
          unwrap: () => Promise.resolve({})
        })
      ) as unknown as AppDispatch
      jest
        .spyOn(appHooks, 'useAppDispatch')
        .mockReturnValue(mockDispatch as unknown as AppDispatch)

      const updateSpy = jest
        .spyOn(eventThunks, 'putEventAsync')
        .mockImplementation(payload => {
          const promise = Promise.resolve(payload)
          ;(promise as any).unwrap = () => promise
          return () => promise as any
        })

      const eventHandlers = createEventHandlers({
        setSelectedRange: jest.fn(),
        setOpenEventModal: jest.fn(),
        setTempEvent: jest.fn(),
        setOpenEventDisplay: jest.fn(),
        dispatch: mockDispatch,
        calendarRange: { start: new Date(), end: new Date() },
        setEventDisplayedId: jest.fn(),
        setEventDisplayedCalId: jest.fn(),
        setEventDisplayedTemp: jest.fn(),
        calendars: preloadedState.calendars.list,
        setSelectedEvent: jest.fn(),
        setAfterChoiceFunc: jest.fn(),
        setOpenEditModePopup: jest.fn()
      } as unknown as EventHandlersProps)

      const mockArg = {
        event: {
          _def: {
            extendedProps: {
              uid: 'event1',
              calId: '667037022b752d0026472254/cal1'
            }
          }
        },
        // drag event → move by 1 day
        delta: { years: 0, months: 0, days: 1, milliseconds: 0 }
      }

      renderCalendar()
      eventHandlers.handleEventDrop(mockArg)

      expect(updateSpy).toHaveBeenCalled()

      // Extract the dispatched update event
      const dispatched = updateSpy.mock.calls[0]

      expect(dispatched).toBeTruthy()

      const updatePayload = dispatched[0]
      const updatedEvent = updatePayload.newEvent

      // Organizer should remain unchanged
      const organizer = updatedEvent.attendee.find(
        (a: any) => a.cal_address === 'alice@example.com'
      )
      expect(organizer?.partstat).toBe('ACCEPTED')

      // Normal attendee must become NEEDS-ACTION
      const normal = updatedEvent.attendee.find(
        (a: any) => a.cal_address === 'bob@example.com'
      )
      expect(normal?.partstat).toBe('NEEDS-ACTION')

      // Verify SEQUENCE is incremented
      expect(updatedEvent?.sequence).toBe(3) // 2 + 1
    })

    it('update event attendees on resize', async () => {
      // Mock dispatch locally — this test calls createEventHandlers directly
      // and does not go through the Redux store or useAppDispatch.
      const mockDispatch = jest.fn().mockReturnValue(
        Object.assign(Promise.resolve({}), {
          unwrap: () => Promise.resolve({})
        })
      ) as unknown as AppDispatch
      jest
        .spyOn(appHooks, 'useAppDispatch')
        .mockReturnValue(mockDispatch as unknown as AppDispatch)

      const updateSpy = jest
        .spyOn(eventThunks, 'putEventAsync')
        .mockImplementation(payload => {
          const promise = Promise.resolve(payload)
          ;(promise as any).unwrap = () => promise
          return () => promise as any
        })

      const eventHandlers = createEventHandlers({
        setSelectedRange: jest.fn(),
        setOpenEventModal: jest.fn(),
        setTempEvent: jest.fn(),
        setOpenEventDisplay: jest.fn(),
        dispatch: mockDispatch,
        calendarRange: { start: new Date(), end: new Date() },
        setEventDisplayedId: jest.fn(),
        setEventDisplayedCalId: jest.fn(),
        setEventDisplayedTemp: jest.fn(),
        calendars: preloadedState.calendars.list,
        setSelectedEvent: jest.fn(),
        setAfterChoiceFunc: jest.fn(),
        setOpenEditModePopup: jest.fn()
      } as unknown as EventHandlersProps)

      const mockArg = {
        event: {
          _def: {
            extendedProps: {
              uid: 'event1',
              calId: '667037022b752d0026472254/cal1'
            }
          }
        },
        startDelta: { years: 0, months: 0, days: 0, milliseconds: 0 },
        endDelta: { years: 0, months: 0, days: 0, milliseconds: 3600000 } // 1 hour
      }

      renderCalendar()
      eventHandlers.handleEventResize(mockArg)

      expect(updateSpy).toHaveBeenCalled()

      // Extract the dispatched update event
      const dispatched = updateSpy.mock.calls[0]

      expect(dispatched).toBeTruthy()

      const updatePayload = dispatched[0]
      const updatedEvent = updatePayload.newEvent

      // Organizer should remain unchanged
      const organizer = updatedEvent.attendee.find(
        (a: any) => a.cal_address === 'alice@example.com'
      )
      expect(organizer?.partstat).toBe('ACCEPTED')

      // Normal attendee must become NEEDS-ACTION
      const normal = updatedEvent.attendee.find(
        (a: any) => a.cal_address === 'bob@example.com'
      )
      expect(normal?.partstat).toBe('NEEDS-ACTION')

      // Verify SEQUENCE is incremented
      expect(updatedEvent?.sequence).toBe(3) // 2 + 1
    })
  })
})
