import { describe, it, expect, vi, beforeEach } from 'vitest'
import * as consoleEnrollment from '../consoleEnrollment'
import client from '../client'

vi.mock('../client')

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

  describe('getConsoleStatus', () => {
    it('should fetch enrollment status with pending state', async () => {
      const mockStatus = {
        status: 'pending',
        tenant: 'my-org',
        agent_name: 'charon-prod',
        key_present: true,
        last_attempt_at: '2025-12-15T09:00:00Z',
      }
      vi.mocked(client.get).mockResolvedValue({ data: mockStatus })

      const result = await consoleEnrollment.getConsoleStatus()

      expect(client.get).toHaveBeenCalledWith('/admin/crowdsec/console/status')
      expect(result).toEqual(mockStatus)
      expect(result.status).toBe('pending')
      expect(result.key_present).toBe(true)
    })

    it('should fetch enrolled status with heartbeat', async () => {
      const mockStatus = {
        status: 'enrolled',
        tenant: 'my-org',
        agent_name: 'charon-prod',
        key_present: true,
        enrolled_at: '2025-12-14T10:00:00Z',
        last_heartbeat_at: '2025-12-15T09:55:00Z',
      }
      vi.mocked(client.get).mockResolvedValue({ data: mockStatus })

      const result = await consoleEnrollment.getConsoleStatus()

      expect(result.status).toBe('enrolled')
      expect(result.enrolled_at).toBeDefined()
      expect(result.last_heartbeat_at).toBeDefined()
    })

    it('should fetch failed status with error message', async () => {
      const mockStatus = {
        status: 'failed',
        tenant: 'my-org',
        agent_name: 'charon-prod',
        key_present: false,
        last_error: 'Invalid enrollment key',
        last_attempt_at: '2025-12-15T09:00:00Z',
        correlation_id: 'req-abc123',
      }
      vi.mocked(client.get).mockResolvedValue({ data: mockStatus })

      const result = await consoleEnrollment.getConsoleStatus()

      expect(result.status).toBe('failed')
      expect(result.last_error).toBe('Invalid enrollment key')
      expect(result.correlation_id).toBe('req-abc123')
      expect(result.key_present).toBe(false)
    })

    it('should fetch status with none state (not enrolled)', async () => {
      const mockStatus = {
        status: 'none',
        key_present: false,
      }
      vi.mocked(client.get).mockResolvedValue({ data: mockStatus })

      const result = await consoleEnrollment.getConsoleStatus()

      expect(result.status).toBe('none')
      expect(result.key_present).toBe(false)
      expect(result.tenant).toBeUndefined()
    })

    it('should NOT return enrollment key in status response', async () => {
      const mockStatus = {
        status: 'enrolled',
        tenant: 'test-org',
        agent_name: 'test-agent',
        key_present: true,
        enrolled_at: '2025-12-14T10:00:00Z',
      }
      vi.mocked(client.get).mockResolvedValue({ data: mockStatus })

      const result = await consoleEnrollment.getConsoleStatus()

      // Security test: Ensure key is never exposed
      expect(result).not.toHaveProperty('enrollment_key')
      expect(result).not.toHaveProperty('encrypted_enroll_key')
      expect(result).toHaveProperty('key_present')
    })

    it('should handle API errors', async () => {
      const error = new Error('Network error')
      vi.mocked(client.get).mockRejectedValue(error)

      await expect(consoleEnrollment.getConsoleStatus()).rejects.toThrow('Network error')
    })

    it('should handle server unavailability', async () => {
      const error = {
        response: {
          status: 503,
          data: { error: 'Service temporarily unavailable' },
        },
      }
      vi.mocked(client.get).mockRejectedValue(error)

      await expect(consoleEnrollment.getConsoleStatus()).rejects.toEqual(error)
    })
  })

  describe('enrollConsole', () => {
    it('should enroll with valid payload', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-abc123xyz',
        tenant: 'my-org',
        agent_name: 'charon-prod',
        force: false,
      }
      const mockResponse = {
        status: 'enrolled',
        tenant: 'my-org',
        agent_name: 'charon-prod',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      expect(client.post).toHaveBeenCalledWith('/admin/crowdsec/console/enroll', payload)
      expect(result).toEqual(mockResponse)
      expect(result.status).toBe('enrolled')
      expect(result.enrolled_at).toBeDefined()
    })

    it('should enroll with minimal payload (no tenant)', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-key123',
        agent_name: 'charon-test',
      }
      const mockResponse = {
        status: 'enrolled',
        agent_name: 'charon-test',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      expect(result.status).toBe('enrolled')
      expect(result.agent_name).toBe('charon-test')
    })

    it('should force re-enrollment when force=true', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-new-key',
        agent_name: 'charon-updated',
        force: true,
      }
      const mockResponse = {
        status: 'enrolled',
        agent_name: 'charon-updated',
        key_present: true,
        enrolled_at: '2025-12-15T10:05:00Z',
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      expect(result.status).toBe('enrolled')
      expect(client.post).toHaveBeenCalledWith('/admin/crowdsec/console/enroll', payload)
    })

    it('should handle invalid enrollment key format', async () => {
      const payload = {
        enrollment_key: 'not-a-valid-key',
        agent_name: 'test',
      }
      const error = {
        response: {
          status: 400,
          data: { error: 'Invalid enrollment key format' },
        },
      }
      vi.mocked(client.post).mockRejectedValue(error)

      await expect(consoleEnrollment.enrollConsole(payload)).rejects.toEqual(error)
    })

    it('should handle transient network errors during enrollment', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-key123',
        agent_name: 'test-agent',
      }
      const error = {
        response: {
          status: 503,
          data: { error: 'CrowdSec Console API temporarily unavailable' },
        },
      }
      vi.mocked(client.post).mockRejectedValue(error)

      await expect(consoleEnrollment.enrollConsole(payload)).rejects.toEqual(error)
    })

    it('should handle enrollment key expiration', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-expired-key',
        agent_name: 'test',
      }
      const mockResponse = {
        status: 'failed',
        key_present: false,
        last_error: 'Enrollment key expired',
        correlation_id: 'err-expired-123',
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      expect(result.status).toBe('failed')
      expect(result.last_error).toBe('Enrollment key expired')
    })

    it('should sanitize tenant name with special characters', async () => {
      const payload = {
        enrollment_key: 'valid-key',
        tenant: 'My Org (Production)',
        agent_name: 'agent1',
      }
      const mockResponse = {
        status: 'enrolled',
        tenant: 'My Org (Production)',
        agent_name: 'agent1',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      expect(result.status).toBe('enrolled')
      expect(result.tenant).toBe('My Org (Production)')
    })

    it('should handle SQL injection attempts in agent_name', async () => {
      const payload = {
        enrollment_key: 'valid-key',
        agent_name: "'; DROP TABLE users; --",
      }
      const error = {
        response: {
          status: 400,
          data: { error: 'Invalid agent name format' },
        },
      }
      vi.mocked(client.post).mockRejectedValue(error)

      await expect(consoleEnrollment.enrollConsole(payload)).rejects.toEqual(error)
    })

    it('should handle CrowdSec not running during enrollment', async () => {
      const payload = {
        enrollment_key: 'valid-key',
        agent_name: 'test',
      }
      const error = {
        response: {
          status: 500,
          data: { error: 'CrowdSec is not running. Start CrowdSec before enrolling.' },
        },
      }
      vi.mocked(client.post).mockRejectedValue(error)

      await expect(consoleEnrollment.enrollConsole(payload)).rejects.toEqual(error)
    })

    it('should return pending status when enrollment is queued', async () => {
      const payload = {
        enrollment_key: 'valid-key',
        agent_name: 'test',
      }
      const mockResponse = {
        status: 'pending',
        agent_name: 'test',
        key_present: true,
        last_attempt_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      expect(result.status).toBe('pending')
      expect(result.last_attempt_at).toBeDefined()
    })
  })

  describe('default export', () => {
    it('should export all functions', () => {
      expect(consoleEnrollment.default).toHaveProperty('getConsoleStatus')
      expect(consoleEnrollment.default).toHaveProperty('enrollConsole')
    })
  })

  describe('integration scenarios', () => {
    it('should handle full enrollment workflow: status → enroll → verify', async () => {
      // 1. Check initial status (not enrolled)
      const mockStatusNone = {
        status: 'none',
        key_present: false,
      }
      vi.mocked(client.get).mockResolvedValueOnce({ data: mockStatusNone })

      const statusBefore = await consoleEnrollment.getConsoleStatus()
      expect(statusBefore.status).toBe('none')

      // 2. Enroll
      const enrollPayload = {
        enrollment_key: 'cs-enroll-valid-key',
        tenant: 'test-org',
        agent_name: 'charon-test',
      }
      const mockEnrollResponse = {
        status: 'enrolled',
        tenant: 'test-org',
        agent_name: 'charon-test',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValueOnce({ data: mockEnrollResponse })

      const enrollResult = await consoleEnrollment.enrollConsole(enrollPayload)
      expect(enrollResult.status).toBe('enrolled')

      // 3. Verify status updated
      const mockStatusEnrolled = {
        status: 'enrolled',
        tenant: 'test-org',
        agent_name: 'charon-test',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:00Z',
        last_heartbeat_at: '2025-12-15T10:01:00Z',
      }
      vi.mocked(client.get).mockResolvedValueOnce({ data: mockStatusEnrolled })

      const statusAfter = await consoleEnrollment.getConsoleStatus()
      expect(statusAfter.status).toBe('enrolled')
      expect(statusAfter.tenant).toBe('test-org')
    })

    it('should handle enrollment failure and retry', async () => {
      // 1. First enrollment attempt fails
      const payload = {
        enrollment_key: 'cs-enroll-key',
        agent_name: 'test',
      }
      const networkError = new Error('Network timeout')
      vi.mocked(client.post).mockRejectedValueOnce(networkError)

      await expect(consoleEnrollment.enrollConsole(payload)).rejects.toThrow('Network timeout')

      // 2. Retry succeeds
      const mockResponse = {
        status: 'enrolled',
        agent_name: 'test',
        key_present: true,
        enrolled_at: '2025-12-15T10:05:00Z',
      }
      vi.mocked(client.post).mockResolvedValueOnce({ data: mockResponse })

      const retryResult = await consoleEnrollment.enrollConsole(payload)
      expect(retryResult.status).toBe('enrolled')
    })

    it('should handle status transitions: none → pending → enrolled', async () => {
      // 1. Initial: none
      const mockNone = { status: 'none', key_present: false }
      vi.mocked(client.get).mockResolvedValueOnce({ data: mockNone })
      const status1 = await consoleEnrollment.getConsoleStatus()
      expect(status1.status).toBe('none')

      // 2. Enroll (returns pending)
      const payload = { enrollment_key: 'key', agent_name: 'agent' }
      const mockPending = {
        status: 'pending',
        agent_name: 'agent',
        key_present: true,
        last_attempt_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValueOnce({ data: mockPending })
      const enrollResult = await consoleEnrollment.enrollConsole(payload)
      expect(enrollResult.status).toBe('pending')

      // 3. Check status again (now enrolled)
      const mockEnrolled = {
        status: 'enrolled',
        agent_name: 'agent',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:30Z',
      }
      vi.mocked(client.get).mockResolvedValueOnce({ data: mockEnrolled })
      const status2 = await consoleEnrollment.getConsoleStatus()
      expect(status2.status).toBe('enrolled')
    })

    it('should handle force re-enrollment over existing enrollment', async () => {
      // 1. Check current enrollment
      const mockCurrent = {
        status: 'enrolled',
        tenant: 'old-org',
        agent_name: 'old-agent',
        key_present: true,
        enrolled_at: '2025-12-14T10:00:00Z',
      }
      vi.mocked(client.get).mockResolvedValueOnce({ data: mockCurrent })
      const currentStatus = await consoleEnrollment.getConsoleStatus()
      expect(currentStatus.tenant).toBe('old-org')

      // 2. Force re-enrollment
      const forcePayload = {
        enrollment_key: 'new-key',
        tenant: 'new-org',
        agent_name: 'new-agent',
        force: true,
      }
      const mockForced = {
        status: 'enrolled',
        tenant: 'new-org',
        agent_name: 'new-agent',
        key_present: true,
        enrolled_at: '2025-12-15T10:00:00Z',
      }
      vi.mocked(client.post).mockResolvedValueOnce({ data: mockForced })
      const forceResult = await consoleEnrollment.enrollConsole(forcePayload)
      expect(forceResult.tenant).toBe('new-org')
    })
  })

  describe('security tests', () => {
    it('should never log or expose enrollment key', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-secret-key-should-never-log',
        agent_name: 'test',
      }
      const mockResponse = {
        status: 'enrolled',
        agent_name: 'test',
        key_present: true,
      }
      vi.mocked(client.post).mockResolvedValue({ data: mockResponse })

      const result = await consoleEnrollment.enrollConsole(payload)

      // Ensure response never contains the key
      expect(result).not.toHaveProperty('enrollment_key')
      expect(JSON.stringify(result)).not.toContain('cs-enroll-secret-key')
    })

    it('should sanitize error messages to avoid key leakage', async () => {
      const payload = {
        enrollment_key: 'cs-enroll-sensitive-key',
        agent_name: 'test',
      }
      const error = {
        response: {
          status: 400,
          data: { error: 'Enrollment failed: invalid key format' },
        },
      }
      vi.mocked(client.post).mockRejectedValue(error)

      try {
        await consoleEnrollment.enrollConsole(payload)
      } catch (e: unknown) {
        // Error message should NOT contain the key
        const error = e as { response?: { data?: { error?: string } } }
        expect(error.response?.data?.error).not.toContain('cs-enroll-sensitive-key')
      }
    })

    it('should handle correlation_id for debugging without exposing keys', async () => {
      const mockStatus = {
        status: 'failed',
        key_present: false,
        last_error: 'Authentication failed',
        correlation_id: 'debug-correlation-abc123',
      }
      vi.mocked(client.get).mockResolvedValue({ data: mockStatus })

      const result = await consoleEnrollment.getConsoleStatus()

      expect(result.correlation_id).toBe('debug-correlation-abc123')
      expect(result).not.toHaveProperty('enrollment_key')
    })
  })
})
