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

const TENANT_ID = '00000000-0000-4000-8000-000000000001'

const mockGetTimeTrackingSettings = vi.fn()
const mockUpdateTimeTrackingSettings = vi.fn()
const mockRequirePermission = vi.fn()

let mockSession: Record<string, unknown>

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (
    c: { set: (key: string, value: unknown) => void },
    next: () => Promise<void>,
  ) => {
    c.set('db', {})
    c.set('session', mockSession)
    await next()
  },
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (...args: unknown[]) => mockRequirePermission(...args),
}))

vi.mock('@zync/time', () => ({
  getTimeTrackingSettings: (...args: unknown[]) => mockGetTimeTrackingSettings(...args),
  updateTimeTrackingSettings: (...args: unknown[]) => mockUpdateTimeTrackingSettings(...args),
}))

async function appWithRoute() {
  const { timeTrackingSettingsRoute } = await import('../src/routes/settings/time-tracking')
  const app = new Hono<AppEnv>()
  app.route('/api/settings/time-tracking', timeTrackingSettingsRoute)
  return app
}

function buildSettings() {
  return {
    time_rounding: 'nearest_15' as const,
    time_min_billable_minutes: 15,
    time_idle_threshold_minutes: 10,
    time_auto_pause_on_idle: true,
    time_standard_hours_per_day: 8,
    time_flag_overtime: true,
    time_require_overtime_approval: false,
    contractor_time_enabled: true,
    mileage_enabled: false,
    time_magic_link_enabled: true,
  }
}

describe('time tracking settings route', () => {
  beforeEach(() => {
    vi.resetModules()
    vi.clearAllMocks()
    mockSession = {
      type: 'user',
      sub: '00000000-0000-4000-8000-000000000099',
      tid: TENANT_ID,
      role: 'OWNER',
      permissions: ['settings:read', 'settings:write'],
    }
    mockGetTimeTrackingSettings.mockResolvedValue(buildSettings())
    mockUpdateTimeTrackingSettings.mockResolvedValue(buildSettings())
    mockRequirePermission.mockImplementation(
      (permission: string) =>
        async (
          c: { get: (key: string) => { permissions?: string[] } | undefined; json: (body: unknown, status: number) => Response },
          next: () => Promise<void>,
        ) => {
          const session = c.get('session')
          if (!session?.permissions?.includes(permission)) {
            return c.json({ error: 'Forbidden' }, 403)
          }
          await next()
        },
    )
  })

  it('registers GET and PATCH with the spec permissions', async () => {
    await import('../src/routes/settings/time-tracking')

    expect(mockRequirePermission).toHaveBeenCalledWith('settings:read')
    expect(mockRequirePermission).toHaveBeenCalledWith('settings:write')
  })

  it('returns the full spec payload', async () => {
    const app = await appWithRoute()
    const res = await app.request('/api/settings/time-tracking')

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual(buildSettings())
    expect(mockGetTimeTrackingSettings).toHaveBeenCalledWith({}, TENANT_ID)
  })

  it('updates any subset of the time tracking settings', async () => {
    const app = await appWithRoute()
    const patch = {
      time_min_billable_minutes: 30,
      time_idle_threshold_minutes: 20,
      time_flag_overtime: false,
      time_magic_link_enabled: false,
    }
    const res = await app.request('/api/settings/time-tracking', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(patch),
    })

    expect(res.status).toBe(200)
    expect(mockUpdateTimeTrackingSettings).toHaveBeenCalledWith({}, TENANT_ID, patch)
    await expect(res.json()).resolves.toEqual(buildSettings())
  })

  it('rejects invalid values with a 400', async () => {
    const app = await appWithRoute()
    const res = await app.request('/api/settings/time-tracking', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        time_idle_threshold_minutes: 61,
        time_standard_hours_per_day: 0,
      }),
    })

    expect(res.status).toBe(400)
    expect(mockUpdateTimeTrackingSettings).not.toHaveBeenCalled()
  })
})
