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,
  generationSource: 'openai' as const,
  active: true,
  createdAt: '2026-03-26T10:00:00Z',
}

let persistedHistory: Array<{
  id: string
  taskId: string
  label: string
  content: string
  createdAt: string
}> = []

describe('EditTaskPage', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    persistedHistory = []
    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} | ${task.generationSource}`
    )
    vi.mocked(tasksApi.activateTask).mockResolvedValue({ ...mockTask, active: true })
    vi.mocked(tasksApi.inactivateTask).mockResolvedValue({ ...mockTask, active: false })
    vi.mocked(tasksApi.deleteTask).mockResolvedValue(undefined)
    vi.mocked(tasksApi.getTaskGeneratedMessages).mockImplementation(async () => persistedHistory)
    vi.mocked(tasksApi.deleteTaskGeneratedMessage).mockImplementation(async (_taskId, messageId) => {
      persistedHistory = persistedHistory.filter((message) => message.id !== messageId)
    })
    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(/^Generation Source$/i)).toHaveValue('openai')
    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).mockImplementation(async () => {
      const content = 'SUBJECT: Preview\nBODY:\nFormal nonsense'
      const nextMessage = {
        id: `message-${persistedHistory.length + 1}`,
        taskId: 'task-1',
        label: `Message #${persistedHistory.length + 1}`,
        content,
        createdAt: '2026-03-27T12:00:00Z',
      }
      persistedHistory = [nextMessage, ...persistedHistory]
      return content
    })
    vi.mocked(tasksApi.updateTask).mockResolvedValue({
      ...mockTask,
      name: 'Daily Check-in',
      prompt: 'Ask about ceremonial coffee',
      scheduleCron: '0 8 * * 1-5',
      emailLookback: 'last_day',
      generationSource: 'llama',
    })

    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.change(screen.getByLabelText(/^Generation Source$/i), {
      target: { value: 'llama' },
    })
    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 | llama'
      )
    ).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',
          generationSource: 'llama',
        })
      )
      expect(vi.mocked(tasksApi.generateTaskPreview).mock.calls[0][0]).toEqual('task-1')
      expect(vi.mocked(tasksApi.generateTaskPreview).mock.calls[0][1]).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',
            generationSource: 'llama',
          }),
        })
      )
      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',
        generationSource: 'llama',
      })
      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_renderGeneratedMessagePanels_when_pageLoads', async () => {
    renderPage()

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

    expect(screen.getByText(/Final Prompt/i)).toBeInTheDocument()
    expect(screen.getByText(/^Generated Message$/i)).toBeInTheDocument()
    expect(screen.getByText(/^Generated Message History$/i)).toBeInTheDocument()
    expect(screen.getByText(/Generate a message and it will appear here./i)).toBeInTheDocument()
    expect(screen.getByRole('list', { name: /generated message history/i })).toBeInTheDocument()
    expect(screen.getByText(/No generated messages yet./i)).toBeInTheDocument()
  })

  it('should_showAndManageGeneratedMessageHistory_when_multipleMessagesGenerated', async () => {
    vi.mocked(tasksApi.generateTaskPreview)
      .mockImplementationOnce(async () => {
        const content = 'SUBJECT: First\nBODY:\nFirst output'
        persistedHistory = [
          {
            id: 'message-1',
            taskId: 'task-1',
            label: 'Message #1',
            content,
            createdAt: '2026-03-27T12:00:00Z',
          },
          ...persistedHistory,
        ]
        return content
      })
      .mockImplementationOnce(async () => {
        const content = 'SUBJECT: Second\nBODY:\nSecond output'
        persistedHistory = [
          {
            id: 'message-2',
            taskId: 'task-1',
            label: 'Message #2',
            content,
            createdAt: '2026-03-27T12:10:00Z',
          },
          ...persistedHistory,
        ]
        return content
      })

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

    const generateButton = screen.getByRole('button', { name: /generate test message/i })

    fireEvent.click(generateButton)
    await screen.findByText(/First output/i)

    fireEvent.click(generateButton)

    await waitFor(() => {
      expect(tasksApi.generateTaskPreview).toHaveBeenCalledTimes(2)
    })

    const history = screen.getByRole('list', { name: /generated message history/i })
    expect(history).toBeInTheDocument()

    const firstMessageHistoryItem = screen.getByRole('button', { name: /^message #1$/i })
    const secondMessageHistoryItem = screen.getByRole('button', { name: /^message #2$/i })

    fireEvent.click(secondMessageHistoryItem)
    expect(screen.getByText(/Second output/i)).toBeInTheDocument()

    fireEvent.click(firstMessageHistoryItem)
    expect(screen.getByText(/First output/i)).toBeInTheDocument()

    fireEvent.click(
      screen.getByRole('button', {
        name: /delete message #1/i,
      })
    )

    await waitFor(() => {
      expect(tasksApi.deleteTaskGeneratedMessage).toHaveBeenCalledWith('task-1', 'message-1')
      expect(firstMessageHistoryItem).not.toBeInTheDocument()
      expect(secondMessageHistoryItem).toBeInTheDocument()
    })
  })

  it('should_removeOnlyDeletedGeneratedMessage_when_deleteSucceedsWithoutRefetch', async () => {
    persistedHistory = [
      {
        id: 'message-2',
        taskId: 'task-1',
        label: 'Message #2',
        content: 'SUBJECT: Second\nBODY:\nSecond output',
        createdAt: '2026-03-27T12:10:00Z',
      },
      {
        id: 'message-1',
        taskId: 'task-1',
        label: 'Message #1',
        content: 'SUBJECT: First\nBODY:\nFirst output',
        createdAt: '2026-03-27T12:00:00Z',
      },
    ]
    vi.mocked(tasksApi.getTaskGeneratedMessages).mockResolvedValue(persistedHistory)
    vi.mocked(tasksApi.deleteTaskGeneratedMessage).mockResolvedValue(undefined)

    renderPage()

    const secondMessageHistoryItem = await screen.findByRole('button', { name: /^message #2$/i })
    expect(await screen.findByRole('button', { name: /^message #1$/i })).toBeInTheDocument()

    fireEvent.click(
      screen.getByRole('button', {
        name: /delete message #1/i,
      })
    )

    await waitFor(() => {
      expect(tasksApi.deleteTaskGeneratedMessage).toHaveBeenCalledWith('task-1', 'message-1')
      expect(screen.queryByRole('button', { name: /^message #1$/i })).not.toBeInTheDocument()
      expect(secondMessageHistoryItem).toBeInTheDocument()
      expect(screen.getByText(/Second output/i)).toBeInTheDocument()
    })
  })

  it('should_loadPersistedGeneratedMessageHistory_when_pageLoads', async () => {
    persistedHistory = [
      {
        id: 'message-1',
        taskId: 'task-1',
        label: 'Message #1',
        content: 'SUBJECT: Persisted\nBODY:\nFrom storage',
        createdAt: '2026-03-27T12:00:00Z',
      },
    ]

    renderPage()

    await screen.findByRole('button', { name: /^message #1$/i })
    expect(screen.getByText(/From storage/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')
    })
  })
})