import i18n from '@renderer/i18n'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'

const toastError = vi.fn()

vi.mock('@logger', () => ({
  loggerService: {
    withContext: () => ({
      error: vi.fn()
    })
  }
}))

vi.mock('@cherrystudio/ui', () => {
  const React = require('react')

  return {
    Button: ({ children, loadingIcon, loading, ...props }) =>
      React.createElement('button', props, loading ? loadingIcon : null, children),
    Dialog: ({ children, open }) => (open ? React.createElement(React.Fragment, null, children) : null),
    DialogContent: ({ children, ...props }) => {
      delete props.showCloseButton
      delete props.onInteractOutside
      delete props.overlayClassName

      return React.createElement('div', { role: 'dialog', ...props }, children)
    },
    DialogDescription: ({ children }) => React.createElement('div', null, children),
    DialogFooter: ({ children, ...props }) => React.createElement('div', props, children),
    DialogHeader: ({ children, ...props }) => React.createElement('div', props, children),
    DialogTitle: ({ children, ...props }) => React.createElement('h2', props, children)
  }
})

import AppModalProvider, { type AppModalApi } from '..'

beforeEach(() => {
  toastError.mockClear()
  Object.defineProperty(window, 'toast', {
    configurable: true,
    value: { error: toastError }
  })
})

async function renderModalProvider() {
  let modal: AppModalApi | undefined

  render(<AppModalProvider onReady={(api) => (modal = api)} />)

  await waitFor(() => {
    expect(modal).toBeDefined()
  })

  return modal!
}

describe('AppModalProvider', () => {
  it('keeps window.modal.confirm compatible with promise-style confirmation', async () => {
    const user = userEvent.setup()
    const modal = await renderModalProvider()

    let confirmed: ReturnType<AppModalApi['confirm']>
    act(() => {
      confirmed = modal.confirm({
        title: 'Delete item',
        content: 'This cannot be undone.',
        okText: 'Delete',
        cancelText: 'Cancel'
      })
    })

    await waitFor(() => {
      expect(screen.getByText('Delete item')).toBeInTheDocument()
    })
    expect(screen.getByText('This cannot be undone.')).toBeInTheDocument()
    expect(typeof confirmed!.catch).toBe('function')
    expect(typeof confirmed!.finally).toBe('function')

    await user.click(screen.getByRole('button', { name: 'Delete' }))

    await expect(confirmed!).resolves.toBe(true)
  })

  it('keeps the modal open when onOk rejects so it can still be cancelled', async () => {
    const user = userEvent.setup()
    const modal = await renderModalProvider()
    const onOk = vi.fn().mockRejectedValue(new Error('failed'))

    let confirmed: ReturnType<AppModalApi['confirm']>
    act(() => {
      confirmed = modal.confirm({
        title: 'Retry action',
        content: 'The first attempt fails.',
        okText: 'Run',
        cancelText: 'Cancel',
        onOk
      })
    })

    await user.click(await screen.findByRole('button', { name: 'Run' }))

    await waitFor(() => {
      expect(onOk).toHaveBeenCalledOnce()
    })
    expect(toastError).toHaveBeenCalledWith({ title: i18n.t('common.error'), description: 'failed' })
    expect(screen.getByText('Retry action')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: 'Cancel' }))

    await expect(confirmed!).resolves.toBe(false)
  })

  it('resolves confirm as false when cancelled', async () => {
    const user = userEvent.setup()
    const modal = await renderModalProvider()

    let confirmed: ReturnType<AppModalApi['confirm']>
    act(() => {
      confirmed = modal.confirm({
        title: 'Leave page',
        content: 'Unsaved changes will be lost.',
        okText: 'Leave',
        cancelText: 'Stay'
      })
    })

    await user.click(await screen.findByRole('button', { name: 'Stay' }))

    await expect(confirmed!).resolves.toBe(false)
  })

  it('renders feedback modals without a cancel button', async () => {
    const user = userEvent.setup()
    const modal = await renderModalProvider()

    let confirmed: ReturnType<AppModalApi['error']>
    act(() => {
      confirmed = modal.error({
        title: 'Backup failed',
        content: 'Disk is full.'
      })
    })

    await waitFor(() => {
      expect(screen.getByText('Backup failed')).toBeInTheDocument()
    })
    expect(screen.queryByRole('button', { name: i18n.t('common.cancel') })).not.toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: i18n.t('common.confirm') }))

    await expect(confirmed!).resolves.toBe(true)
  })

  it('constrains long plain-text feedback content inside the modal', async () => {
    const modal = await renderModalProvider()
    const longError =
      'Error invoking remote method ai:generate-image: AI_RetryError: Failed after 3 attempts. Last error: request failed: ' +
      'https://example.com/' +
      'a'.repeat(180)

    act(() => {
      modal.error({
        title: 'Generate failed',
        content: longError
      })
    })

    const content = await screen.findByText(longError)

    expect(content).toHaveClass('min-w-0', 'max-w-full', 'wrap-anywhere')
  })

  it('uses translated default text for destructive confirmations', async () => {
    const user = userEvent.setup()
    const modal = await renderModalProvider()

    let confirmed: ReturnType<AppModalApi['confirm']>
    act(() => {
      confirmed = modal.confirm({
        title: 'Delete item',
        content: 'This cannot be undone.',
        okButtonProps: { danger: true }
      })
    })

    await user.click(await screen.findByRole('button', { name: i18n.t('common.delete') }))

    await expect(confirmed!).resolves.toBe(true)
  })

  it('supports update and destroy handles for loading-style modals', async () => {
    const modal = await renderModalProvider()

    let loadingModal: ReturnType<AppModalApi['info']>
    act(() => {
      loadingModal = modal.info({
        title: 'Migrating data',
        content: 'Starting...',
        okButtonProps: { style: { display: 'none' } }
      })
    })

    await waitFor(() => {
      expect(screen.getByText('Starting...')).toBeInTheDocument()
    })
    expect(screen.queryByRole('button', { name: i18n.t('common.confirm') })).not.toBeInTheDocument()

    act(() => {
      loadingModal!.update({ content: 'Almost done.' })
    })

    await waitFor(() => {
      expect(screen.getByText('Almost done.')).toBeInTheDocument()
    })

    act(() => {
      loadingModal!.destroy()
    })

    await expect(loadingModal!).resolves.toBe(false)

    await waitFor(() => {
      expect(screen.queryByText('Almost done.')).not.toBeInTheDocument()
    })
  })

  it('destroyAll resolves every open modal as cancelled', async () => {
    const modal = await renderModalProvider()

    let first: ReturnType<AppModalApi['info']>
    let second: ReturnType<AppModalApi['confirm']>
    act(() => {
      first = modal.info({ title: 'First modal' })
      second = modal.confirm({ title: 'Second modal' })
    })

    await waitFor(() => {
      expect(screen.getByText('First modal')).toBeInTheDocument()
      expect(screen.getByText('Second modal')).toBeInTheDocument()
    })

    act(() => {
      modal.destroyAll()
    })

    await expect(first!).resolves.toBe(false)
    await expect(second!).resolves.toBe(false)
  })

  it('keeps the modal mounted until the close animation finishes', async () => {
    const modal = await renderModalProvider()
    vi.useFakeTimers()
    try {
      const afterClose = vi.fn()

      let loadingModal: ReturnType<AppModalApi['info']>
      act(() => {
        loadingModal = modal.info({
          title: 'Closing soon',
          content: 'Waiting for animation.',
          afterClose
        })
      })

      expect(screen.getByText('Closing soon')).toBeInTheDocument()

      act(() => {
        loadingModal!.destroy()
      })

      expect(afterClose).not.toHaveBeenCalled()

      act(() => {
        vi.advanceTimersByTime(199)
      })
      expect(afterClose).not.toHaveBeenCalled()

      act(() => {
        vi.advanceTimersByTime(1)
      })
      expect(afterClose).toHaveBeenCalledTimes(1)
    } finally {
      vi.useRealTimers()
    }
  })
})
