import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Hono } from 'hono'
import type { AppEnv } from '../src/types'

const mockListTickets = vi.fn()
const mockGetTicket = vi.fn()
const mockCreateTicketMessage = vi.fn()
const mockAppendStatusTransitionMessage = vi.fn()
const mockMarkFirstResponse = vi.fn()
const mockGetSlaEnabled = vi.fn()
const mockGetSlaPolicyByPriority = vi.fn()
const mockCreateNotification = vi.fn()
const mockGetOwnerAdminUserIds = vi.fn()
const mockUpdateTicket = vi.fn()
const mockFindUserById = vi.fn()
const mockComputeAndSetDueAt = vi.fn()

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (
    c: { set: (key: string, value: unknown) => void },
    next: () => Promise<void>,
  ) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      role: 'OWNER',
      permissions: ['tickets:read', 'tickets:write'],
    })
    c.set('db', {})
    await next()
  },
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (_permission: string) => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('../src/middleware/require-module-enabled', () => ({
  requireModuleEnabled: () => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('@zync/db/queries', async () => {
  const actual = await vi.importActual<Record<string, unknown>>('@zync/db/queries')
  return {
    ...actual,
    listTickets: (...args: unknown[]) => mockListTickets(...args),
    getTicket: (...args: unknown[]) => mockGetTicket(...args),
    createTicketMessage: (...args: unknown[]) => mockCreateTicketMessage(...args),
    appendStatusTransitionMessage: (...args: unknown[]) =>
      mockAppendStatusTransitionMessage(...args),
    markFirstResponse: (...args: unknown[]) => mockMarkFirstResponse(...args),
    getSlaEnabled: (...args: unknown[]) => mockGetSlaEnabled(...args),
    getSlaPolicyByPriority: (...args: unknown[]) => mockGetSlaPolicyByPriority(...args),
    createNotification: (...args: unknown[]) => mockCreateNotification(...args),
    getOwnerAdminUserIds: (...args: unknown[]) => mockGetOwnerAdminUserIds(...args),
    updateTicket: (...args: unknown[]) => mockUpdateTicket(...args),
    findUserById: (...args: unknown[]) => mockFindUserById(...args),
    computeAndSetDueAt: (...args: unknown[]) => mockComputeAndSetDueAt(...args),
  }
})

vi.mock('../src/lib/sanitize-comment', () => ({
  sanitizeCommentHtml: (value: string) => value,
}))

vi.mock('../src/lib/ticket-webhooks', () => ({
  enqueueTicketWebhook: vi.fn(() => Promise.resolve()),
}))

vi.mock('../src/services/route-ticket-reply', () => ({
  routeTicketReplyToChannel: vi.fn(() => Promise.resolve()),
}))

vi.mock('@zync/realtime/server', () => ({
  publishRealtimeEvent: vi.fn(() => Promise.resolve()),
}))

import { supportRoutes } from '../src/routes/support/router'

function appWithRoutes() {
  const app = new Hono<AppEnv>()
  app.route('/api/tickets', supportRoutes)
  return app
}

describe('ticket SLA support route integrations', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useRealTimers()
    mockListTickets.mockResolvedValue({ rows: [], nextCursor: null })
    mockCreateTicketMessage.mockResolvedValue({ id: 'msg-1' })
    mockAppendStatusTransitionMessage.mockResolvedValue(undefined)
    mockUpdateTicket.mockResolvedValue({ id: 'ticket-1', status: 'pending_customer' })
    mockFindUserById.mockResolvedValue({ name: 'Dana' })
  })

  it('passes the sla_breached filter through the ticket list endpoint', async () => {
    const res = await appWithRoutes().request('/api/tickets?sla_breached=true')

    expect(res.status).toBe(200)
    expect(mockListTickets).toHaveBeenCalledWith(
      {},
      'tenant-1',
      expect.objectContaining({ sla_breached: true }),
      undefined,
      50,
    )
  })

  it('recomputes due_at when priority changes on an SLA-enabled tenant', async () => {
    mockGetTicket.mockResolvedValueOnce({
      id: 'ticket-1',
      status: 'open',
      priority: 'urgent',
      created_at: '2026-07-01T00:00:00.000Z',
    })
    mockGetSlaEnabled.mockResolvedValue(true)

    const res = await appWithRoutes().request('/api/tickets/ticket-1', {
      method: 'PATCH',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ priority: 'low' }),
    })

    expect(res.status).toBe(200)
    expect(mockComputeAndSetDueAt).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'ticket-1',
      'low',
      new Date('2026-07-01T00:00:00.000Z'),
    )
  })

  it('emits first-response SLA notifications when the first staff reply is late', async () => {
    vi.useFakeTimers()
    mockGetTicket.mockResolvedValueOnce({
      id: 'ticket-1',
      status: 'open',
      priority: 'high',
      created_at: '2026-07-01T00:00:00.000Z',
      first_response_at: null,
      assignee_id: 'assignee-1',
    })
    mockGetSlaEnabled.mockResolvedValue(true)
    mockGetSlaPolicyByPriority.mockResolvedValue({ first_response_hours: 1 })
    mockGetOwnerAdminUserIds.mockResolvedValue(['owner-1'])
    vi.setSystemTime(new Date('2026-07-01T03:00:00.000Z'))

    const res = await appWithRoutes().request('/api/tickets/ticket-1/reply', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ content: 'Looking now' }),
    })

    expect(res.status).toBe(201)
    expect(mockMarkFirstResponse).toHaveBeenCalledWith({}, 'tenant-1', 'ticket-1')
    expect(mockCreateNotification).toHaveBeenCalledTimes(2)
    expect(mockCreateNotification).toHaveBeenCalledWith(
      {},
      expect.objectContaining({
        userId: 'assignee-1',
        type: 'ticket_escalated',
      }),
    )
  })
})
