import CalendarPopover from '@/components/Calendar/CalendarModal'
import { getSecretLink } from '@/features/Calendars/CalendarApi'
import { Calendar } from '@/features/Calendars/CalendarTypes'
import * as eventThunks from '@/features/Calendars/services'
import * as delegationThunks from '@/features/Calendars/services/updateDelegationCalendarAsync'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { renderWithProviders } from '../../utils/Renderwithproviders'

jest.mock('@/features/Calendars/CalendarApi', () => ({
  getSecretLink: jest.fn()
}))

const mockThunkWithUnwrap = (resolvedValue: unknown = {}) =>
  jest.fn().mockImplementation(() => {
    const dispatchResult = Object.assign(Promise.resolve(resolvedValue), {
      unwrap: () => Promise.resolve(resolvedValue)
    })
    return jest.fn().mockReturnValue(dispatchResult)
  })

describe('CalendarPopover', () => {
  const mockOnClose = jest.fn()

  beforeEach(() => {
    jest.clearAllMocks()
    jest
      .spyOn(delegationThunks, 'updateDelegationCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())
  })

  const renderPopover = (open = true) => {
    const preloadedState = {
      user: {
        userData: {
          sub: 'test',
          email: 'test@test.com',
          sid: 'aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro',
          openpaasId: '667037022b752d0026472254'
        }
      },
      calendars: { list: {}, pending: true }
    }
    renderWithProviders(
      <CalendarPopover open={open} onClose={mockOnClose} />,
      preloadedState
    )
  }

  it('renders popover and inputs', () => {
    renderPopover()

    expect(screen.getByLabelText(/Name/i)).toBeInTheDocument()
    expect(screen.getByText('event.form.addDescription')).toBeInTheDocument()
  })

  it('updates name and description fields', () => {
    renderPopover()

    const nameInput = screen.getByLabelText(/Name/i)
    fireEvent.change(nameInput, { target: { value: 'My Calendar' } })
    expect(nameInput).toHaveValue('My Calendar')

    fireEvent.click(screen.getByText('event.form.addDescription'))
    const descInput = screen.getByLabelText(/Description/i)
    fireEvent.change(descInput, { target: { value: 'Test description' } })
    expect(descInput).toHaveValue('Test description')
  })

  it('dispatches createCalendar and calls onClose when Save clicked', async () => {
    jest
      .spyOn(eventThunks, 'createCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())

    renderPopover()

    fireEvent.change(screen.getByLabelText(/Name/i), {
      target: { value: 'Test Calendar' }
    })
    fireEvent.click(screen.getByText('event.form.addDescription'))
    fireEvent.change(screen.getByLabelText(/Description/i), {
      target: { value: 'Test Description' }
    })

    const colorButtons = screen.getAllByRole('button', {
      name: /select color/i
    })
    fireEvent.click(colorButtons[0])

    fireEvent.click(screen.getByRole('button', { name: /Create/i }))

    await waitFor(() =>
      expect(eventThunks.createCalendarAsync).toHaveBeenCalled()
    )
    await waitFor(() =>
      expect(mockOnClose).toHaveBeenCalledWith({}, 'backdropClick')
    )
  })

  it('calls onClose when Cancel clicked', () => {
    renderPopover()

    fireEvent.click(screen.getByRole('button', { name: /Cancel/i }))

    expect(mockOnClose).toHaveBeenCalledWith({}, 'backdropClick')
  })
})

describe('CalendarPopover (editing mode)', () => {
  const mockOnClose = jest.fn()

  const baseUser = {
    userData: {
      sub: 'test',
      email: 'test@test.com',
      sid: 'mockSid',
      openpaasId: 'user1'
    }
  }

  const existingCalendar: Calendar = {
    id: 'user1/cal1',
    link: '/calendars/user/cal1',
    name: 'Work Calendar',
    description: 'Team meetings',
    color: { light: '#33B679' },
    owner: { firstname: 'alice', emails: ['alice@example.com'] },
    visibility: 'public',
    events: {}
  }

  beforeEach(() => {
    jest.clearAllMocks()
    jest
      .spyOn(delegationThunks, 'updateDelegationCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())
  })

  it('prefills fields when calendar prop is given', () => {
    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    )

    expect(screen.getByLabelText(/Name/i)).toHaveValue('Work Calendar')
    expect(screen.getByLabelText(/Description/i)).toHaveValue('Team meetings')
  })

  test('Save button is disabled when name is empty or whitespace only', () => {
    renderWithProviders(<CalendarPopover open={true} onClose={jest.fn()} />, {
      user: baseUser
    })

    const saveButton = screen.getByRole('button', { name: /create/i })
    expect(saveButton).toBeDisabled()
    // only spaces
    const nameInput = screen.getByLabelText(/name/i)
    fireEvent.change(nameInput, { target: { value: '    ' } })

    expect(saveButton).toBeDisabled()

    // valid name
    fireEvent.change(nameInput, { target: { value: 'Work Calendar' } })
    expect(saveButton).toBeEnabled()
  })

  it('allows modifying and saving existing calendar', async () => {
    jest
      .spyOn(eventThunks, 'patchCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    )

    // Change name
    fireEvent.change(screen.getByLabelText(/Name/i), {
      target: { value: 'Updated Calendar' }
    })

    // Save
    fireEvent.click(screen.getByRole('button', { name: 'actions.save' }))

    await waitFor(() =>
      expect(eventThunks.patchCalendarAsync).toHaveBeenCalledWith(
        expect.objectContaining({
          calId: 'user1/cal1',
          calLink: '/calendars/user/cal1',
          patch: {
            color: { light: '#33B679' },
            desc: 'Team meetings',
            name: 'Updated Calendar'
          }
        })
      )
    )
    await waitFor(() => expect(mockOnClose).toHaveBeenCalled())
  })
})

describe('CalendarPopover - Tabs Scenarios', () => {
  const mockOnClose = jest.fn()
  const baseUser = {
    userData: {
      openpaasId: 'user1'
    }
  }

  const writeText = jest.fn()

  Object.assign(navigator, {
    clipboard: {
      writeText
    }
  })

  const existingCalendar: Calendar = {
    id: 'user1/cal1',
    link: '/calendars/user1/cal1.json',
    name: 'Work Calendar',
    description: 'Team meetings',
    color: { light: '#33B679' },
    owner: { firstname: 'alice', emails: ['alice@example.com'] },
    visibility: 'public',
    events: {}
  }

  beforeEach(() => {
    jest.clearAllMocks()
    jest
      .spyOn(delegationThunks, 'updateDelegationCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())
  })

  it('resets state after closing and reopening', () => {
    const { rerender } = renderWithProviders(
      <CalendarPopover open={true} onClose={mockOnClose} />,
      { user: baseUser }
    )

    // Enter some data
    fireEvent.change(screen.getByLabelText(/Name/i), {
      target: { value: 'Temp Calendar' }
    })
    fireEvent.click(screen.getByRole('button', { name: /Cancel/i }))

    expect(mockOnClose).toHaveBeenCalled()

    // Reopen: state should be reset
    rerender(<CalendarPopover open={true} onClose={mockOnClose} />)
    expect(screen.getByLabelText(/Name/i)).toHaveValue('')
  })

  it('shows Access tab only when editing an existing calendar', () => {
    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    )

    expect(screen.getByRole('tab', { name: /Access/i })).toBeInTheDocument()
  })

  it('does not show Access tab when creating new calendar', () => {
    renderWithProviders(<CalendarPopover open={true} onClose={mockOnClose} />, {
      user: baseUser
    })

    expect(
      screen.queryByRole('tab', { name: /Access/i })
    ).not.toBeInTheDocument()
  })

  it('patches ACL when visibility changes', async () => {
    jest
      .spyOn(eventThunks, 'patchCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())

    jest
      .spyOn(eventThunks, 'patchACLCalendarAsync')
      .mockImplementation(mockThunkWithUnwrap())

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    )

    // By default: "All" (public) is selected
    const publicButton = screen.getByRole('button', { name: /All/i })
    const privateButton = screen.getByRole('button', { name: /You/i })

    expect(publicButton).toHaveAttribute('aria-pressed', 'true')
    expect(privateButton).toHaveAttribute('aria-pressed', 'false')

    // Change to private
    fireEvent.click(privateButton)

    expect(privateButton).toHaveAttribute('aria-pressed', 'true')
    expect(publicButton).toHaveAttribute('aria-pressed', 'false')

    // Save
    fireEvent.click(screen.getByRole('button', { name: 'actions.save' }))

    await waitFor(() =>
      expect(eventThunks.patchACLCalendarAsync).toHaveBeenCalledWith(
        expect.objectContaining({
          calId: 'user1/cal1',
          request: ''
        })
      )
    )
  })

  it('copies CalDAV link from Access tab', async () => {
    window.DAV_BASE_URL = 'https://cal.example.org'
    Object.assign(navigator, {
      clipboard: { writeText: jest.fn() }
    })
    ;(getSecretLink as jest.Mock).mockResolvedValue({
      secretLink: 'https://example.org/secret/initial'
    })

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    )

    // Switch to Access tab
    fireEvent.click(screen.getByRole('tab', { name: /Access/i }))

    // Expect text field with caldav link
    const input = screen.getByLabelText('calendar.caldav_access')
    expect(input).toHaveValue('https://cal.example.org/calendars/user1/cal1')

    // Click copy button (find button containing ContentCopyIcon)
    const copyIcon = screen.getAllByTestId('ContentCopyIcon')[0]
    const copyButton = copyIcon.closest('button')
    if (copyButton) {
      fireEvent.click(copyButton)
    }

    expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
      'https://cal.example.org/calendars/user1/cal1'
    )

    // Snackbar should appear
    await waitFor(() =>
      expect(screen.getByText('common.link_copied')).toBeInTheDocument()
    )
  })

  describe('Import flow', () => {
    const file = new File(['test'], 'events.ics', { type: 'text/calendar' })

    it("creates a new calendar and imports events when Import with 'new' target", async () => {
      jest
        .spyOn(eventThunks, 'createCalendarAsync')
        .mockImplementation(mockThunkWithUnwrap())
      jest
        .spyOn(eventThunks, 'importEventFromFileAsync')
        .mockImplementation(mockThunkWithUnwrap())

      renderWithProviders(
        <CalendarPopover open={true} onClose={mockOnClose} />,
        {
          user: baseUser
        }
      )

      // Switch to Import tab
      fireEvent.click(screen.getByRole('tab', { name: /Import/i }))

      // Provide new calendar params
      fireEvent.change(screen.getByLabelText(/Name/i), {
        target: { value: 'Imported Calendar' }
      })
      const fileInput = screen.getByLabelText('common.select_file')
      fireEvent.change(fileInput, { target: { files: [file] } })

      // Click Import
      fireEvent.click(screen.getByRole('button', { name: 'actions.import' }))

      await waitFor(() =>
        expect(eventThunks.createCalendarAsync).toHaveBeenCalled()
      )
      await waitFor(() =>
        expect(eventThunks.importEventFromFileAsync).toHaveBeenCalled()
      )
    })

    it('imports into an existing calendar when target is set', async () => {
      jest
        .spyOn(eventThunks, 'importEventFromFileAsync')
        .mockImplementation(mockThunkWithUnwrap())

      const calendars = {
        'user1/cal1': existingCalendar
      }

      renderWithProviders(
        <CalendarPopover
          open={true}
          onClose={mockOnClose}
          calendar={existingCalendar}
        />,
        { user: baseUser, calendars: { list: calendars } }
      )

      fireEvent.click(screen.getByRole('tab', { name: /Import/i }))
      const fileInput = screen.getByLabelText('common.select_file')
      fireEvent.change(fileInput, { target: { files: [file] } })

      fireEvent.click(screen.getByRole('button', { name: 'actions.import' }))

      await waitFor(() =>
        expect(eventThunks.importEventFromFileAsync).toHaveBeenCalledWith(
          expect.objectContaining({
            calLink: '/calendars/user1/cal1.json',
            file
          })
        )
      )
    })

    it('disables Import button until a file is uploaded', () => {
      renderWithProviders(
        <CalendarPopover open={true} onClose={mockOnClose} />,
        {
          user: baseUser
        }
      )

      fireEvent.click(screen.getByRole('tab', { name: /Import/i }))

      const importButton = screen.getByRole('button', {
        name: 'actions.import'
      })
      expect(importButton).toBeDisabled()
    })
  })

  it('fetches and resets the secret link', async () => {
    window.DAV_BASE_URL = 'https://cal.example.org'
    ;(getSecretLink as jest.Mock)
      .mockResolvedValueOnce({
        secretLink: 'https://example.org/secret/initial'
      })
      .mockResolvedValueOnce({
        secretLink: 'https://example.org/secret/new'
      })

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    )

    fireEvent.click(screen.getByRole('tab', { name: /Access/i }))

    await waitFor(() =>
      expect(
        screen.getByDisplayValue('https://example.org/secret/initial')
      ).toBeInTheDocument()
    )

    fireEvent.click(screen.getByRole('button', { name: /reset/i }))

    await waitFor(() =>
      expect(
        screen.getByDisplayValue('https://example.org/secret/new')
      ).toBeInTheDocument()
    )

    expect(getSecretLink).toHaveBeenCalledWith(
      existingCalendar.link.replace('.json', ''),
      true
    )
  })
})
