import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { MemoryRouter } from 'react-router-dom'
import { LoginPage } from '../../../features/auth/pages/LoginPage'
import { useAuthStore } from '../../../stores/authStore'

// Must be at top level for Vitest hoisting
const mockNavigate = vi.fn()
vi.mock('react-router-dom', async (importOriginal) => {
  const actual = await importOriginal<typeof import('react-router-dom')>()
  return { ...actual, useNavigate: () => mockNavigate }
})

const API_URL = 'http://localhost:5000'

const mockLoginResponse = {
  accessToken: 'eyJhbGciOiJSUzI1NiJ9.payload.sig',
  refreshToken: 'refresh-token-abc',
  expiresIn: 3600,
  usuario: { id: 1, username: 'admin', nombre: 'Admin', rol: 'admin', permisos: ['administracion:usuarios:gestionar'], mustChangePassword: false },
}

const server = setupServer(
  http.post(`${API_URL}/api/v1/auth/login`, async ({ request }) => {
    const body = await request.json() as { username: string; password: string }
    if (body.username === 'admin' && body.password === '@Diego550@') {
      return HttpResponse.json(mockLoginResponse, { status: 200 })
    }
    return HttpResponse.json({ error: 'Credenciales inválidas' }, { status: 401 })
  }),
)

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => {
  server.resetHandlers()
  // Use clearAuth (sync) to avoid triggering API logout call in tests
  useAuthStore.getState().clearAuth()
  localStorage.clear()
  mockNavigate.mockClear()
})
afterAll(() => server.close())

function renderLoginPage() {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
  })

  return render(
    <QueryClientProvider client={queryClient}>
      <MemoryRouter>
        <LoginPage />
      </MemoryRouter>
    </QueryClientProvider>,
  )
}

describe('LoginPage', () => {
  it('renders username and password inputs and submit button', () => {
    renderLoginPage()

    expect(screen.getByLabelText(/usuario/i)).toBeInTheDocument()
    expect(screen.getByLabelText(/contraseña/i)).toBeInTheDocument()
    expect(screen.getByRole('button', { name: /ingresar/i })).toBeInTheDocument()
  })

  it('shows error message on 401 invalid credentials', async () => {
    const user = userEvent.setup()
    renderLoginPage()

    await user.type(screen.getByLabelText(/usuario/i), 'admin')
    await user.type(screen.getByLabelText(/contraseña/i), 'wrongpassword')
    await user.click(screen.getByRole('button', { name: /ingresar/i }))

    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent(/credenciales/i)
    })
  })

  it('disables submit button while loading', async () => {
    const user = userEvent.setup()
    renderLoginPage()

    server.use(
      http.post(`${API_URL}/api/v1/auth/login`, async () => {
        await new Promise((resolve) => setTimeout(resolve, 300))
        return HttpResponse.json(mockLoginResponse)
      }),
    )

    await user.type(screen.getByLabelText(/usuario/i), 'admin')
    await user.type(screen.getByLabelText(/contraseña/i), '@Diego550@')

    const button = screen.getByRole('button', { name: /ingresar/i })
    await user.click(button)

    // Button should be disabled during the pending request
    expect(button).toBeDisabled()
  })

  it('saves auth to store on successful login', async () => {
    const user = userEvent.setup()
    renderLoginPage()

    await user.type(screen.getByLabelText(/usuario/i), 'admin')
    await user.type(screen.getByLabelText(/contraseña/i), '@Diego550@')
    await user.click(screen.getByRole('button', { name: /ingresar/i }))

    await waitFor(() => {
      const state = useAuthStore.getState()
      expect(state.accessToken).toBe(mockLoginResponse.accessToken)
      expect(state.user?.username).toBe('admin')
    })
  })

  it('setAuth receives expiresIn and store calculates expiresAt correctly', async () => {
    const before = Date.now()
    const user = userEvent.setup()
    renderLoginPage()

    await user.type(screen.getByLabelText(/usuario/i), 'admin')
    await user.type(screen.getByLabelText(/contraseña/i), '@Diego550@')
    await user.click(screen.getByRole('button', { name: /ingresar/i }))

    await waitFor(() => {
      const state = useAuthStore.getState()
      const after = Date.now()

      // refreshToken should be persisted
      expect(state.refreshToken).toBe(mockLoginResponse.refreshToken)

      // expiresAt should be computed as Date.now() + expiresIn * 1000
      // Allow a 2s window for test execution
      expect(state.expiresAt).not.toBeNull()
      expect(state.expiresAt!).toBeGreaterThanOrEqual(before + mockLoginResponse.expiresIn * 1000)
      expect(state.expiresAt!).toBeLessThanOrEqual(after + mockLoginResponse.expiresIn * 1000)
    })
  })
})
