import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { MemoryRouter, Route, Routes } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import EditTaskPage from '@/pages/EditTaskPage'
import * as entitiesApi from '@/api/entitiesApi'
import * as tasksApi from '@/api/tasksApi'

type RenderPageOptions = {
  task?: typeof mockTask
}

const mockNavigate = vi.fn()
vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual('react-router-dom')
  return { ...actual, useNavigate: () => mockNavigate }
})

vi.mock('@/api/entitiesApi')
vi.mock('@/api/tasksApi')

function renderPage(options: RenderPageOptions = {}) {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      <MemoryRouter initialEntries={['/entities/entity-1/tasks/task-1']}>
        <Routes>
          <Route path="/entities/:entityId/tasks/:taskId" element={children} />
        </Routes>
      </MemoryRouter>
    </QueryClientProvider>
  )

  vi.mocked(tasksApi.getTask).mockResolvedValue(options.task ?? mockTask)

  render(<EditTaskPage />, { wrapper })

  return { queryClient }
}

const mockEntity = {
  id: 'entity-1',
  name: 'Entity A',
  email: 'a@a.com',
  jobTitle: 'Ops',
  personality: 'Formal',
  scheduleCron: '0 9 * * 1',
  contextWindowDays: 3,
  active: true,
  createdAt: '',
}

const mockTask = {
  id: 'task-1',
  entityId: 'entity-1',
  name: 'Weekly Check-in',
  prompt: 'Summarize jokes',
  scheduleCron: '0 9 * * 1',
  emailLookback: 'last_week' as const,
  active: true,
  createdAt: '2026-03-26T10:00:00Z',
}

describe('EditTaskPage', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.mocked(entitiesApi.getEntity).mockResolvedValue(mockEntity)
    vi.mocked(tasksApi.getTask).mockResolvedValue(mockTask)
    vi.mocked(tasksApi.buildTaskPreviewPrompt).mockImplementation(
      (entity, task) =>
        `PROMPT FOR ${entity.name}: ${task.name} | ${task.prompt} | ${task.scheduleCron} | ${task.emailLookback}`
    )
    vi.mocked(tasksApi.activateTask).mockResolvedValue({ ...mockTask, active: true })
    vi.mocked(tasksApi.inactivateTask).mockResolvedValue({ ...mockTask, active: false })
    vi.mocked(tasksApi.deleteTask).mockResolvedValue(undefined)
    mockNavigate.mockClear()
  })

  it('should_renderPrefilledTaskForm_when_pageLoads', async () => {
    renderPage()

    await screen.findByRole('link', { name: /back to entity a/i })

    expect(screen.getByRole('heading', { name: /edit task/i })).toBeInTheDocument()
    expect(screen.getByLabelText(/task name/i)).toHaveValue('Weekly Check-in')
    expect(screen.getByLabelText(/task prompt/i)).toHaveValue('Summarize jokes')
    expect(screen.getByLabelText(/^Email Period$/i)).toHaveValue('last_week')
    expect(screen.getByLabelText(/^Minute$/i)).toHaveValue('0')
    expect(screen.getByLabelText(/^Hour$/i)).toHaveValue('9')
    expect(screen.getByLabelText(/^Day of Month$/i)).toHaveValue('*')
    expect(screen.getByLabelText(/^Month$/i)).toHaveValue('*')
    expect(screen.getByLabelText(/^Day of Week$/i)).toHaveValue('1')
  })

  it('should_generatePreviewAndUpdateTask_when_formSubmitted', async () => {
    vi.mocked(tasksApi.generateTaskPreview).mockResolvedValue('SUBJECT: Preview\nBODY:\nFormal nonsense')
    vi.mocked(tasksApi.updateTask).mockResolvedValue({
      ...mockTask,
      name: 'Daily Check-in',
      prompt: 'Ask about ceremonial coffee',
      scheduleCron: '0 8 * * 1-5',
      emailLookback: 'last_day',
    })

    const { queryClient } = renderPage()
    const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries')
    await screen.findByRole('link', { name: /back to entity a/i })

    fireEvent.change(screen.getByLabelText(/task name/i), { target: { value: 'Daily Check-in' } })
    fireEvent.change(screen.getByLabelText(/task prompt/i), {
      target: { value: 'Ask about ceremonial coffee' },
    })
    fireEvent.change(screen.getByLabelText(/^Email Period$/i), {
      target: { value: 'last_day' },
    })
    fireEvent.click(screen.getByRole('button', { name: /Weekdays/i }))
    fireEvent.change(screen.getByLabelText(/^Hour$/i), { target: { value: '8' } })

    expect(screen.getByText(/Final Prompt/i)).toBeInTheDocument()
    expect(
      screen.getByText(
        'PROMPT FOR Entity A: Daily Check-in | Ask about ceremonial coffee | 0 8 * * 1-5 | last_day'
      )
    ).toBeInTheDocument()

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

    await waitFor(() => {
      expect(tasksApi.generateTaskPreview).toHaveBeenCalled()
      expect(vi.mocked(tasksApi.buildTaskPreviewPrompt)).toHaveBeenCalledWith(
        mockEntity,
        expect.objectContaining({
          entityId: 'entity-1',
          name: 'Daily Check-in',
          prompt: 'Ask about ceremonial coffee',
          scheduleCron: '0 8 * * 1-5',
          emailLookback: 'last_day',
        })
      )
      expect(vi.mocked(tasksApi.generateTaskPreview).mock.calls[0][0]).toEqual(
        expect.objectContaining({
          entity: mockEntity,
          task: expect.objectContaining({
            entityId: 'entity-1',
            name: 'Daily Check-in',
            prompt: 'Ask about ceremonial coffee',
            scheduleCron: '0 8 * * 1-5',
            emailLookback: 'last_day',
          }),
        })
      )
      expect(screen.getByText(/Formal nonsense/i)).toBeInTheDocument()
    })

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

    await waitFor(() => {
      expect(tasksApi.updateTask).toHaveBeenCalledWith('task-1', {
        entityId: 'entity-1',
        name: 'Daily Check-in',
        prompt: 'Ask about ceremonial coffee',
        scheduleCron: '0 8 * * 1-5',
        emailLookback: 'last_day',
      })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks', 'entity-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-task', 'task-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks'] })
      expect(mockNavigate).toHaveBeenCalledWith('/entities/entity-1')
    })
  })

  it('should_showReadablePreviewError_when_generationFails', async () => {
    vi.mocked(tasksApi.generateTaskPreview).mockRejectedValue(
      new Error('Unable to generate a test message from the local model. Connection refused')
    )

    renderPage()
    await screen.findByRole('link', { name: /back to entity a/i })

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

    await waitFor(() => {
      expect(
        screen.getByText(/Unable to generate a test message from the local model. Connection refused/i)
      ).toBeInTheDocument()
    })
  })

  it('should_renderActivateButton_when_taskIsInactive', async () => {
    renderPage({ task: { ...mockTask, active: false } })

    const activateButton = await screen.findByRole('button', { name: /activate/i })

    expect(activateButton).toHaveClass('border-emerald-700')
    expect(activateButton).toHaveClass('text-emerald-200')
    expect(screen.queryByRole('button', { name: /^inactivate$/i })).not.toBeInTheDocument()
  })

  it('should_inactivateTaskAndNavigateBack_when_inactivateClicked', async () => {
    const { queryClient } = renderPage()
    const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries')

    const inactivateButton = await screen.findByRole('button', { name: /^inactivate$/i })

    expect(inactivateButton).toHaveClass('border-amber-700')
    expect(inactivateButton).toHaveClass('text-amber-200')

    fireEvent.click(inactivateButton)

    await waitFor(() => {
      expect(tasksApi.inactivateTask).toHaveBeenCalledWith('task-1')
      expect(tasksApi.activateTask).not.toHaveBeenCalled()
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks', 'entity-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-task', 'task-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks'] })
      expect(mockNavigate).toHaveBeenCalledWith('/entities/entity-1')
    })
  })

  it('should_activateTaskAndNavigateBack_when_activateClicked', async () => {
    const { queryClient } = renderPage({ task: { ...mockTask, active: false } })
    const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries')

    const activateButton = await screen.findByRole('button', { name: /^activate$/i })
    fireEvent.click(activateButton)

    await waitFor(() => {
      expect(tasksApi.activateTask).toHaveBeenCalledWith('task-1')
      expect(tasksApi.inactivateTask).not.toHaveBeenCalled()
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks', 'entity-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-task', 'task-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks'] })
      expect(mockNavigate).toHaveBeenCalledWith('/entities/entity-1')
    })
  })

  it('should_deleteTaskAndNavigateBack_when_deleteClicked', async () => {
    const { queryClient } = renderPage()
    const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries')

    await screen.findByRole('link', { name: /back to entity a/i })
    fireEvent.click(screen.getByRole('button', { name: /delete/i }))

    await waitFor(() => {
      expect(tasksApi.deleteTask).toHaveBeenCalledWith('task-1')
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks', 'entity-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-task', 'task-1'] })
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks'] })
      expect(mockNavigate).toHaveBeenCalledWith('/entities/entity-1')
    })
  })
})