import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { afterEach, beforeEach, expect, test, vi } from 'vitest'
import App from './App'

const users = [
  {
    id: 'd56b54dd-31b0-4d71-8a10-82464be59a61',
    name: 'Urban',
    createdAt: '2026-07-24T10:15:30Z',
  },
  {
    id: '2213a673-c859-462b-a031-7b68b6f01c73',
    name: 'Anna',
    createdAt: '2026-07-24T10:16:30Z',
  },
]

const tasks = [
  {
    id: '00000000-0000-0000-0000-000000000001',
    title: 'Dammsuga',
    description: 'Bottenvåningen',
    status: 'WAITING',
    createdAt: '2026-07-24T10:00:00Z',
  },
  {
    id: '00000000-0000-0000-0000-000000000002',
    title: 'Diska',
    description: null,
    status: 'IN_PROGRESS',
    createdAt: '2026-07-24T10:01:00Z',
  },
  {
    id: '00000000-0000-0000-0000-000000000003',
    title: 'Vattna blommor',
    description: null,
    status: 'COMPLETED',
    createdAt: '2026-07-24T10:02:00Z',
  },
]

beforeEach(() => {
  window.localStorage.clear()
})

afterEach(() => {
  cleanup()
  vi.restoreAllMocks()
})

test('tom användarlista visar Skapa användare', async () => {
  mockJsonResponse([])

  render(<App />)

  expect(await screen.findByRole('heading', { name: 'Skapa användare' })).toBeInTheDocument()
})

test('befintliga användare utan lokalt val visar Vem är du', async () => {
  mockJsonResponse(users)

  render(<App />)

  expect(await screen.findByRole('heading', { name: 'Vem är du?' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Urban' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Anna' })).toBeInTheDocument()
})

test('val av användare lagrar id och visar brädan', async () => {
  mockUsersAndTasks(users, [])
  render(<App />)

  fireEvent.click(await screen.findByRole('button', { name: 'Urban' }))

  expect(await screen.findByRole('heading', { name: 'Uppgifter' })).toBeInTheDocument()
  expect(screen.getByText('Urban')).toBeInTheDocument()
  expect(window.localStorage.getItem('hemhub.activeUserId')).toBe(users[0].id)
})

test('skapad användare blir automatiskt aktiv', async () => {
  const createdUser = users[0]
  const fetchMock = vi.spyOn(globalThis, 'fetch')
  fetchMock.mockResolvedValueOnce(jsonResponse([]))
  fetchMock.mockResolvedValueOnce(jsonResponse(createdUser, 201))
  fetchMock.mockResolvedValueOnce(jsonResponse([]))

  render(<App />)

  fireEvent.change(await screen.findByLabelText('Namn'), { target: { value: '  Urban  ' } })
  fireEvent.click(screen.getByRole('button', { name: 'Skapa användare' }))

  expect(await screen.findByRole('heading', { name: 'Uppgifter' })).toBeInTheDocument()
  expect(window.localStorage.getItem('hemhub.activeUserId')).toBe(createdUser.id)
  expect(fetchMock).toHaveBeenNthCalledWith(2, '/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'Urban' }),
  })
})

test('skapandefel för användare visas utan att namnet försvinner', async () => {
  const fetchMock = vi.spyOn(globalThis, 'fetch')
  fetchMock.mockResolvedValueOnce(jsonResponse([]))
  fetchMock.mockResolvedValueOnce(
    jsonResponse(
      {
        code: 'USER_NAME_ALREADY_EXISTS',
        message: 'En användare med det namnet finns redan.',
      },
      409,
    ),
  )

  render(<App />)

  const input = await screen.findByLabelText('Namn')
  fireEvent.change(input, { target: { value: 'Urban' } })
  fireEvent.click(screen.getByRole('button', { name: 'Skapa användare' }))

  expect(await screen.findByRole('alert')).toHaveTextContent(
    'En användare med det namnet finns redan.',
  )
  expect(input).toHaveValue('Urban')
})

test('Logga ut rensar valt id och väljer inte användaren automatiskt', async () => {
  window.localStorage.setItem('hemhub.activeUserId', users[0].id)
  mockUsersAndTasks(users, [])
  render(<App />)

  fireEvent.click(await screen.findByRole('button', { name: 'Logga ut' }))

  expect(screen.getByRole('heading', { name: 'Vem är du?' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Urban' })).toBeInTheDocument()
  expect(screen.queryByRole('heading', { name: 'Uppgifter' })).not.toBeInTheDocument()
  expect(window.localStorage.getItem('hemhub.activeUserId')).toBeNull()
})

test('hämtningsfel för användare visas som fel och kan återförsökas', async () => {
  const fetchMock = vi.spyOn(globalThis, 'fetch')
  fetchMock.mockRejectedValueOnce(new Error('Nätverksfel'))
  fetchMock.mockResolvedValueOnce(jsonResponse(users))
  render(<App />)

  expect(await screen.findByText('Kunde inte ansluta till HemHub.')).toBeInTheDocument()
  expect(screen.queryByRole('heading', { name: 'Skapa användare' })).not.toBeInTheDocument()

  fireEvent.click(screen.getByRole('button', { name: 'Försök igen' }))

  expect(await screen.findByRole('heading', { name: 'Vem är du?' })).toBeInTheDocument()
  expect(fetchMock).toHaveBeenCalledTimes(2)
})

test('ogiltigt lagrat användar-id rensas', async () => {
  window.localStorage.setItem('hemhub.activeUserId', 'finns-inte')
  mockJsonResponse(users)

  render(<App />)

  expect(await screen.findByRole('heading', { name: 'Vem är du?' })).toBeInTheDocument()
  expect(window.localStorage.getItem('hemhub.activeUserId')).toBeNull()
})

test('brädan visar tre kolumner och grupperar hämtade uppgifter', async () => {
  window.localStorage.setItem('hemhub.activeUserId', users[0].id)
  mockUsersAndTasks(users, tasks)

  render(<App />)

  const waiting = await screen.findByRole('region', { name: 'Väntande' })
  const inProgress = screen.getByRole('region', { name: 'Pågående' })
  const completed = screen.getByRole('region', { name: 'Klart' })

  expect(within(waiting).getByText('Dammsuga')).toBeInTheDocument()
  expect(within(waiting).getByText('Bottenvåningen')).toBeInTheDocument()
  expect(within(inProgress).getByText('Diska')).toBeInTheDocument()
  expect(within(completed).getByText('Vattna blommor')).toBeInTheDocument()
  expect(screen.queryByText(/Inga uppgifter/i)).not.toBeInTheDocument()
})

test('Ny uppgift öppnar modalen med fokus i titelfältet', async () => {
  window.localStorage.setItem('hemhub.activeUserId', users[0].id)
  mockUsersAndTasks(users, [])
  render(<App />)

  fireEvent.click(await screen.findByRole('button', { name: 'Ny uppgift' }))

  expect(screen.getByRole('dialog', { name: 'Skapa ny uppgift' })).toBeInTheDocument()
  expect(screen.getByLabelText('Titel')).toHaveFocus()
})

test.each([
  {
    sätt: 'stängningskrysset',
    close: () => fireEvent.click(screen.getByRole('button', { name: 'Stäng' })),
  },
  {
    sätt: 'Escape',
    close: () => fireEvent.keyDown(window, { key: 'Escape' }),
  },
  {
    sätt: 'modalens bakgrund',
    close: () => {
      const backdrop = screen.getByRole('dialog', { name: 'Skapa ny uppgift' }).parentElement

      if (!backdrop) {
        throw new Error('Modalens bakgrund saknas')
      }

      fireEvent.mouseDown(backdrop)
    },
  },
])('modalen stängs med $sätt och rensar formuläret', async ({ close }) => {
  window.localStorage.setItem('hemhub.activeUserId', users[0].id)
  mockUsersAndTasks(users, [])
  render(<App />)

  fireEvent.click(await screen.findByRole('button', { name: 'Ny uppgift' }))
  fireEvent.change(screen.getByLabelText('Titel'), { target: { value: 'Dammsuga' } })
  fireEvent.change(screen.getByLabelText('Beskrivning (valfri)'), {
    target: { value: 'Bottenvåningen' },
  })

  close()

  expect(screen.queryByRole('dialog', { name: 'Skapa ny uppgift' })).not.toBeInTheDocument()

  fireEvent.click(screen.getByRole('button', { name: 'Ny uppgift' }))

  expect(screen.getByLabelText('Titel')).toHaveValue('')
  expect(screen.getByLabelText('Beskrivning (valfri)')).toHaveValue('')
})

test('en skapad uppgift visas längst ned i Väntande och modalen stängs', async () => {
  const createdTask = {
    id: '00000000-0000-0000-0000-000000000004',
    title: 'Putsa fönster',
    description: 'Köket',
    status: 'WAITING',
    createdAt: '2026-07-24T10:03:00Z',
  }
  window.localStorage.setItem('hemhub.activeUserId', users[0].id)
  const fetchMock = vi.spyOn(globalThis, 'fetch')
  fetchMock.mockResolvedValueOnce(jsonResponse(users))
  fetchMock.mockResolvedValueOnce(jsonResponse([tasks[0]]))
  fetchMock.mockResolvedValueOnce(jsonResponse(createdTask, 201))

  render(<App />)

  await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2))
  fireEvent.click(screen.getByRole('button', { name: 'Ny uppgift' }))
  fireEvent.change(screen.getByLabelText('Titel'), { target: { value: '  Putsa fönster  ' } })
  fireEvent.change(screen.getByLabelText('Beskrivning (valfri)'), {
    target: { value: '  Köket  ' },
  })
  fireEvent.click(screen.getByRole('button', { name: 'Skapa uppgift' }))

  await waitFor(() =>
    expect(screen.queryByRole('dialog', { name: 'Skapa ny uppgift' })).not.toBeInTheDocument(),
  )
  const waiting = screen.getByRole('region', { name: 'Väntande' })
  expect(within(waiting).getAllByRole('article').map((card) => card.textContent)).toEqual([
    'DammsugaBottenvåningen',
    'Putsa fönsterKöket',
  ])
  expect(fetchMock).toHaveBeenLastCalledWith('/api/tasks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: 'Putsa fönster', description: 'Köket' }),
  })
})

test('formulärdata bevaras när skapande av uppgift misslyckas', async () => {
  window.localStorage.setItem('hemhub.activeUserId', users[0].id)
  const fetchMock = vi.spyOn(globalThis, 'fetch')
  fetchMock.mockResolvedValueOnce(jsonResponse(users))
  fetchMock.mockResolvedValueOnce(jsonResponse([]))
  fetchMock.mockResolvedValueOnce(
    jsonResponse({ code: 'INVALID_TASK', message: 'Uppgiften är ogiltig.' }, 400),
  )

  render(<App />)

  await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2))
  fireEvent.click(screen.getByRole('button', { name: 'Ny uppgift' }))
  const title = screen.getByLabelText('Titel')
  const description = screen.getByLabelText('Beskrivning (valfri)')
  fireEvent.change(title, { target: { value: 'Dammsuga' } })
  fireEvent.change(description, { target: { value: 'Bottenvåningen' } })
  fireEvent.click(screen.getByRole('button', { name: 'Skapa uppgift' }))

  expect(await screen.findByRole('alert')).toHaveTextContent('Uppgiften är ogiltig.')
  expect(screen.getByRole('dialog', { name: 'Skapa ny uppgift' })).toBeInTheDocument()
  expect(title).toHaveValue('Dammsuga')
  expect(description).toHaveValue('Bottenvåningen')
})

function mockUsersAndTasks(userResponse: unknown, taskResponse: unknown) {
  const fetchMock = vi.spyOn(globalThis, 'fetch')
  fetchMock.mockResolvedValueOnce(jsonResponse(userResponse))
  fetchMock.mockResolvedValueOnce(jsonResponse(taskResponse))
  return fetchMock
}

function mockJsonResponse(body: unknown, status = 200) {
  return vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse(body, status))
}

function jsonResponse(body: unknown, status = 200) {
  return new Response(JSON.stringify(body), {
    status,
    headers: { 'Content-Type': 'application/json' },
  })
}
