/**
 * Tests that typed ExecutionError subclasses pass through wrapAndLogError
 * without being re-wrapped as AgentIterationError/AgentCompletionError.
 *
 * Uses vi.mock to inject errors at the processReasoning level,
 * which is where LLM errors (CircuitBreakerOpenError, LLMError, etc.)
 * originate in production.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { Agent } from '../agent'
import type { AgentDefinition, AgentConfig, LLMAdapterFactory } from '../types'
import type { Contract } from '../../../base/types'
import { z } from 'zod'
import { createMockExecutionContext } from '../../../test-utils/mocks'
import { CircuitBreakerOpenError } from '../../../llm/adapters/circuit-breaker'
import { LLMError, InsufficientTokensError } from '../../../llm/errors'
import { AgentIterationError } from '../../errors'

// Mock processReasoning to inject errors during iteration phase
const mockProcessReasoning = vi.fn()
vi.mock('../../reasoning/processor', () => ({
  processReasoning: (...args: unknown[]) => mockProcessReasoning(...args)
}))

// Mock processActions/processMemory (not needed when processReasoning throws)
vi.mock('../../actions/processor', () => ({
  processActions: vi.fn()
}))
vi.mock('../../memory/processor', () => ({
  processMemory: vi.fn()
}))

// Mock adapter factory -- processReasoning is mocked so the adapter is never called
const mockAdapterFactory: LLMAdapterFactory = vi.fn() as unknown as LLMAdapterFactory

const createTestAgent = (): Agent => {
  const config: AgentConfig = {
    type: 'agent',
    kind: 'utility',
    resourceId: 'test-agent',
    name: 'Test Agent',
    description: 'Agent for error passthrough testing',
    environment: 'dev',
    systemPrompt: 'You are a test agent',
    constraints: { maxIterations: 3 }
  }

  const contract: Contract = {
    inputSchema: z.object({ query: z.string() }),
    outputSchema: z.object({ response: z.string() })
  }

  return new Agent({
    config,
    contract,
    tools: [],
    modelConfig: { model: 'mock', provider: 'mock', apiKey: 'test-key' }
  } as AgentDefinition, mockAdapterFactory)
}

describe('ExecutionError passthrough in wrapAndLogError', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('preserves CircuitBreakerOpenError through iteration phase', async () => {
    const agent = createTestAgent()
    const context = createMockExecutionContext()
    const originalError = new CircuitBreakerOpenError('gpt-4')

    mockProcessReasoning.mockRejectedValueOnce(originalError)

    const thrown = await agent.execute({ query: 'test' }, context).catch((e: unknown) => e)

    expect(thrown).toBe(originalError)
    expect(thrown).toBeInstanceOf(CircuitBreakerOpenError)
    expect(thrown).not.toBeInstanceOf(AgentIterationError)
  })

  it('preserves LLMError through iteration phase', async () => {
    const agent = createTestAgent()
    const context = createMockExecutionContext()
    const originalError = new LLMError('Provider returned 500')

    mockProcessReasoning.mockRejectedValueOnce(originalError)

    const thrown = await agent.execute({ query: 'test' }, context).catch((e: unknown) => e)

    expect(thrown).toBe(originalError)
    expect(thrown).toBeInstanceOf(LLMError)
    expect(thrown).not.toBeInstanceOf(AgentIterationError)
  })

  it('preserves InsufficientTokensError through iteration phase', async () => {
    const agent = createTestAgent()
    const context = createMockExecutionContext()
    const originalError = new InsufficientTokensError(100, 50)

    mockProcessReasoning.mockRejectedValueOnce(originalError)

    const thrown = await agent.execute({ query: 'test' }, context).catch((e: unknown) => e)

    expect(thrown).toBe(originalError)
    expect(thrown).toBeInstanceOf(InsufficientTokensError)
    expect(thrown).not.toBeInstanceOf(AgentIterationError)
  })

  it('still wraps plain Error as AgentIterationError', async () => {
    const agent = createTestAgent()
    const context = createMockExecutionContext()

    mockProcessReasoning.mockRejectedValueOnce(new TypeError('Cannot read properties of undefined'))

    const thrown = await agent.execute({ query: 'test' }, context).catch((e: unknown) => e)

    expect(thrown).toBeInstanceOf(AgentIterationError)
    expect(thrown).not.toBeInstanceOf(TypeError)
  })

  it('preserves error metadata (type, severity, category)', async () => {
    const agent = createTestAgent()
    const context = createMockExecutionContext()
    const originalError = new CircuitBreakerOpenError('gpt-4')

    mockProcessReasoning.mockRejectedValueOnce(originalError)

    const thrown = await agent.execute({ query: 'test' }, context).catch((e: unknown) => e) as CircuitBreakerOpenError

    expect(thrown.type).toBe('circuit_breaker_open')
    expect(thrown.severity).toBe('critical')
    expect(thrown.category).toBe('system')
    expect(thrown.isRetryable()).toBe(true)
  })
})
