import { describe, it, expect, vi, beforeEach } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
  useSecurityStatus,
  useSecurityConfig,
  useUpdateSecurityConfig,
  useGenerateBreakGlassToken,
  useDecisions,
  useCreateDecision,
  useRuleSets,
  useUpsertRuleSet,
  useDeleteRuleSet,
  useEnableCerberus,
  useDisableCerberus,
} from '../useSecurity'
import * as securityApi from '../../api/security'
import toast from 'react-hot-toast'

vi.mock('../../api/security')
vi.mock('react-hot-toast')

describe('useSecurity hooks', () => {
  let queryClient: QueryClient

  beforeEach(() => {
    queryClient = new QueryClient({
      defaultOptions: {
        queries: { retry: false },
        mutations: { retry: false },
      },
    })
    vi.clearAllMocks()
  })

  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )

  describe('useSecurityStatus', () => {
    it('should fetch security status', async () => {
      const mockStatus = {
        cerberus: { enabled: true },
        crowdsec: { mode: 'local' as const, api_url: 'http://localhost', enabled: true },
        waf: { mode: 'enabled' as const, enabled: true },
        rate_limit: { enabled: true },
        acl: { enabled: true }
      }
      vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockStatus)

      const { result } = renderHook(() => useSecurityStatus(), { wrapper })

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(result.current.data).toEqual(mockStatus)
    })
  })

  describe('useSecurityConfig', () => {
    it('should fetch security config', async () => {
      const mockConfig = { config: { admin_whitelist: '10.0.0.0/8' } }
      vi.mocked(securityApi.getSecurityConfig).mockResolvedValue(mockConfig)

      const { result } = renderHook(() => useSecurityConfig(), { wrapper })

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(result.current.data).toEqual(mockConfig)
    })
  })

  describe('useUpdateSecurityConfig', () => {
    it('should update security config and invalidate queries on success', async () => {
      const payload = { admin_whitelist: '192.168.0.0/16' }
      vi.mocked(securityApi.updateSecurityConfig).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useUpdateSecurityConfig(), { wrapper })

      result.current.mutate(payload)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.updateSecurityConfig).toHaveBeenCalledWith(payload)
      expect(toast.success).toHaveBeenCalledWith('Security configuration updated')
    })

    it('should show error toast on failure', async () => {
      const error = new Error('Update failed')
      vi.mocked(securityApi.updateSecurityConfig).mockRejectedValue(error)

      const { result } = renderHook(() => useUpdateSecurityConfig(), { wrapper })

      result.current.mutate({ admin_whitelist: 'invalid' })

      await waitFor(() => expect(result.current.isError).toBe(true))
      expect(toast.error).toHaveBeenCalledWith('Failed to update security settings: Update failed')
    })
  })

  describe('useGenerateBreakGlassToken', () => {
    it('should generate break glass token', async () => {
      const mockToken = { token: 'abc123' }
      vi.mocked(securityApi.generateBreakGlassToken).mockResolvedValue(mockToken)

      const { result } = renderHook(() => useGenerateBreakGlassToken(), { wrapper })

      result.current.mutate(undefined)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(result.current.data).toEqual(mockToken)
    })
  })

  describe('useDecisions', () => {
    it('should fetch decisions with default limit', async () => {
      const mockDecisions = { decisions: [{ ip: '1.2.3.4', type: 'ban' }] }
      vi.mocked(securityApi.getDecisions).mockResolvedValue(mockDecisions)

      const { result } = renderHook(() => useDecisions(), { wrapper })

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.getDecisions).toHaveBeenCalledWith(50)
      expect(result.current.data).toEqual(mockDecisions)
    })

    it('should fetch decisions with custom limit', async () => {
      const mockDecisions = { decisions: [] }
      vi.mocked(securityApi.getDecisions).mockResolvedValue(mockDecisions)

      const { result } = renderHook(() => useDecisions(100), { wrapper })

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.getDecisions).toHaveBeenCalledWith(100)
    })
  })

  describe('useCreateDecision', () => {
    it('should create decision and invalidate queries', async () => {
      const payload = { value: '1.2.3.4', duration: '4h', type: 'ban' }
      vi.mocked(securityApi.createDecision).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useCreateDecision(), { wrapper })

      result.current.mutate(payload)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.createDecision).toHaveBeenCalledWith(payload)
    })
  })

  describe('useRuleSets', () => {
    it('should fetch rule sets', async () => {
      const mockRuleSets = {
        rulesets: [{
          id: 1,
          uuid: 'abc-123',
          name: 'OWASP CRS',
          source_url: 'https://example.com',
          mode: 'blocking',
          last_updated: '2025-12-04',
          content: 'rules'
        }]
      }
      vi.mocked(securityApi.getRuleSets).mockResolvedValue(mockRuleSets)

      const { result } = renderHook(() => useRuleSets(), { wrapper })

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(result.current.data).toEqual(mockRuleSets)
    })
  })

  describe('useUpsertRuleSet', () => {
    it('should upsert rule set and show success toast', async () => {
      const payload = { name: 'Custom Rules', content: 'rule data', mode: 'blocking' as const }
      vi.mocked(securityApi.upsertRuleSet).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useUpsertRuleSet(), { wrapper })

      result.current.mutate(payload)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.upsertRuleSet).toHaveBeenCalledWith(payload)
      expect(toast.success).toHaveBeenCalledWith('Rule set saved successfully')
    })

    it('should show error toast on failure', async () => {
      const error = new Error('Save failed')
      vi.mocked(securityApi.upsertRuleSet).mockRejectedValue(error)

      const { result } = renderHook(() => useUpsertRuleSet(), { wrapper })

      result.current.mutate({ name: 'Test', content: 'data', mode: 'blocking' })

      await waitFor(() => expect(result.current.isError).toBe(true))
      expect(toast.error).toHaveBeenCalledWith('Failed to save rule set: Save failed')
    })
  })

  describe('useDeleteRuleSet', () => {
    it('should delete rule set and show success toast', async () => {
      vi.mocked(securityApi.deleteRuleSet).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useDeleteRuleSet(), { wrapper })

      result.current.mutate(1)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.deleteRuleSet).toHaveBeenCalledWith(1)
      expect(toast.success).toHaveBeenCalledWith('Rule set deleted')
    })

    it('should show error toast on failure', async () => {
      const error = new Error('Delete failed')
      vi.mocked(securityApi.deleteRuleSet).mockRejectedValue(error)

      const { result } = renderHook(() => useDeleteRuleSet(), { wrapper })

      result.current.mutate(1)

      await waitFor(() => expect(result.current.isError).toBe(true))
      expect(toast.error).toHaveBeenCalledWith('Failed to delete rule set: Delete failed')
    })
  })

  describe('useEnableCerberus', () => {
    it('should enable Cerberus and show success toast', async () => {
      vi.mocked(securityApi.enableCerberus).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useEnableCerberus(), { wrapper })

      result.current.mutate(undefined)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.enableCerberus).toHaveBeenCalledWith(undefined)
      expect(toast.success).toHaveBeenCalledWith('Cerberus enabled')
    })

    it('should enable Cerberus with payload', async () => {
      const payload = { mode: 'full' }
      vi.mocked(securityApi.enableCerberus).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useEnableCerberus(), { wrapper })

      result.current.mutate(payload)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.enableCerberus).toHaveBeenCalledWith(payload)
    })

    it('should show error toast on failure', async () => {
      const error = new Error('Enable failed')
      vi.mocked(securityApi.enableCerberus).mockRejectedValue(error)

      const { result } = renderHook(() => useEnableCerberus(), { wrapper })

      result.current.mutate(undefined)

      await waitFor(() => expect(result.current.isError).toBe(true))
      expect(toast.error).toHaveBeenCalledWith('Failed to enable Cerberus: Enable failed')
    })
  })

  describe('useDisableCerberus', () => {
    it('should disable Cerberus and show success toast', async () => {
      vi.mocked(securityApi.disableCerberus).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useDisableCerberus(), { wrapper })

      result.current.mutate(undefined)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.disableCerberus).toHaveBeenCalledWith(undefined)
      expect(toast.success).toHaveBeenCalledWith('Cerberus disabled')
    })

    it('should disable Cerberus with payload', async () => {
      const payload = { reason: 'maintenance' }
      vi.mocked(securityApi.disableCerberus).mockResolvedValue({ success: true })

      const { result } = renderHook(() => useDisableCerberus(), { wrapper })

      result.current.mutate(payload)

      await waitFor(() => expect(result.current.isSuccess).toBe(true))
      expect(securityApi.disableCerberus).toHaveBeenCalledWith(payload)
    })

    it('should show error toast on failure', async () => {
      const error = new Error('Disable failed')
      vi.mocked(securityApi.disableCerberus).mockRejectedValue(error)

      const { result } = renderHook(() => useDisableCerberus(), { wrapper })

      result.current.mutate(undefined)

      await waitFor(() => expect(result.current.isError).toBe(true))
      expect(toast.error).toHaveBeenCalledWith('Failed to disable Cerberus: Disable failed')
    })
  })
})
