/**
 * Tests for TotpSettings (M4-T08).
 *
 * Strategy: vi.mock the auth/totp module so we can control API responses
 * without a real server. We also mock the api/client module for ApiError.
 *
 * Coverage:
 * 1. Shows loading spinner while fetching TOTP status.
 * 2. Shows error when TOTP status fetch fails.
 * 3. Shows "Disabled" badge and enable button when TOTP is disabled.
 * 4. Enable flow: clicking setup → shows QR code + recovery codes + confirm form.
 * 5. Confirm with correct code → TOTP enabled (status refetched).
 * 6. Confirm with wrong code → shows error, stays on confirm form.
 * 7. Shows "Enabled" badge and disable form when TOTP is enabled.
 * 8. Disable with password → TOTP disabled.
 * 9. Disable with TOTP code → TOTP disabled.
 * 10. Disable without providing password or code → shows validation error.
 * 11. Setup error shows error message.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { screen, waitFor, fireEvent } from '@testing-library/react'
import { renderWithProviders } from '../test-utils'
import { TotpSettings } from './TotpSettings'

// ---------------------------------------------------------------------------
// Mock auth/totp
// ---------------------------------------------------------------------------

const mockGetTotpStatus = vi.fn()
const mockSetupTotp = vi.fn()
const mockEnableTotp = vi.fn()
const mockDisableTotp = vi.fn()

vi.mock('../auth/totp', () => ({
  getTotpStatus: (...args: unknown[]) => mockGetTotpStatus(...args),
  setupTotp: (...args: unknown[]) => mockSetupTotp(...args),
  enableTotp: (...args: unknown[]) => mockEnableTotp(...args),
  disableTotp: (...args: unknown[]) => mockDisableTotp(...args),
}))

// ---------------------------------------------------------------------------
// Mock api/client (for ApiError)
// ---------------------------------------------------------------------------

vi.mock('../api/client', () => ({
  default: {
    GET: vi.fn(),
    POST: vi.fn(),
    PUT: vi.fn(),
  },
  ApiError: class ApiError extends Error {
    status: number
    body: unknown
    constructor(status: number, body: unknown) {
      super(`API error ${status}`)
      this.name = 'ApiError'
      this.status = status
      this.body = body
    }
  },
  registerLoginRedirect: vi.fn(),
}))

// ---------------------------------------------------------------------------
// Fixture
// ---------------------------------------------------------------------------

const MOCK_SETUP_DATA = {
  secret: 'JBSWY3DPEHPK3PXP',
  otpauth_uri: 'otpauth://totp/TestApp:admin?secret=JBSWY3DPEHPK3PXP&issuer=TestApp',
  recovery_codes: ['abcd-1234', 'efgh-5678', 'ijkl-9012'],
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function renderTotp() {
  return renderWithProviders(<TotpSettings />, { initialPath: '/config' })
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('TotpSettings', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  // -------------------------------------------------------------------------
  // 1. Loading state
  // -------------------------------------------------------------------------

  it('shows loading spinner while fetching TOTP status', async () => {
    // Never resolves during this test
    mockGetTotpStatus.mockReturnValue(new Promise(() => {}))
    renderTotp()
    await waitFor(() => {
      expect(screen.getByTestId('totp-status-loading')).toBeInTheDocument()
    })
  })

  // -------------------------------------------------------------------------
  // 2. Error state
  // -------------------------------------------------------------------------

  it('shows error when TOTP status fetch fails', async () => {
    mockGetTotpStatus.mockRejectedValue(new Error('Network error'))
    renderTotp()
    await waitFor(() => {
      expect(screen.getByTestId('totp-status-error')).toBeInTheDocument()
    })
  })

  // -------------------------------------------------------------------------
  // 3. Disabled state
  // -------------------------------------------------------------------------

  it('shows Disabled badge and enable button when TOTP is disabled', async () => {
    mockGetTotpStatus.mockResolvedValue({ enabled: false })
    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-status-badge')).toBeInTheDocument()
    })

    expect(screen.getByTestId('totp-status-badge')).toHaveTextContent(/disabled/i)
    expect(screen.getByTestId('totp-setup-button')).toBeInTheDocument()
  })

  // -------------------------------------------------------------------------
  // 4. Enable flow: setup shows QR + recovery codes + confirm form
  // -------------------------------------------------------------------------

  it('shows QR code, secret, and recovery codes after clicking setup', async () => {
    mockGetTotpStatus.mockResolvedValue({ enabled: false })
    mockSetupTotp.mockResolvedValue(MOCK_SETUP_DATA)

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-setup-button')).toBeInTheDocument()
    })

    fireEvent.click(screen.getByTestId('totp-setup-button'))

    await waitFor(() => {
      expect(screen.getByTestId('totp-qr-code')).toBeInTheDocument()
    })

    expect(screen.getByTestId('totp-secret')).toHaveTextContent('JBSWY3DPEHPK3PXP')
    expect(screen.getByTestId('totp-recovery-codes')).toBeInTheDocument()
    expect(screen.getByTestId('totp-recovery-codes')).toHaveTextContent('abcd-1234')
    expect(screen.getByTestId('totp-confirm-form')).toBeInTheDocument()
    expect(screen.getByTestId('totp-confirm-input')).toBeInTheDocument()
  })

  // -------------------------------------------------------------------------
  // 5. Confirm with correct code → enabled
  // -------------------------------------------------------------------------

  it('enables TOTP after confirming with correct code', async () => {
    mockGetTotpStatus
      .mockResolvedValueOnce({ enabled: false })
      .mockResolvedValueOnce({ enabled: true })
    mockSetupTotp.mockResolvedValue(MOCK_SETUP_DATA)
    mockEnableTotp.mockResolvedValue(undefined)

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-setup-button')).toBeInTheDocument()
    })

    fireEvent.click(screen.getByTestId('totp-setup-button'))

    await waitFor(() => {
      expect(screen.getByTestId('totp-confirm-form')).toBeInTheDocument()
    })

    fireEvent.change(screen.getByTestId('totp-confirm-input'), { target: { value: '123456' } })
    fireEvent.submit(screen.getByTestId('totp-confirm-form'))

    await waitFor(() => {
      expect(mockEnableTotp).toHaveBeenCalledWith('123456')
    })

    // After enable the status refetches → shows enabled
    await waitFor(() => {
      expect(screen.getByTestId('totp-status-badge')).toHaveTextContent(/enabled/i)
    })
  })

  // -------------------------------------------------------------------------
  // 6. Confirm with wrong code → error
  // -------------------------------------------------------------------------

  it('shows error on wrong TOTP code during enable', async () => {
    mockGetTotpStatus.mockResolvedValue({ enabled: false })
    mockSetupTotp.mockResolvedValue(MOCK_SETUP_DATA)

    const { ApiError } = await import('../api/client')
    mockEnableTotp.mockRejectedValue(new ApiError(400, { detail: 'invalid code' }))

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-setup-button')).toBeInTheDocument()
    })

    fireEvent.click(screen.getByTestId('totp-setup-button'))

    await waitFor(() => {
      expect(screen.getByTestId('totp-confirm-form')).toBeInTheDocument()
    })

    fireEvent.change(screen.getByTestId('totp-confirm-input'), { target: { value: '000000' } })
    fireEvent.submit(screen.getByTestId('totp-confirm-form'))

    await waitFor(() => {
      expect(screen.getByTestId('totp-confirm-error')).toBeInTheDocument()
    })

    // Should still show confirm form (not enabled)
    expect(screen.getByTestId('totp-confirm-form')).toBeInTheDocument()
  })

  // -------------------------------------------------------------------------
  // 7. Enabled state
  // -------------------------------------------------------------------------

  it('shows Enabled badge and disable form when TOTP is enabled', async () => {
    mockGetTotpStatus.mockResolvedValue({ enabled: true })
    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-status-badge')).toBeInTheDocument()
    })

    expect(screen.getByTestId('totp-status-badge')).toHaveTextContent(/enabled/i)
    expect(screen.getByTestId('totp-disable-form')).toBeInTheDocument()
    expect(screen.getByTestId('totp-disable-button')).toBeInTheDocument()
  })

  // -------------------------------------------------------------------------
  // 8. Disable with password
  // -------------------------------------------------------------------------

  it('disables TOTP when correct password is provided', async () => {
    mockGetTotpStatus
      .mockResolvedValueOnce({ enabled: true })
      .mockResolvedValueOnce({ enabled: false })
    mockDisableTotp.mockResolvedValue(undefined)

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-disable-form')).toBeInTheDocument()
    })

    fireEvent.change(screen.getByTestId('totp-disable-password'), {
      target: { value: 'my-password' },
    })
    fireEvent.submit(screen.getByTestId('totp-disable-form'))

    await waitFor(() => {
      expect(mockDisableTotp).toHaveBeenCalledWith({ password: 'my-password', code: null })
    })

    await waitFor(() => {
      expect(screen.getByTestId('totp-status-badge')).toHaveTextContent(/disabled/i)
    })
  })

  // -------------------------------------------------------------------------
  // 9. Disable with TOTP code
  // -------------------------------------------------------------------------

  it('disables TOTP when correct TOTP code is provided', async () => {
    mockGetTotpStatus
      .mockResolvedValueOnce({ enabled: true })
      .mockResolvedValueOnce({ enabled: false })
    mockDisableTotp.mockResolvedValue(undefined)

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-disable-form')).toBeInTheDocument()
    })

    fireEvent.change(screen.getByTestId('totp-disable-code'), { target: { value: '654321' } })
    fireEvent.submit(screen.getByTestId('totp-disable-form'))

    await waitFor(() => {
      expect(mockDisableTotp).toHaveBeenCalledWith({ password: null, code: '654321' })
    })
  })

  // -------------------------------------------------------------------------
  // 10. Disable without credentials → validation error
  // -------------------------------------------------------------------------

  it('shows validation error when disable submitted without password or code', async () => {
    mockGetTotpStatus.mockResolvedValue({ enabled: true })

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-disable-form')).toBeInTheDocument()
    })

    // Submit without filling in anything
    fireEvent.submit(screen.getByTestId('totp-disable-form'))

    await waitFor(() => {
      expect(screen.getByTestId('totp-disable-error')).toBeInTheDocument()
    })

    expect(mockDisableTotp).not.toHaveBeenCalled()
  })

  // -------------------------------------------------------------------------
  // 11. Setup error
  // -------------------------------------------------------------------------

  it('shows error when TOTP setup request fails', async () => {
    mockGetTotpStatus.mockResolvedValue({ enabled: false })
    mockSetupTotp.mockRejectedValue(new Error('Network error'))

    renderTotp()

    await waitFor(() => {
      expect(screen.getByTestId('totp-setup-button')).toBeInTheDocument()
    })

    fireEvent.click(screen.getByTestId('totp-setup-button'))

    await waitFor(() => {
      expect(screen.getByTestId('totp-enable-error')).toBeInTheDocument()
    })
  })
})
