import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import CalendarPage from '@/pages/CalendarPage'

const mockGet = vi.fn()
const mockPost = vi.fn()

vi.mock('@/services/api', () => ({
  default: {
    get: (...args: any[]) => mockGet(...args),
    post: (...args: any[]) => mockPost(...args),
  },
}))

type Slot = {
  slot_id: string
  date: string
  slot_type: 'work' | 'on_call' | 'entertainment' | 'system'
  estimated_duration: number
  scheduled_at: string
  started_at: string | null
  attended: boolean
  actual_duration: number | null
  event_type: 'job' | 'entertainment' | 'system_event' | null
  event_data: Record<string, any> | null
  priority: number
  status: 'not_started' | 'ongoing' | 'deferred' | 'skipped' | 'paused' | 'finished' | 'aborted'
  plan_id: number | null
  is_virtual: boolean
  created_at: string | null
  updated_at: string | null
}

type Plan = {
  id: number
  slot_type: 'work' | 'on_call' | 'entertainment' | 'system'
  estimated_duration: number
  event_type: string | null
  event_data: Record<string, any> | null
  at_time: string
  on_day: string | null
  on_week: number | null
  on_month: string | null
  is_active: boolean
  created_at: string
  updated_at: string | null
}

const slots: Slot[] = [
  {
    slot_id: '1',
    date: '2026-04-01',
    slot_type: 'work',
    estimated_duration: 25,
    scheduled_at: '09:00',
    started_at: null,
    attended: false,
    actual_duration: null,
    event_type: 'job',
    event_data: { type: 'Task', code: 'TASK-1' },
    priority: 50,
    status: 'not_started',
    plan_id: null,
    is_virtual: false,
    created_at: '2026-04-01T00:00:00Z',
    updated_at: '2026-04-01T00:00:00Z',
  },
  {
    slot_id: 'plan-5-2026-04-01',
    date: '2026-04-01',
    slot_type: 'on_call',
    estimated_duration: 30,
    scheduled_at: '14:00',
    started_at: null,
    attended: false,
    actual_duration: null,
    event_type: null,
    event_data: null,
    priority: 70,
    status: 'deferred',
    plan_id: 5,
    is_virtual: true,
    created_at: null,
    updated_at: null,
  },
]

const plans: Plan[] = [
  {
    id: 5,
    slot_type: 'on_call',
    estimated_duration: 30,
    event_type: null,
    event_data: null,
    at_time: '14:00',
    on_day: 'Mon',
    on_week: null,
    on_month: null,
    is_active: true,
    created_at: '2026-03-01T00:00:00Z',
    updated_at: null,
  },
  {
    id: 6,
    slot_type: 'work',
    estimated_duration: 50,
    event_type: 'system_event',
    event_data: { event: 'ScheduleToday' },
    at_time: '08:00',
    on_day: 'Tue',
    on_week: 1,
    on_month: null,
    is_active: false,
    created_at: '2026-03-01T00:00:00Z',
    updated_at: null,
  },
]

function setupApi(options?: {
  slots?: Slot[]
  plans?: Plan[]
  dayError?: string
  postImpl?: (url: string, payload?: any) => any
}) {
  const currentSlots = options?.slots ?? slots
  const currentPlans = options?.plans ?? plans

  mockGet.mockImplementation((url: string) => {
    if (url.startsWith('/calendar/day')) {
      if (options?.dayError) {
        return Promise.reject({ response: { data: { detail: options.dayError } } })
      }
      return Promise.resolve({ data: currentSlots })
    }
    if (url === '/calendar/plans') {
      return Promise.resolve({ data: currentPlans })
    }
    return Promise.reject(new Error(`Unhandled GET ${url}`))
  })

  mockPost.mockImplementation((url: string, payload?: any) => {
    if (options?.postImpl) return options.postImpl(url, payload)
    return Promise.resolve({ data: {} })
  })
}

describe('CalendarPage', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    setupApi()
    vi.spyOn(window, 'confirm').mockReturnValue(true)
  })

  afterEach(() => {
    cleanup()
    vi.restoreAllMocks()
  })

  it('renders daily slots, deferred notice, and virtual-plan badge', async () => {
    render(<CalendarPage />)

    expect(await screen.findByText('09:00')).toBeInTheDocument()
    expect(screen.getByText('14:00')).toBeInTheDocument()
    expect(screen.getAllByText(/deferred/i).length).toBeGreaterThan(0)
    expect(screen.getByText(/agent unavailability/i)).toBeInTheDocument()
    expect(screen.getByText(/TASK-1/i)).toBeInTheDocument()
    expect(screen.getByText(/^plan$/i)).toBeInTheDocument()
    expect(screen.getByText(/Priority: 50/i)).toBeInTheDocument()
  })

  it('renders plans tab content', async () => {
    render(<CalendarPage />)

    await screen.findByText('09:00')
    await userEvent.click(screen.getByRole('button', { name: /Plans \(2\)/i }))

    expect(await screen.findByText(/Plan #5/i)).toBeInTheDocument()
    expect(screen.getByText(/Plan #6/i)).toBeInTheDocument()
    expect(screen.getByText(/at 14:00 · on Mon/i)).toBeInTheDocument()
    expect(screen.getByText(/inactive/i)).toBeInTheDocument()
  })

  it('opens create modal and toggles event-specific fields', async () => {
    render(<CalendarPage />)

    await screen.findByText('09:00')
    await userEvent.click(screen.getByRole('button', { name: /\+ New Slot/i }))

    expect(screen.getByRole('heading', { name: /New Slot/i })).toBeInTheDocument()

    const selects = screen.getAllByRole('combobox')
    const eventTypeSelect = selects[1]

    fireEvent.change(eventTypeSelect, { target: { value: 'job' } })
    expect(screen.getByLabelText(/Job Code/i)).toBeInTheDocument()

    fireEvent.change(eventTypeSelect, { target: { value: 'system_event' } })
    expect(screen.getByLabelText(/System Event/i)).toBeInTheDocument()
  })

  it('submits new slot and displays workload warnings', async () => {
    setupApi({
      postImpl: (url: string) => {
        if (url === '/calendar/schedule') {
          return Promise.resolve({
            data: {
              slot: slots[0],
              warnings: [
                {
                  period: 'daily',
                  slot_type: 'work',
                  current: 25,
                  minimum: 120,
                  message: 'Daily work minimum not met',
                },
              ],
            },
          })
        }
        return Promise.resolve({ data: {} })
      },
    })

    render(<CalendarPage />)

    await screen.findByText('09:00')
    await userEvent.click(screen.getByRole('button', { name: /\+ New Slot/i }))
    await userEvent.click(screen.getByRole('button', { name: /^Save$/i }))

    await waitFor(() => {
      expect(mockPost).toHaveBeenCalledWith(
        '/calendar/schedule',
        expect.objectContaining({
          slot_type: 'work',
          scheduled_at: '09:00',
          estimated_duration: 25,
          priority: 50,
          date: '2026-04-01',
        }),
      )
    })

    expect(await screen.findByText(/Workload Warnings/i)).toBeInTheDocument()
    expect(screen.getByText(/Daily work minimum not met/i)).toBeInTheDocument()
  })

  it('shows overlap error when save fails with overlap detail', async () => {
    setupApi({
      postImpl: (url: string) => {
        if (url === '/calendar/schedule') {
          return Promise.reject({
            response: { data: { detail: 'Slot overlap with existing slot at 09:00' } },
          })
        }
        return Promise.resolve({ data: {} })
      },
    })

    render(<CalendarPage />)

    await screen.findByText('09:00')
    await userEvent.click(screen.getByRole('button', { name: /\+ New Slot/i }))
    await userEvent.click(screen.getByRole('button', { name: /^Save$/i }))

    expect(await screen.findByText(/Overlap conflict/i)).toBeInTheDocument()
  })

  it('opens edit modal with existing slot values and submits edit request', async () => {
    setupApi({
      postImpl: (url: string) => {
        if (url.startsWith('/calendar/edit')) {
          return Promise.resolve({ data: { slot: slots[0], warnings: [] } })
        }
        return Promise.resolve({ data: {} })
      },
    })

    render(<CalendarPage />)

    await screen.findByText('09:00')
    await userEvent.click(screen.getAllByRole('button', { name: /✏️ Edit/i })[0])

    expect(screen.getByRole('heading', { name: /Edit Slot/i })).toBeInTheDocument()
    expect((screen.getAllByRole('combobox')[0] as HTMLSelectElement).value).toBe('work')
    expect((document.querySelector('input[type="time"]') as HTMLInputElement).value).toBe('09:00')

    await userEvent.click(screen.getByRole('button', { name: /^Save$/i }))

    await waitFor(() => {
      expect(mockPost).toHaveBeenCalledWith(
        expect.stringContaining('/calendar/edit?date=2026-04-01&slot_id=1'),
        expect.objectContaining({ slot_type: 'work' }),
      )
    })
  })

  it('cancels a slot after confirmation', async () => {
    render(<CalendarPage />)

    await screen.findByText('09:00')
    await userEvent.click(screen.getAllByRole('button', { name: /❌ Cancel/i })[0])

    await waitFor(() => {
      expect(window.confirm).toHaveBeenCalled()
      expect(mockPost).toHaveBeenCalledWith('/calendar/cancel?date=2026-04-01&slot_id=1')
    })
  })

  it('shows fetch error banner when day loading fails', async () => {
    setupApi({ dayError: 'Failed to load calendar' })

    render(<CalendarPage />)

    expect(await screen.findByText(/Failed to load calendar/i)).toBeInTheDocument()
  })
})
