import { describe, it, expect, vi, beforeEach } from 'vitest'
import { z } from 'zod'
import { createApprovalTool } from '../approval'
import { ToolingError } from '../../types'
import type { ApprovalToolConfig } from '../types'
import { getToolServices } from '../../registry'
import { createMockExecutionContext } from '../../../test-utils/mocks'

vi.mock('../../registry', () => ({
  getToolServices: vi.fn()
}))

describe('createApprovalTool', () => {
  const mockConfig: ApprovalToolConfig = {
    name: 'request_payment_approval',
    description: 'Request approval for payment',
    inputSchema: z.object({
      amount: z.number(),
      recipient: z.string()
    }),
    outputSchema: z.object({
      taskId: z.string()
    }),
    approveTargetResourceId: 'payment-processor',
    rejectTargetResourceId: 'payment-rejection-handler',
    priority: 8
  }

  const mockContext = createMockExecutionContext({
    organizationId: 'org-123',
    executionId: 'exec-456',
    resourceId: 'agent-789',
    resourceType: 'agent'
  })

  const mockCommandQueue = {
    createTask: vi.fn().mockResolvedValue({ id: 'task-123' })
  }

  beforeEach(() => {
    vi.clearAllMocks()
    mockCommandQueue.createTask.mockResolvedValue({ id: 'task-123' })
    vi.mocked(getToolServices).mockReturnValue({
      commandQueueService: mockCommandQueue,
      taskSchedulerService: null,
      notificationsService: null,
      credentialsService: null,
      integrationService: null
    })
  })

  it('creates tool with correct properties', () => {
    const tool = createApprovalTool(mockConfig)

    expect(tool.name).toBe('request_payment_approval')
    expect(tool.description).toBe('Request approval for payment')
    expect(tool.inputSchema).toBe(mockConfig.inputSchema)
    expect(tool.outputSchema).toBe(mockConfig.outputSchema)
    expect(tool.execute).toBeInstanceOf(Function)
  })

  it('creates approval task with all parameters', async () => {
    const tool = createApprovalTool(mockConfig)
    const input = { amount: 1000, recipient: 'vendor@example.com' }

    const result = await tool.execute({
      input,
      executionContext: mockContext
    })

    expect(result).toEqual({ taskId: 'task-123' })
    expect(mockCommandQueue.createTask).toHaveBeenCalledWith({
      organizationId: 'org-123',
      originExecutionId: 'exec-456',
      originResourceType: 'agent',
      originResourceId: 'agent-789',
      actions: [
        {
          id: 'approve',
          label: 'Approve',
          type: 'primary',
          icon: 'IconCheck',
          color: 'green',
          target: {
            resourceType: 'agent',
            resourceId: 'payment-processor'
          }
        },
        {
          id: 'reject',
          label: 'Reject',
          type: 'danger',
          icon: 'IconX',
          color: 'red',
          target: {
            resourceType: 'agent',
            resourceId: 'payment-rejection-handler'
          }
        }
      ],
      context: input,
      priority: 8,
      expiresAt: undefined
    })
  })

  it('uses default priority when not specified', async () => {
    const configNoPriority = { ...mockConfig, priority: undefined }
    const tool = createApprovalTool(configNoPriority)
    const input = { amount: 500, recipient: 'test@example.com' }

    await tool.execute({
      input,
      executionContext: mockContext
    })

    expect(mockCommandQueue.createTask).toHaveBeenCalledWith(
      expect.objectContaining({ priority: 5 })
    )
  })

  it('supports optional reject target and expiration', async () => {
    const expiresAt = new Date('2025-12-31')
    const configOptional = {
      ...mockConfig,
      rejectTargetResourceId: undefined,
      expiresAt
    }
    const tool = createApprovalTool(configOptional)

    await tool.execute({
      input: { amount: 100, recipient: 'test@test.com' },
      executionContext: mockContext
    })

    expect(mockCommandQueue.createTask).toHaveBeenCalledWith(
      expect.objectContaining({
        actions: [
          {
            id: 'approve',
            label: 'Approve',
            type: 'primary',
            icon: 'IconCheck',
            color: 'green',
            target: {
              resourceType: 'agent',
              resourceId: 'payment-processor'
            }
          }
        ],
        expiresAt
      })
    )
  })

  it('throws ToolingError when executionContext is missing', async () => {
    const tool = createApprovalTool(mockConfig)
    const input = { amount: 1000, recipient: 'vendor@example.com' }

    await expect(tool.execute({ input })).rejects.toThrow(ToolingError)
    await expect(tool.execute({ input })).rejects.toThrow(
      'ExecutionContext is unavailable'
    )
  })

  it('throws ToolingError when commandQueue service is unavailable', async () => {
    vi.mocked(getToolServices).mockReturnValue({
      commandQueueService: null,
      taskSchedulerService: null,
      notificationsService: null,
      credentialsService: null,
      integrationService: null
    })

    const tool = createApprovalTool(mockConfig)
    const input = { amount: 1000, recipient: 'vendor@example.com' }

    try {
      await tool.execute({
        input,
        executionContext: mockContext
      })
      expect.fail('Should have thrown')
    } catch (error) {
      expect(error).toBeInstanceOf(ToolingError)
      const toolingError = error as ToolingError
      expect(toolingError.errorType).toBe('service_unavailable')
      expect(toolingError.message).toBe('CommandQueueService is unavailable')
      expect(toolingError.details).toMatchObject({
        tool: 'request_payment_approval',
        message: expect.stringContaining('toolServicesRegistry.initialize()')
      })
    }
  })

  it('propagates errors from commandQueue.createTask', async () => {
    mockCommandQueue.createTask.mockRejectedValue(new Error('Database error'))

    const tool = createApprovalTool(mockConfig)
    const input = { amount: 1000, recipient: 'vendor@example.com' }

    await expect(
      tool.execute({
        input,
        executionContext: mockContext
      })
    ).rejects.toThrow('Database error')
  })

  it('handles multiple concurrent approval requests', async () => {
    mockCommandQueue.createTask
      .mockResolvedValueOnce({ id: 'task-1' })
      .mockResolvedValueOnce({ id: 'task-2' })
      .mockResolvedValueOnce({ id: 'task-3' })

    const tool = createApprovalTool(mockConfig)

    const promises = [
      tool.execute({
        input: { amount: 100, recipient: 'a@test.com' },
        executionContext: mockContext
      }),
      tool.execute({
        input: { amount: 200, recipient: 'b@test.com' },
        executionContext: mockContext
      }),
      tool.execute({
        input: { amount: 300, recipient: 'c@test.com' },
        executionContext: mockContext
      })
    ]

    const results = await Promise.all(promises)

    expect(results).toEqual([
      { taskId: 'task-1' },
      { taskId: 'task-2' },
      { taskId: 'task-3' }
    ])
    expect(mockCommandQueue.createTask).toHaveBeenCalledTimes(3)
  })
})
