/**
 * /v1/tasks routes — tenant-public-api (wave-11 leaf-D).
 *
 * GET  /v1/tasks        — list (paginated, filterable)
 * GET  /v1/tasks/:id    — get single
 * POST /v1/tasks        — create
 * PATCH /v1/tasks/:id   — update
 */
import { Hono } from 'hono'
import { z } from 'zod'
import {
  listTasks,
  getTask,
  createTask,
  updateTask,
  getProjectById,
  getTaskStatusById,
  getMembership,
} from '@zync/db/queries'
import type { TaskSource, TenantId, UserId } from '@zync/types'
import {
  hasScope,
  serializeTask,
  errForbiddenScope,
  errNotFound,
  errValidation,
} from '@zync/public-api'
import type { ApiKeyContext } from '../middleware/auth'
import type { Env } from '../env'
import { createDb } from '../db'

type AppEnv = { Bindings: Env }

export const tasksRouter = new Hono<AppEnv>()

const listQuerySchema = z.object({
  limit: z.coerce.number().int().min(1).max(100).default(20),
  cursor: z.string().optional(),
  project_id: z.string().uuid().optional(),
  assignee_id: z.string().uuid().optional(),
})

const createBodySchema = z.object({
  title: z.string().min(1),
  status_id: z.string().uuid(),
  project_id: z.string().uuid().optional(),
  priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
  assignee_id: z.string().uuid().optional(),
  due_date: z.string().optional(),
})

const updateBodySchema = z.object({
  title: z.string().min(1).optional(),
  status_id: z.string().uuid().optional(),
  project_id: z.string().uuid().optional(),
  priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
  assignee_id: z.string().uuid().optional(),
  due_date: z.string().optional(),
})

tasksRouter.get('/', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'tasks:read')) {
    return errForbiddenScope('tasks:read')
  }

  const parsed = listQuerySchema.safeParse(Object.fromEntries(new URL(c.req.url).searchParams))
  if (!parsed.success) {
    return errValidation('Invalid query parameters')
  }

  const db = createDb(c.env)
  const result = await listTasks(
    db,
    apiKey.tenantId,
    {
      project: parsed.data.project_id,
      assignee: parsed.data.assignee_id,
    },
    parsed.data.cursor,
    parsed.data.limit,
  )

  return c.json({
    data: result.rows.map((row) => serializeTask(row, '')),
    meta: {
      next_cursor: result.nextCursor,
      has_more: result.nextCursor !== null,
      limit: parsed.data.limit,
    },
  })
})

tasksRouter.get('/:id', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'tasks:read')) {
    return errForbiddenScope('tasks:read')
  }

  const db = createDb(c.env)
  const task = await getTask(db, apiKey.tenantId, c.req.param('id'))
  if (!task) return errNotFound('Task')

  return c.json({
    data: serializeTask(task, ''),
  })
})

tasksRouter.post('/', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'tasks:write')) {
    return errForbiddenScope('tasks:write')
  }

  const body = await c.req.json().catch(() => null)
  const parsed = createBodySchema.safeParse(body)
  if (!parsed.success) {
    return errValidation(parsed.error.issues[0]?.message ?? 'Invalid body', parsed.error.issues[0]?.path[0] as string)
  }

  const db = createDb(c.env)

  if (parsed.data.project_id) {
    const project = await getProjectById(db, apiKey.tenantId, parsed.data.project_id)
    if (!project) return errNotFound('Project')
  }

  const status = await getTaskStatusById(db, apiKey.tenantId, parsed.data.status_id)
  if (!status) return errNotFound('Task status')

  if (parsed.data.assignee_id) {
    const member = await getMembership(db, parsed.data.assignee_id as UserId, apiKey.tenantId as TenantId)
    if (!member) return errNotFound('Assignee')
  }

  const task = await createTask(db, apiKey.tenantId, {
    title: parsed.data.title,
    status_id: parsed.data.status_id,
    project_id: parsed.data.project_id ?? null,
    priority: parsed.data.priority ?? 'medium',
    assignee_id: parsed.data.assignee_id ?? null,
    due_date: parsed.data.due_date ?? null,
    reporter_id: apiKey.userId,
    source: 'api' as TaskSource,
  })

  return c.json({
    data: serializeTask(task, ''),
  }, 201)
})

tasksRouter.patch('/:id', async (c) => {
  const apiKey = c.get('apiKey' as never) as ApiKeyContext
  if (!hasScope(apiKey.scopes, 'tasks:write')) {
    return errForbiddenScope('tasks:write')
  }

  const body = await c.req.json().catch(() => null)
  const parsed = updateBodySchema.safeParse(body)
  if (!parsed.success) {
    return errValidation(parsed.error.issues[0]?.message ?? 'Invalid body', parsed.error.issues[0]?.path[0] as string)
  }

  const db = createDb(c.env)

  if (parsed.data.project_id) {
    const project = await getProjectById(db, apiKey.tenantId, parsed.data.project_id)
    if (!project) return errNotFound('Project')
  }
  if (parsed.data.status_id) {
    const status = await getTaskStatusById(db, apiKey.tenantId, parsed.data.status_id)
    if (!status) return errNotFound('Status')
  }
  if (parsed.data.assignee_id) {
    const member = await getMembership(db, parsed.data.assignee_id as UserId, apiKey.tenantId as TenantId)
    if (!member) return errNotFound('Assignee')
  }

  try {
    const task = await updateTask(db, apiKey.tenantId, c.req.param('id'), {
      title: parsed.data.title,
      status_id: parsed.data.status_id,
      project_id: parsed.data.project_id,
      priority: parsed.data.priority,
      assignee_id: parsed.data.assignee_id,
      due_date: parsed.data.due_date,
    })

    return c.json({
      data: serializeTask(task, ''),
    })
  } catch {
    return errNotFound('Task')
  }
})
