import { CalendarApi } from '@fullcalendar/core'
import {
  act,
  render,
  screen,
  fireEvent,
  waitFor,
  RenderResult
} from '@testing-library/react'
import React from 'react'
import { DatePickerMobile } from '@/components/Menubar/components/DatePickerMobile'

jest.mock('twake-i18n', () => ({
  useI18n: (): { t: (key: string) => string; lang: string } => ({
    t: (key: string) => key,
    lang: 'en'
  })
}))

jest.mock('@linagora/twake-mui', () => {
  const actual = jest.requireActual<typeof import('@linagora/twake-mui')>(
    '@linagora/twake-mui'
  )
  return {
    ...actual,
    useTheme: (): { palette: { grey: { 500: string } } } => ({
      palette: { grey: { 500: '#9e9e9e' } }
    })
  }
})

const scrollIntoViewMock = jest.fn()
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView

beforeAll(() => {
  window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock
})

afterAll(() => {
  window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView
})

function makeCalendarApi(initialDate: Date): CalendarApi & {
  getDate: jest.Mock
  gotoDate: jest.Mock
} {
  let _date = initialDate
  const mockApi = {
    getDate: jest.fn(() => _date),
    gotoDate: jest.fn((d: Date) => {
      _date = d
    })
  }
  return mockApi as unknown as CalendarApi & typeof mockApi
}

describe('DatePickerMobile', () => {
  beforeEach(() => {
    jest.clearAllMocks()
    scrollIntoViewMock.mockClear()
  })

  const setup = async (
    initialDate: Date
  ): Promise<
    RenderResult & {
      calendarApi: CalendarApi
      onDateChange: jest.Mock
      onCloseDatePicker: jest.Mock
    }
  > => {
    const calendarApi = makeCalendarApi(initialDate)
    const onDateChange = jest.fn()
    const onCloseDatePicker = jest.fn()

    const renderResult = await act(() =>
      render(
        <DatePickerMobile
          calendarRef={{ current: calendarApi }}
          currentDate={initialDate}
          onDateChange={onDateChange}
          onCloseDatePicker={onCloseDatePicker}
        />
      )
    )
    return { ...renderResult, calendarApi, onDateChange, onCloseDatePicker }
  }

  const expectNavigatedDate = (
    calendarApi: CalendarApi,
    expected: { day: number; month: number; year: number }
  ): void => {
    // eslint-disable-next-line @typescript-eslint/unbound-method
    const gotoDateMock = calendarApi.gotoDate as jest.Mock<void, [Date]>
    const navigatedDate: Date = gotoDateMock.mock.calls[0][0]
    expect(navigatedDate.getDate()).toBe(expected.day)
    expect(navigatedDate.getMonth()).toBe(expected.month)
    expect(navigatedDate.getFullYear()).toBe(expected.year)
  }

  const testMonthNavigation = async (
    initialDate: Date,
    monthToClick: number,
    expected: { day: number; month: number; year: number }
  ): Promise<void> => {
    const { calendarApi } = await setup(initialDate)

    const monthButton = screen.getByText(`months.short.${monthToClick}`)
    act(() => {
      fireEvent.click(monthButton)
    })

    await waitFor(() => {
      // eslint-disable-next-line @typescript-eslint/unbound-method
      expect(calendarApi.gotoDate).toHaveBeenCalled()
    })

    expectNavigatedDate(calendarApi, expected)
  }

  it('calls calendarApi.gotoDate and onDateChange with the selected date', async () => {
    const initialDate = new Date(2025, 5, 15) // June 15, 2025
    const { calendarApi, onDateChange } = await setup(initialDate)

    const day20 = screen.getByRole('gridcell', { name: '20' })
    act(() => {
      fireEvent.click(day20)
    })

    await waitFor(() => {
      // eslint-disable-next-line @typescript-eslint/unbound-method
      expect(calendarApi.gotoDate).toHaveBeenCalled()
    })

    expectNavigatedDate(calendarApi, { day: 20, month: 5, year: 2025 })
    // eslint-disable-next-line @typescript-eslint/unbound-method
    const gotoDateMock = calendarApi.gotoDate as jest.Mock<void, [Date]>
    expect(onDateChange).toHaveBeenCalledWith(gotoDateMock.mock.calls[0][0])
  })

  it('navigates to the clicked month while keeping the current day', async () => {
    await testMonthNavigation(
      new Date(2025, 5, 15), // June 15, 2025
      8,
      { day: 15, month: 8, year: 2025 }
    )
  })

  it('clamps the day to the last valid day when switching to a shorter month', async () => {
    await testMonthNavigation(
      new Date(2025, 0, 31), // Jan 31, 2025
      1,
      { day: 28, month: 1, year: 2025 }
    )
  })

  it('scrollIntoView is called on the correct month button when currentDate changes to another month', async () => {
    const initialDate = new Date(2025, 5, 15) // June 15, 2025  (month index 5)
    const { rerender, calendarApi, onDateChange } = await setup(initialDate)

    // Simulate parent updating currentDate to August (e.g. user picked a day in August)
    const newDate = new Date(2025, 7, 5) // August 5, 2025  (month index 7)

    act(() => {
      rerender(
        <DatePickerMobile
          calendarRef={{ current: calendarApi }}
          currentDate={newDate}
          onDateChange={onDateChange}
          onCloseDatePicker={jest.fn()} // Need to pass dummy fn for required prop
        />
      )
    })

    await waitFor(() => {
      // scrollIntoView should have been called at least once (initial mount + rerender)
      expect(scrollIntoViewMock).toHaveBeenCalledWith({
        behavior: 'smooth',
        block: 'nearest',
        inline: 'center'
      })
    })

    // The last call should correspond to August (index 7) button being highlighted
    const augustButton = screen.getByText('months.short.7')
    expect(augustButton).toBeInTheDocument()
  })
})
