import { Hono } from 'hono'
import type { AppEnv } from '../../types'
import type { SessionPayload } from '@zync/types'
import { requirePermission } from '../../middleware/guards'
import {
  MilestoneInvoicedError,
  assertTenantOwnsInvoice,
  completeMilestone,
  createMilestone,
  deleteMilestone,
  getInvoice,
  getMilestone,
  getMilestoneSummary,
  getProjectById,
  listMilestones,
  reopenMilestone,
  setMilestoneInvoice,
  updateMilestone,
} from '@zync/db/queries'
import { createMilestoneSchema, updateMilestoneSchema } from '@zync/types'

export const projectMilestoneRoutes = new Hono<AppEnv>()

type TenantUserSession = SessionPayload & {
  type: 'user'
  tid: NonNullable<SessionPayload['tid']>
}

function getUserSession(c: { get: (key: string) => unknown }) {
  const session = c.get('session') as AppEnv['Variables']['session']
  if (!session || session.type !== 'user' || !session.tid) {
    return null
  }
  return session as TenantUserSession
}

async function loadProject(db: AppEnv['Variables']['db'], tenantId: string, projectId: string) {
  return getProjectById(db, tenantId, projectId)
}

async function loadMilestoneForProject(
  db: AppEnv['Variables']['db'],
  tenantId: string,
  projectId: string,
  milestoneId: string,
) {
  const milestone = await getMilestone(db, tenantId, milestoneId)
  if (!milestone || milestone.projectId !== projectId) return null
  return milestone
}

projectMilestoneRoutes.get('/:id/milestones', requirePermission('projects:read'), async (c) => {
  const session = getUserSession(c)
  if (!session) return c.json({ error: 'Unauthorized' }, 401)

  const db = c.get('db')
  const projectId = c.req.param('id')
  const project = await loadProject(db, session.tid, projectId)
  if (!project) return c.json({ error: 'Project not found' }, 404)

  const milestones = await listMilestones(db, session.tid, projectId)
  const summary = await getMilestoneSummary(db, session.tid, projectId, project.billing_config)
  return c.json({ milestones, summary }, 200)
})

projectMilestoneRoutes.post('/:id/milestones', requirePermission('projects:write'), async (c) => {
  const session = getUserSession(c)
  if (!session) return c.json({ error: 'Unauthorized' }, 401)

  const parsed = createMilestoneSchema.safeParse(await c.req.json())
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }

  const db = c.get('db')
  const projectId = c.req.param('id')
  const project = await loadProject(db, session.tid, projectId)
  if (!project) return c.json({ error: 'Project not found' }, 404)

  const milestone = await createMilestone(db, session.tid, projectId, parsed.data, session.sub)
  return c.json({ milestone }, 201)
})

projectMilestoneRoutes.patch('/:id/milestones/:mid', requirePermission('projects:write'), async (c) => {
  const session = getUserSession(c)
  if (!session) return c.json({ error: 'Unauthorized' }, 401)

  const db = c.get('db')
  const projectId = c.req.param('id')
  const milestoneId = c.req.param('mid')
  const milestone = await loadMilestoneForProject(db, session.tid, projectId, milestoneId)
  if (!milestone) return c.json({ error: 'Milestone not found' }, 404)

  const parsed = updateMilestoneSchema.safeParse(await c.req.json())
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }
  const invoiceId = parsed.data.invoice_id

  if (invoiceId !== undefined) {
    const ownsInvoice = await assertTenantOwnsInvoice(db, session.tid, invoiceId)
    if (!ownsInvoice) return c.json({ error: 'Invoice not found' }, 404)
    const invoice = await getInvoice(db, session.tid, invoiceId)
    if (!invoice || invoice.projectId !== projectId) {
      return c.json({ error: 'Invoice not found' }, 404)
    }
    const linked = await setMilestoneInvoice(db, session.tid, milestoneId, invoiceId)
    if (!linked) return c.json({ error: 'Milestone not found' }, 404)
    return c.json({ milestone: linked }, 200)
  }

  const updated = await updateMilestone(db, session.tid, milestoneId, parsed.data)
  if (!updated) return c.json({ error: 'Milestone not found' }, 404)
  return c.json({ milestone: updated }, 200)
})

projectMilestoneRoutes.post('/:id/milestones/:mid/complete', requirePermission('projects:write'), async (c) => {
  const session = getUserSession(c)
  if (!session) return c.json({ error: 'Unauthorized' }, 401)

  const db = c.get('db')
  const projectId = c.req.param('id')
  const milestoneId = c.req.param('mid')
  const milestone = await loadMilestoneForProject(db, session.tid, projectId, milestoneId)
  if (!milestone) return c.json({ error: 'Milestone not found' }, 404)

  const updated = await completeMilestone(db, session.tid, milestoneId)
  return c.json({ milestone: updated }, 200)
})

projectMilestoneRoutes.post('/:id/milestones/:mid/reopen', requirePermission('projects:write'), async (c) => {
  const session = getUserSession(c)
  if (!session) return c.json({ error: 'Unauthorized' }, 401)

  const db = c.get('db')
  const projectId = c.req.param('id')
  const milestoneId = c.req.param('mid')
  const milestone = await loadMilestoneForProject(db, session.tid, projectId, milestoneId)
  if (!milestone) return c.json({ error: 'Milestone not found' }, 404)

  try {
    const updated = await reopenMilestone(db, session.tid, milestoneId)
    return c.json({ milestone: updated }, 200)
  } catch (error) {
    if (error instanceof MilestoneInvoicedError) {
      return c.json({ code: error.code, message: error.message }, 422)
    }
    throw error
  }
})

projectMilestoneRoutes.delete('/:id/milestones/:mid', requirePermission('projects:write'), async (c) => {
  const session = getUserSession(c)
  if (!session) return c.json({ error: 'Unauthorized' }, 401)

  const db = c.get('db')
  const projectId = c.req.param('id')
  const milestoneId = c.req.param('mid')
  const milestone = await loadMilestoneForProject(db, session.tid, projectId, milestoneId)
  if (!milestone) return c.json({ error: 'Milestone not found' }, 404)

  try {
    await deleteMilestone(db, session.tid, milestoneId)
    return c.body(null, 204)
  } catch (error) {
    if (error instanceof MilestoneInvoicedError) {
      return c.json({ code: error.code, message: error.message }, 422)
    }
    throw error
  }
})
