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

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

describe('createSchedulerTool', () => {
  const runAt = new Date('2025-12-31T10:00:00Z')

  const mockConfig: SchedulerToolConfig = {
    name: 'schedule_followup',
    description: 'Schedule a follow-up task',
    inputSchema: z.object({
      message: z.string(),
      recipientId: z.string()
    }),
    outputSchema: z.object({
      taskId: z.string()
    }),
    targetResourceId: 'followup-agent',
    targetResourceType: 'agent',
    runAt,
    maxRetries: 5,
    taskDescription: 'Follow-up task',
    idempotencyKey: 'followup-123'
  }

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

  const mockScheduleResult = {
    id: 'scheduled-task-123',
    organizationId: 'org-123',
    name: 'Test Schedule',
    status: 'active' as const,
    currentStep: 0,
    scheduleConfig: { type: 'absolute' as const, items: [] },
    target: { resourceType: 'agent', resourceId: 'agent-123' },
    createdAt: new Date(),
    updatedAt: new Date()
  }

  const mockTaskScheduler = {
    createSchedule: vi.fn().mockResolvedValue(mockScheduleResult)
  }

  beforeEach(() => {
    vi.clearAllMocks()
    mockTaskScheduler.createSchedule.mockResolvedValue(mockScheduleResult)
    vi.mocked(getToolServices).mockReturnValue({
      commandQueueService: null,
      taskSchedulerService: mockTaskScheduler,
      notificationsService: null,
      credentialsService: null,
      integrationService: null
    })
  })

  it('creates scheduled task with all parameters', async () => {
    const tool = createSchedulerTool(mockConfig)
    const input = { message: 'Follow up on quote', recipientId: 'user-456' }

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

    expect(result).toEqual({ taskId: 'scheduled-task-123' })
    expect(mockTaskScheduler.createSchedule).toHaveBeenCalledWith({
      organizationId: 'org-123',
      name: 'Follow-up task',
      description: 'Created by schedule_followup tool',
      target: {
        resourceType: 'agent',
        resourceId: 'followup-agent'
      },
      scheduleConfig: {
        type: 'absolute',
        items: [{
          runAt: runAt.toISOString(),
          payload: input,
          label: 'Follow-up task'
        }]
      },
      maxRetries: 5,
      idempotencyKey: 'followup-123',
      originExecutionId: 'exec-456',
      originResourceType: 'agent',
      originResourceId: 'agent-789'
    })
  })

  it('uses default maxRetries when not specified', async () => {
    const configNoRetries = { ...mockConfig, maxRetries: undefined }
    const tool = createSchedulerTool(configNoRetries)

    await tool.execute({
      input: { message: 'Test', recipientId: 'user-123' },
      executionContext: mockContext
    })

    expect(mockTaskScheduler.createSchedule).toHaveBeenCalledWith(
      expect.objectContaining({ maxRetries: 3 })
    )
  })

  it('supports workflow targets', async () => {
    const workflowConfig = { ...mockConfig, targetResourceType: 'workflow' as const }
    const tool = createSchedulerTool(workflowConfig)

    await tool.execute({
      input: { message: 'Test', recipientId: 'user-123' },
      executionContext: mockContext
    })

    expect(mockTaskScheduler.createSchedule).toHaveBeenCalledWith(
      expect.objectContaining({
        target: expect.objectContaining({ resourceType: 'workflow' })
      })
    )
  })

  it('throws Error when executionContext is missing', async () => {
    const tool = createSchedulerTool(mockConfig)

    await expect(
      tool.execute({
        input: { message: 'Test', recipientId: 'user-123' }
      })
    ).rejects.toThrow('ExecutionContext is unavailable')
  })

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

    const tool = createSchedulerTool(mockConfig)

    try {
      await tool.execute({
        input: { message: 'Test', recipientId: 'user-123' },
        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('TaskSchedulerService is unavailable')
    }
  })

  it('handles multiple concurrent scheduling requests', async () => {
    mockTaskScheduler.createSchedule
      .mockResolvedValueOnce({ ...mockScheduleResult, id: 'task-1' })
      .mockResolvedValueOnce({ ...mockScheduleResult, id: 'task-2' })

    const tool = createSchedulerTool(mockConfig)

    const results = await Promise.all([
      tool.execute({
        input: { message: 'A', recipientId: 'u1' },
        executionContext: mockContext
      }),
      tool.execute({
        input: { message: 'B', recipientId: 'u2' },
        executionContext: mockContext
      })
    ])

    expect(results).toEqual([{ taskId: 'task-1' }, { taskId: 'task-2' }])
    expect(mockTaskScheduler.createSchedule).toHaveBeenCalledTimes(2)
  })
})
