import { describe, it, expect, vi, beforeEach } from 'vitest'
import {
  createScheduleListTool,
  createScheduleCancelByMetadataTool,
  createScheduleCancelByKeyTool,
  createScheduleDeleteByKeyTool
} from '../schedules'
import { ToolingError } from '../../types'
import { getToolServices } from '../../registry'
import { createMockExecutionContext } from '../../../test-utils/mocks'

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

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

const mockSchedulerService = {
  createSchedule: vi.fn(),
  deleteScheduleByIdempotencyKey: vi.fn(),
  updateAnchor: vi.fn(),
  deleteSchedule: vi.fn(),
  findByIdempotencyKey: vi.fn(),
  listSchedules: vi.fn(),
  getSchedule: vi.fn(),
  cancelSchedule: vi.fn(),
  cancelSchedulesByMetadata: vi.fn()
}

beforeEach(() => {
  vi.clearAllMocks()
  vi.mocked(getToolServices).mockReturnValue({
    commandQueueService: null,
    taskSchedulerService: mockSchedulerService,
    notificationsService: null,
    emailService: null,
    credentialsService: null,
    integrationService: null,
    leadService: null,
    storageService: null,
    pdfService: null
  })
})

describe('createScheduleListTool', () => {
  it('creates tool with correct properties', () => {
    const tool = createScheduleListTool()
    expect(tool.name).toBe('schedule_list')
    expect(tool.execute).toBeInstanceOf(Function)
  })

  it('calls listSchedules with correct params', async () => {
    mockSchedulerService.listSchedules.mockResolvedValue({
      schedules: [{
        id: 'sched-1',
        name: 'Test Schedule',
        status: 'active',
        nextRunAt: new Date('2026-03-01'),
        idempotencyKey: 'key-1',
        createdAt: new Date('2026-02-01')
      }],
      total: 1
    })

    const tool = createScheduleListTool()
    const result = await tool.execute({
      input: { metadata: { contactEmail: 'test@example.com' }, status: 'active', limit: 10 },
      executionContext: mockContext
    })

    expect(mockSchedulerService.listSchedules).toHaveBeenCalledWith({
      organizationId: 'org-123',
      metadata: { contactEmail: 'test@example.com' },
      status: 'active',
      limit: 10,
      targetResourceId: undefined
    })
    expect(result).toMatchObject({ total: 1 })
  })

  it('maps schedule fields to output format', async () => {
    mockSchedulerService.listSchedules.mockResolvedValue({
      schedules: [{
        id: 'sched-1',
        name: 'Reminder',
        status: 'active',
        nextRunAt: new Date('2026-03-01T12:00:00.000Z'),
        idempotencyKey: 'key-1',
        createdAt: new Date('2026-02-01T08:00:00.000Z')
      }],
      total: 1
    })

    const tool = createScheduleListTool()
    const result = await tool.execute({
      input: { limit: 20 },
      executionContext: mockContext
    }) as { schedules: Array<{ id: string; name: string; status: string; nextRunAt: string | null; idempotencyKey: string | null; createdAt: string }>; total: number }

    expect(result.schedules).toHaveLength(1)
    expect(result.schedules[0]).toEqual({
      id: 'sched-1',
      name: 'Reminder',
      status: 'active',
      nextRunAt: '2026-03-01T12:00:00.000Z',
      idempotencyKey: 'key-1',
      createdAt: '2026-02-01T08:00:00.000Z'
    })
  })

  it('throws when executionContext missing', async () => {
    const tool = createScheduleListTool()
    await expect(tool.execute({ input: {} })).rejects.toThrow('ExecutionContext is unavailable')
  })

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

    const tool = createScheduleListTool()
    try {
      await tool.execute({ input: {}, executionContext: mockContext })
      expect.fail('Should have thrown')
    } catch (error) {
      expect(error).toBeInstanceOf(ToolingError)
      expect((error as ToolingError).errorType).toBe('service_unavailable')
    }
  })

  it('propagates errors from service', async () => {
    mockSchedulerService.listSchedules.mockRejectedValue(new Error('DB error'))
    const tool = createScheduleListTool()
    await expect(tool.execute({ input: {}, executionContext: mockContext })).rejects.toThrow('DB error')
  })
})

describe('createScheduleCancelByMetadataTool', () => {
  it('creates tool with correct properties', () => {
    const tool = createScheduleCancelByMetadataTool()
    expect(tool.name).toBe('schedule_cancel_by_metadata')
  })

  it('calls cancelSchedulesByMetadata with correct params', async () => {
    mockSchedulerService.cancelSchedulesByMetadata.mockResolvedValue({ cancelled: 3 })

    const tool = createScheduleCancelByMetadataTool()
    const result = await tool.execute({
      input: { metadata: { contactEmail: 'test@example.com' }, status: 'active' },
      executionContext: mockContext
    })

    expect(mockSchedulerService.cancelSchedulesByMetadata).toHaveBeenCalledWith(
      'org-123',
      { contactEmail: 'test@example.com' },
      'active'
    )
    expect(result).toEqual({ cancelled: 3 })
  })

  it('throws when executionContext missing', async () => {
    const tool = createScheduleCancelByMetadataTool()
    await expect(tool.execute({ input: { metadata: {} } })).rejects.toThrow('ExecutionContext is unavailable')
  })

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

    const tool = createScheduleCancelByMetadataTool()
    try {
      await tool.execute({ input: { metadata: {} }, executionContext: mockContext })
      expect.fail('Should have thrown')
    } catch (error) {
      expect(error).toBeInstanceOf(ToolingError)
      expect((error as ToolingError).errorType).toBe('service_unavailable')
    }
  })

  it('propagates errors from service', async () => {
    mockSchedulerService.cancelSchedulesByMetadata.mockRejectedValue(new Error('DB error'))
    const tool = createScheduleCancelByMetadataTool()
    await expect(
      tool.execute({ input: { metadata: {}, status: 'active' }, executionContext: mockContext })
    ).rejects.toThrow('DB error')
  })
})

describe('createScheduleCancelByKeyTool', () => {
  it('creates tool with correct properties', () => {
    const tool = createScheduleCancelByKeyTool()
    expect(tool.name).toBe('schedule_cancel_by_key')
  })

  it('cancels schedule when found', async () => {
    mockSchedulerService.findByIdempotencyKey.mockResolvedValue({ id: 'sched-1', organizationId: 'org-123' })
    mockSchedulerService.cancelSchedule.mockResolvedValue({ id: 'sched-1', status: 'cancelled' })

    const tool = createScheduleCancelByKeyTool()
    const result = await tool.execute({
      input: { idempotencyKey: 'my-key' },
      executionContext: mockContext
    })

    expect(mockSchedulerService.findByIdempotencyKey).toHaveBeenCalledWith('org-123', 'my-key')
    expect(mockSchedulerService.cancelSchedule).toHaveBeenCalledWith('sched-1', 'org-123')
    expect(result).toEqual({ cancelled: true })
  })

  it('returns false when schedule not found', async () => {
    mockSchedulerService.findByIdempotencyKey.mockResolvedValue(null)

    const tool = createScheduleCancelByKeyTool()
    const result = await tool.execute({
      input: { idempotencyKey: 'missing-key' },
      executionContext: mockContext
    })

    expect(result).toEqual({ cancelled: false })
    expect(mockSchedulerService.cancelSchedule).not.toHaveBeenCalled()
  })

  it('throws when executionContext missing', async () => {
    const tool = createScheduleCancelByKeyTool()
    await expect(tool.execute({ input: { idempotencyKey: 'k' } })).rejects.toThrow('ExecutionContext is unavailable')
  })

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

    const tool = createScheduleCancelByKeyTool()
    try {
      await tool.execute({ input: { idempotencyKey: 'k' }, executionContext: mockContext })
      expect.fail('Should have thrown')
    } catch (error) {
      expect(error).toBeInstanceOf(ToolingError)
      expect((error as ToolingError).errorType).toBe('service_unavailable')
    }
  })

  it('propagates errors from findByIdempotencyKey', async () => {
    mockSchedulerService.findByIdempotencyKey.mockRejectedValue(new Error('Lookup failed'))
    const tool = createScheduleCancelByKeyTool()
    await expect(
      tool.execute({ input: { idempotencyKey: 'k' }, executionContext: mockContext })
    ).rejects.toThrow('Lookup failed')
  })
})

describe('createScheduleDeleteByKeyTool', () => {
  it('creates tool with correct properties', () => {
    const tool = createScheduleDeleteByKeyTool()
    expect(tool.name).toBe('schedule_delete_by_key')
  })

  it('calls deleteScheduleByIdempotencyKey with correct params', async () => {
    mockSchedulerService.deleteScheduleByIdempotencyKey.mockResolvedValue(undefined)

    const tool = createScheduleDeleteByKeyTool()
    const result = await tool.execute({
      input: { idempotencyKey: 'my-key' },
      executionContext: mockContext
    })

    expect(mockSchedulerService.deleteScheduleByIdempotencyKey).toHaveBeenCalledWith('my-key', 'org-123')
    expect(result).toEqual({ deleted: true })
  })

  it('throws when executionContext missing', async () => {
    const tool = createScheduleDeleteByKeyTool()
    await expect(tool.execute({ input: { idempotencyKey: 'k' } })).rejects.toThrow('ExecutionContext is unavailable')
  })

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

    const tool = createScheduleDeleteByKeyTool()
    try {
      await tool.execute({ input: { idempotencyKey: 'k' }, executionContext: mockContext })
      expect.fail('Should have thrown')
    } catch (error) {
      expect(error).toBeInstanceOf(ToolingError)
      expect((error as ToolingError).errorType).toBe('service_unavailable')
    }
  })

  it('propagates errors from service', async () => {
    mockSchedulerService.deleteScheduleByIdempotencyKey.mockRejectedValue(new Error('Not found'))
    const tool = createScheduleDeleteByKeyTool()
    await expect(
      tool.execute({ input: { idempotencyKey: 'k' }, executionContext: mockContext })
    ).rejects.toThrow('Not found')
  })
})
