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

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

describe('createNotificationTool', () => {
  const mockConfig: NotificationToolConfig = {
    name: 'notify_user',
    description: 'Send notification to user',
    inputSchema: z.object({
      userId: z.string(),
      title: z.string(),
      message: z.string(),
      category: z.enum(['info', 'success', 'warning', 'error']),
      actionUrl: z.string().optional()
    }),
    outputSchema: z.object({
      notificationId: z.string()
    })
  }

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

  const mockNotificationsService = {
    create: vi.fn().mockResolvedValue({ id: 'notif-123' })
  }

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

  it('creates notification with all parameters from input', async () => {
    const tool = createNotificationTool(mockConfig)
    const input = {
      userId: 'user-456',
      title: 'Task Complete',
      message: 'Your task has been completed successfully',
      category: 'success' as const,
      actionUrl: '/tasks/123'
    }

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

    expect(result).toEqual({ notificationId: 'notif-123' })
    expect(mockNotificationsService.create).toHaveBeenCalledWith({
      userId: 'user-456',
      organizationId: 'org-123',
      category: 'success',
      title: 'Task Complete',
      message: 'Your task has been completed successfully',
      actionUrl: '/tasks/123'
    })
  })

  it('supports static userId and category configuration', async () => {
    const staticConfig = {
      ...mockConfig,
      userId: 'static-user-123',
      category: 'warning' as const
    }
    const tool = createNotificationTool(staticConfig)
    const input = {
      userId: 'ignored',
      title: 'Test',
      message: 'Message',
      category: 'info' as const
    }

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

    expect(mockNotificationsService.create).toHaveBeenCalledWith(
      expect.objectContaining({
        userId: 'static-user-123',
        category: 'warning'
      })
    )
  })

  it('supports optional actionUrl', async () => {
    const tool = createNotificationTool(mockConfig)
    const inputWithUrl = {
      userId: 'user-1',
      title: 'Alert',
      message: 'Click here',
      category: 'info' as const,
      actionUrl: '/action'
    }
    const inputWithoutUrl = {
      userId: 'user-2',
      title: 'Alert',
      message: 'No action',
      category: 'info' as const
    }

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

    expect(mockNotificationsService.create).toHaveBeenNthCalledWith(
      1,
      expect.objectContaining({ actionUrl: '/action' })
    )
    expect(mockNotificationsService.create).toHaveBeenNthCalledWith(
      2,
      expect.objectContaining({ actionUrl: undefined })
    )
  })

  it('throws Error when executionContext is missing', async () => {
    const tool = createNotificationTool(mockConfig)
    const input = {
      userId: 'user-123',
      title: 'Test',
      message: 'Message',
      category: 'info' as const
    }

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

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

    const tool = createNotificationTool(mockConfig)
    const input = {
      userId: 'user-123',
      title: 'Test',
      message: 'Message',
      category: 'info' as const
    }

    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('NotificationsService is unavailable')
    }
  })

  it('handles multiple concurrent notifications', async () => {
    mockNotificationsService.create
      .mockResolvedValueOnce({ id: 'notif-1' })
      .mockResolvedValueOnce({ id: 'notif-2' })

    const tool = createNotificationTool(mockConfig)

    const results = await Promise.all([
      tool.execute({
        input: { userId: 'u1', title: 'T1', message: 'M1', category: 'info' as const },
        executionContext: mockContext
      }),
      tool.execute({
        input: { userId: 'u2', title: 'T2', message: 'M2', category: 'success' as const },
        executionContext: mockContext
      })
    ])

    expect(results).toEqual([{ notificationId: 'notif-1' }, { notificationId: 'notif-2' }])
    expect(mockNotificationsService.create).toHaveBeenCalledTimes(2)
  })
})
