import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react'
import ControlLibraryPage from '../page'

// ============================================================================
// Mock data
// ============================================================================

const MOCK_FRAMEWORK = {
  id: 'fw-1',
  framework_id: 'bp_security_v1',
  name: 'BreakPilot Security',
  version: '1.0',
  description: 'Test framework',
  release_state: 'draft',
}

const MOCK_CONTROL = {
  id: 'ctrl-1',
  framework_id: 'fw-1',
  control_id: 'AUTH-001',
  title: 'Multi-Factor Authentication',
  objective: 'Require MFA for all admin accounts.',
  rationale: 'Passwords alone are insufficient.',
  scope: {},
  requirements: ['MFA for admin'],
  test_procedure: ['Test admin login'],
  evidence: [{ type: 'config', description: 'MFA enabled' }],
  severity: 'high',
  risk_score: 4.0,
  implementation_effort: 'm',
  evidence_confidence: null,
  open_anchors: [{ framework: 'OWASP', ref: 'V2.8', url: 'https://owasp.org' }],
  release_state: 'draft',
  tags: ['mfa'],
  license_rule: 1,
  source_original_text: null,
  source_citation: { source: 'DSGVO' },
  customer_visible: true,
  verification_method: 'automated',
  category: 'authentication',
  target_audience: 'developer',
  generation_metadata: null,
  generation_strategy: 'ungrouped',
  created_at: '2026-03-15T10:00:00+00:00',
  updated_at: '2026-03-15T10:00:00+00:00',
}

const MOCK_META = {
  total: 1,
  domains: [{ domain: 'AUTH', count: 1 }],
  sources: [{ source: 'DSGVO', count: 1 }],
  no_source_count: 0,
}

// ============================================================================
// Fetch mock
// ============================================================================

function createFetchMock(overrides?: Record<string, unknown>) {
  const responses: Record<string, unknown> = {
    frameworks: [MOCK_FRAMEWORK],
    controls: [MOCK_CONTROL],
    'controls-count': { total: 1 },
    'controls-meta': MOCK_META,
    ...overrides,
  }

  return vi.fn((url: string) => {
    const urlStr = typeof url === 'string' ? url : ''
    // Match endpoint param
    const match = urlStr.match(/endpoint=([^&]+)/)
    const endpoint = match?.[1] || ''
    const data = responses[endpoint] ?? []

    return Promise.resolve({
      ok: true,
      status: 200,
      json: () => Promise.resolve(data),
    })
  })
}

// ============================================================================
// Tests
// ============================================================================

describe('ControlLibraryPage', () => {
  let fetchMock: ReturnType<typeof createFetchMock>

  beforeEach(() => {
    fetchMock = createFetchMock()
    global.fetch = fetchMock as unknown as typeof fetch
  })

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

  it('renders the page header', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('Canonical Control Library')).toBeInTheDocument()
    })
  })

  it('shows control count from meta', async () => {
    fetchMock = createFetchMock({ 'controls-meta': { ...MOCK_META, total: 42 } })
    global.fetch = fetchMock as unknown as typeof fetch

    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText(/42 Security Controls/)).toBeInTheDocument()
    })
  })

  it('renders control list with data', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('AUTH-001')).toBeInTheDocument()
      expect(screen.getByText('Multi-Factor Authentication')).toBeInTheDocument()
    })
  })

  it('shows timestamp on control cards', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      // The date should be rendered in German locale format
      expect(screen.getByText(/15\.03\.26/)).toBeInTheDocument()
    })
  })

  it('shows source citation on control cards', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('DSGVO')).toBeInTheDocument()
    })
  })

  it('fetches with limit and offset params', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalled()
    })

    // Find the controls fetch call
    const controlsCalls = fetchMock.mock.calls.filter(
      (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls&')
    )
    expect(controlsCalls.length).toBeGreaterThan(0)

    const url = controlsCalls[0][0] as string
    expect(url).toContain('limit=50')
    expect(url).toContain('offset=0')
  })

  it('fetches controls-count alongside controls', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      const countCalls = fetchMock.mock.calls.filter(
        (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls-count')
      )
      expect(countCalls.length).toBeGreaterThan(0)
    })
  })

  it('fetches controls-meta on mount', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      const metaCalls = fetchMock.mock.calls.filter(
        (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls-meta')
      )
      expect(metaCalls.length).toBeGreaterThan(0)
    })
  })

  it('renders domain dropdown from meta', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('AUTH (1)')).toBeInTheDocument()
    })
  })

  it('renders source dropdown from meta', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      // The source option should appear in the dropdown
      expect(screen.getByText('DSGVO (1)')).toBeInTheDocument()
    })
  })

  it('has sort dropdown with all sort options', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('Sortierung: ID')).toBeInTheDocument()
      expect(screen.getByText('Nach Quelle')).toBeInTheDocument()
      expect(screen.getByText('Neueste zuerst')).toBeInTheDocument()
      expect(screen.getByText('Aelteste zuerst')).toBeInTheDocument()
    })
  })

  it('sends sort params when sorting by newest', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('AUTH-001')).toBeInTheDocument()
    })

    // Clear previous calls
    fetchMock.mockClear()

    // Change sort to newest
    const sortSelect = screen.getByDisplayValue('Sortierung: ID')
    await act(async () => {
      fireEvent.change(sortSelect, { target: { value: 'newest' } })
    })

    await waitFor(() => {
      const controlsCalls = fetchMock.mock.calls.filter(
        (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls&')
      )
      expect(controlsCalls.length).toBeGreaterThan(0)
      const url = controlsCalls[0][0] as string
      expect(url).toContain('sort=created_at')
      expect(url).toContain('order=desc')
    })
  })

  it('sends search param after debounce', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText('AUTH-001')).toBeInTheDocument()
    })

    fetchMock.mockClear()

    const searchInput = screen.getByPlaceholderText(/Controls durchsuchen/)
    await act(async () => {
      fireEvent.change(searchInput, { target: { value: 'encryption' } })
    })

    // Wait for debounce (400ms)
    await waitFor(
      () => {
        const controlsCalls = fetchMock.mock.calls.filter(
          (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('search=encryption')
        )
        expect(controlsCalls.length).toBeGreaterThan(0)
      },
      { timeout: 1000 }
    )
  })

  it('shows empty state when no controls', async () => {
    fetchMock = createFetchMock({
      controls: [],
      'controls-count': { total: 0 },
      'controls-meta': { ...MOCK_META, total: 0 },
    })
    global.fetch = fetchMock as unknown as typeof fetch

    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText(/Noch keine Controls/)).toBeInTheDocument()
    })
  })

  it('shows "Keine Controls gefunden" when filter matches nothing', async () => {
    fetchMock = createFetchMock({
      controls: [],
      'controls-count': { total: 0 },
      'controls-meta': { ...MOCK_META, total: 50 },
    })
    global.fetch = fetchMock as unknown as typeof fetch

    render(<ControlLibraryPage />)

    // Wait for initial load to finish
    await waitFor(() => {
      expect(screen.getByPlaceholderText(/Controls durchsuchen/)).toBeInTheDocument()
    })

    // Trigger a search to have a filter active
    const searchInput = screen.getByPlaceholderText(/Controls durchsuchen/)
    await act(async () => {
      fireEvent.change(searchInput, { target: { value: 'zzzzzzz' } })
    })

    await waitFor(
      () => {
        expect(screen.getByText('Keine Controls gefunden.')).toBeInTheDocument()
      },
      { timeout: 1000 }
    )
  })

  it('has a refresh button', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByTitle('Aktualisieren')).toBeInTheDocument()
    })
  })

  it('renders pagination info', async () => {
    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText(/Seite 1 von 1/)).toBeInTheDocument()
    })
  })

  it('shows pagination buttons for many controls', async () => {
    fetchMock = createFetchMock({
      'controls-count': { total: 150 },
      'controls-meta': { ...MOCK_META, total: 150 },
    })
    global.fetch = fetchMock as unknown as typeof fetch

    render(<ControlLibraryPage />)
    await waitFor(() => {
      expect(screen.getByText(/Seite 1 von 3/)).toBeInTheDocument()
    })
  })
})
