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 USER_ID = '00000000-0000-4000-8000-000000000002'
const ENTRY_ID = '00000000-0000-4000-8000-000000000003'

const mockListTimeApprovalEntries = vi.fn()
const mockApproveTimeEntry = vi.fn()
const mockRejectTimeEntry = vi.fn()
const mockBulkReviewTimeEntries = vi.fn()
const mockResubmitTimeEntry = vi.fn()
const mockExportApprovedTimeEntries = vi.fn()

let mockSession: Record<string, unknown> = {
  type: 'user',
  sub: USER_ID,
  tid: TENANT_ID,
  permissions: ['time:manage'],
}

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (permission: string) => {
    return async (
      c: {
        get: (key: string) => unknown
        json: (body: unknown, status: number) => Response
      },
      next: () => Promise<void>,
    ) => {
      const session = c.get('session') as { permissions?: string[] } | undefined
      if (!session?.permissions?.includes(permission)) {
        return c.json({ error: 'Forbidden' }, 403)
      }
      await next()
    }
  },
}))

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

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

vi.mock('@zync/db/queries', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@zync/db/queries')>()
  return {
    ...actual,
    listTimeApprovalEntries: (...args: unknown[]) => mockListTimeApprovalEntries(...args),
    approveTimeEntry: (...args: unknown[]) => mockApproveTimeEntry(...args),
    rejectTimeEntry: (...args: unknown[]) => mockRejectTimeEntry(...args),
    bulkReviewTimeEntries: (...args: unknown[]) => mockBulkReviewTimeEntries(...args),
    resubmitTimeEntry: (...args: unknown[]) => mockResubmitTimeEntry(...args),
    exportApprovedTimeEntries: (...args: unknown[]) => mockExportApprovedTimeEntries(...args),
    createDb: () => ({}),
  }
})

import { timeApprovalRoutes } from '../src/routes/time/approvals'

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

describe('time approval routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockSession = {
      type: 'user',
      sub: USER_ID,
      tid: TENANT_ID,
      permissions: ['time:manage'],
    }
  })

  it('lists pending approvals from GET /api/time/approvals', async () => {
    mockListTimeApprovalEntries.mockResolvedValue({
      items: [{ id: ENTRY_ID, approvalStatus: 'pending' }],
      nextCursor: null,
      total: 1,
    })

    const res = await appWithRoutes().request('/api/time/approvals?status=pending', {
      method: 'GET',
      headers: { Origin: 'https://app.zync.is' },
    })

    expect(res.status).toBe(200)
    expect(mockListTimeApprovalEntries).toHaveBeenCalledOnce()
  })

  it('approves a single entry through POST /api/time/:id/approve', async () => {
    mockApproveTimeEntry.mockResolvedValue({ id: ENTRY_ID, approvalStatus: 'approved' })

    const res = await appWithRoutes().request(`/api/time/${ENTRY_ID}/approve`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({}),
    })

    expect(res.status).toBe(200)
    expect(mockApproveTimeEntry).toHaveBeenCalledWith({}, TENANT_ID, USER_ID, ENTRY_ID, undefined)
  })

  it('preserves query status codes when approval review fails', async () => {
    const conflict = new Error('Entry is already approved') as Error & { status: number }
    conflict.status = 409
    mockApproveTimeEntry.mockRejectedValue(conflict)

    const res = await appWithRoutes().request(`/api/time/${ENTRY_ID}/approve`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({}),
    })

    expect(res.status).toBe(409)
    await expect(res.json()).resolves.toEqual({ error: 'Entry is already approved' })
  })

  it('rejects a single entry through POST /api/time/:id/reject', async () => {
    mockRejectTimeEntry.mockResolvedValue({ id: ENTRY_ID, approvalStatus: 'rejected' })

    const res = await appWithRoutes().request(`/api/time/${ENTRY_ID}/reject`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({ note: 'Please split these hours across days.' }),
    })

    expect(res.status).toBe(200)
    expect(mockRejectTimeEntry).toHaveBeenCalledWith(
      {},
      TENANT_ID,
      USER_ID,
      ENTRY_ID,
      'Please split these hours across days.',
    )
  })

  it('bulk reviews entries through POST /api/time/approvals/bulk', async () => {
    mockBulkReviewTimeEntries.mockResolvedValue({
      updated: 2,
      skippedIds: [],
    })

    const res = await appWithRoutes().request('/api/time/approvals/bulk', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({
        action: 'approve',
        entry_ids: [ENTRY_ID, '00000000-0000-4000-8000-000000000004'],
      }),
    })

    expect(res.status).toBe(200)
    expect(mockBulkReviewTimeEntries).toHaveBeenCalledOnce()
  })

  it('resubmits through POST /api/time/:id/resubmit', async () => {
    mockSession = {
      type: 'user',
      sub: USER_ID,
      tid: TENANT_ID,
      permissions: ['time:write'],
    }
    mockResubmitTimeEntry.mockResolvedValue({ id: ENTRY_ID, approvalStatus: 'pending' })

    const res = await appWithRoutes().request(`/api/time/${ENTRY_ID}/resubmit`, {
      method: 'POST',
      headers: {
        Origin: 'https://app.zync.is',
      },
    })

    expect(res.status).toBe(200)
    expect(mockResubmitTimeEntry).toHaveBeenCalledWith({}, TENANT_ID, USER_ID, ENTRY_ID)
  })

  it('streams payroll CSV through GET /api/time/export/payroll', async () => {
    mockExportApprovedTimeEntries.mockResolvedValue({
      csv: 'date,person_name\\n',
      filename: 'payroll-2026-05-01_2026-05-31.csv',
      exportedIds: [ENTRY_ID],
    })

    const res = await appWithRoutes().request('/api/time/export/payroll?from=2026-05-01&to=2026-05-31', {
      method: 'GET',
      headers: { Origin: 'https://app.zync.is' },
    })

    expect(res.status).toBe(200)
    expect(res.headers.get('Content-Type')).toContain('text/csv')
    expect(res.headers.get('Content-Disposition')).toContain('payroll-2026-05-01_2026-05-31.csv')
  })
})
