/**
 * Scheduled reports API — scheduled-reports (wave-15).
 *
 * Mounted at /api/report-schedules in routes/index.ts.
 *
 * Endpoints:
 *  GET    /api/report-schedules           — list schedules for tenant
 *  POST   /api/report-schedules           — create schedule
 *  GET    /api/report-schedules/:id       — get single schedule
 *  PATCH  /api/report-schedules/:id       — update schedule
 *  DELETE /api/report-schedules/:id       — delete schedule
 *  POST   /api/report-schedules/:id/run   — trigger one-off run (queued)
 *
 * Guards: authMiddleware + requireTier(BUSINESS) + requirePermission('reports:export_external')
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission, requireTier } from '../middleware/guards'
import { TenantTier } from '@zync/types'
import {
  createDb,
  listReportSchedules,
  getReportSchedule,
  createReportSchedule,
  updateReportSchedule,
  deleteReportSchedule,
  logAuditEvent,
} from '@zync/db/queries'
import { computeNextRun } from '../lib/schedule-math'

export const scheduledReportsRouter = new Hono<AppEnv>()

scheduledReportsRouter.use('*', authMiddleware)
scheduledReportsRouter.use('*', requireTier(TenantTier.BUSINESS))
scheduledReportsRouter.use('*', requirePermission('reports:export_external'))

// ── Validation schemas ─────────────────────────────────────────────────────

const recipientSchema = z.union([
  z.object({ type: z.literal('user'), user_id: z.string().uuid() }),
  z.object({ type: z.literal('email'), email: z.string().email() }),
])

const createScheduleSchema = z.object({
  name: z.string().min(1).max(200),
  report_type: z.enum(['pnl', 'cashflow', 'revenue', 'expenses']),
  format: z.literal('xlsx').default('xlsx'),
  frequency: z.enum(['daily', 'weekly', 'monthly', 'quarterly']),
  day_of_week: z.number().int().min(0).max(6).optional(),
  day_of_month: z.number().int().min(1).max(28).optional(),
  time_of_day: z.string().regex(/^\d{2}:\d{2}$/).default('08:00'),
  period_type: z.enum(['previous', 'current', 'ytd']).default('previous'),
  report_params: z.record(z.unknown()).default({}),
  recipients: z.array(recipientSchema).min(1),
})

const updateScheduleSchema = createScheduleSchema
  .partial()
  .extend({ is_active: z.boolean().optional() })

const DEFAULT_TZ = 'Asia/Jerusalem'

// ── Handlers ──────────────────────────────────────────────────────────────

scheduledReportsRouter.get('/', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid as string
  const db = createDb(c.env)
  const schedules = await listReportSchedules(db, tenantId)
  return c.json({ items: schedules })
})

scheduledReportsRouter.post('/', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid as string
  const userId = session.sub as string

  const raw = await c.req.json().catch(() => null)
  const parsed = createScheduleSchema.safeParse(raw)
  if (!parsed.success) return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)

  const body = parsed.data
  const db = createDb(c.env)

  const now = new Date()
  const nextRunAt = computeNextRun(
    {
      frequency: body.frequency,
      dayOfWeek: body.day_of_week ?? null,
      dayOfMonth: body.day_of_month ?? null,
      timeOfDay: body.time_of_day,
    },
    now,
    DEFAULT_TZ,
  )

  const schedule = await createReportSchedule(db, {
    tenantId,
    createdBy: userId,
    name: body.name,
    reportType: body.report_type,
    format: body.format,
    frequency: body.frequency,
    dayOfWeek: body.day_of_week,
    dayOfMonth: body.day_of_month,
    timeOfDay: body.time_of_day,
    periodType: body.period_type,
    reportParams: body.report_params,
    recipients: body.recipients,
    nextRunAt,
  })

  await logAuditEvent(
    { env: c.env },
    {
      tenantId,
      userId,
      eventType: 'report_schedule.created',
      entityType: 'report_schedule',
      entityId: schedule.id,
      metadata: { name: schedule.name, reportType: schedule.reportType },
    },
  )

  return c.json(schedule, 201)
})

scheduledReportsRouter.get('/:id', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid as string
  const db = createDb(c.env)
  const schedule = await getReportSchedule(db, tenantId, c.req.param('id'))
  if (!schedule) return c.json({ error: 'Not found' }, 404)
  return c.json(schedule)
})

scheduledReportsRouter.patch('/:id', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid as string
  const userId = session.sub as string

  const raw = await c.req.json().catch(() => null)
  const parsed = updateScheduleSchema.safeParse(raw)
  if (!parsed.success) return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)

  const body = parsed.data
  const db = createDb(c.env)

  const existing = await getReportSchedule(db, tenantId, c.req.param('id'))
  if (!existing) return c.json({ error: 'Not found' }, 404)

  const updateInput: Record<string, unknown> = {}
  if (body.name !== undefined) updateInput['name'] = body.name
  if (body.report_type !== undefined) updateInput['reportType'] = body.report_type
  if (body.format !== undefined) updateInput['format'] = body.format
  if (body.frequency !== undefined) updateInput['frequency'] = body.frequency
  if (body.day_of_week !== undefined) updateInput['dayOfWeek'] = body.day_of_week
  if (body.day_of_month !== undefined) updateInput['dayOfMonth'] = body.day_of_month
  if (body.time_of_day !== undefined) updateInput['timeOfDay'] = body.time_of_day
  if (body.period_type !== undefined) updateInput['periodType'] = body.period_type
  if (body.report_params !== undefined) updateInput['reportParams'] = body.report_params
  if (body.recipients !== undefined) updateInput['recipients'] = body.recipients
  if (body.is_active !== undefined) updateInput['isActive'] = body.is_active

  // Recompute next_run_at if schedule timing changed
  const timeChanged =
    body.frequency !== undefined ||
    body.day_of_week !== undefined ||
    body.day_of_month !== undefined ||
    body.time_of_day !== undefined
  if (timeChanged) {
    const nextRunAt = computeNextRun(
      {
        frequency: (body.frequency ?? existing.frequency) as 'daily' | 'weekly' | 'monthly' | 'quarterly',
        dayOfWeek: body.day_of_week !== undefined ? (body.day_of_week ?? null) : existing.dayOfWeek,
        dayOfMonth: body.day_of_month !== undefined ? (body.day_of_month ?? null) : existing.dayOfMonth,
        timeOfDay: body.time_of_day ?? existing.timeOfDay,
      },
      new Date(),
      DEFAULT_TZ,
    )
    updateInput['nextRunAt'] = nextRunAt
  }

  const updated = await updateReportSchedule(
    db,
    tenantId,
    c.req.param('id'),
    updateInput as Parameters<typeof updateReportSchedule>[3],
  )
  if (!updated) return c.json({ error: 'Not found' }, 404)

  await logAuditEvent(
    { env: c.env },
    {
      tenantId,
      userId,
      eventType: 'report_schedule.updated',
      entityType: 'report_schedule',
      entityId: updated.id,
      metadata: { name: updated.name, changes: Object.keys(updateInput) },
    },
  )

  return c.json(updated)
})

scheduledReportsRouter.delete('/:id', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid as string
  const userId = session.sub as string
  const db = createDb(c.env)

  const existing = await getReportSchedule(db, tenantId, c.req.param('id'))
  if (!existing) return c.json({ error: 'Not found' }, 404)

  const deleted = await deleteReportSchedule(db, tenantId, c.req.param('id'))
  if (!deleted) return c.json({ error: 'Not found' }, 404)

  await logAuditEvent(
    { env: c.env },
    {
      tenantId,
      userId,
      eventType: 'report_schedule.deleted',
      entityType: 'report_schedule',
      entityId: c.req.param('id'),
      metadata: { name: existing.name },
    },
  )

  return c.json({ ok: true })
})

// One-off run — dispatch to queue
scheduledReportsRouter.post('/:id/run', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid as string
  const userId = session.sub as string
  const db = createDb(c.env)

  const schedule = await getReportSchedule(db, tenantId, c.req.param('id'))
  if (!schedule) return c.json({ error: 'Not found' }, 404)
  if (!schedule.isActive) return c.json({ error: 'Schedule is inactive' }, 422)

  await c.env.QUEUE.send({
    type: 'report.schedule',
    scheduleId: schedule.id,
    tenantId: schedule.tenantId,
    oneOff: true,
  })

  await logAuditEvent(
    { env: c.env },
    {
      tenantId,
      userId,
      eventType: 'report_schedule.one_off_triggered',
      entityType: 'report_schedule',
      entityId: schedule.id,
      metadata: { name: schedule.name },
    },
  )

  return c.json({ ok: true, queued: true })
})
