/**
 * Settings — Module Management API routes.
 *
 * GET  /api/settings/modules              — list all 14 toggleable modules + state
 * GET  /api/settings/modules/:moduleId/impact — preview impact without committing
 * PATCH /api/settings/modules/:moduleId    — enable or disable a module
 *
 * Permission: settings:modules:read (GET), settings:modules:write (PATCH).
 * Tenant inferred from session JWT `tid` claim.
 *
 * No raw Drizzle tables here — all DB access via query helpers per
 * `no-raw-drizzle-from-routes` ESLint rule.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import {
  getTenantModules,
  getEnabledModuleIds,
  setModuleStates,
} from '@zync/db/queries'
import {
  MODULE_BY_ID,
  MODULE_CARD_ORDER,
  getCascadeDisables,
  getSoftImpacts,
  canEnable,
  type ModuleId,
} from '@zync/modules'
import type { SessionPayload } from '@zync/types'

export const settingsModulesRoutes = new Hono<AppEnv>()

settingsModulesRoutes.use('*', authMiddleware)

const patchBodySchema = z.object({ enabled: z.boolean() })
const impactQuerySchema = z.object({
  action: z.enum(['disable', 'enable']).default('disable'),
})

// ---------------------------------------------------------------------------
// GET /api/settings/modules
// ---------------------------------------------------------------------------
settingsModulesRoutes.get(
  '/',
  requirePermission('settings:modules:read'),
  async (c) => {
    const session = c.get('session') as SessionPayload
    const tenantId = session.tid!

    const db = c.get('db')
    const [rows, enabledIds] = await Promise.all([
      getTenantModules(db, tenantId),
      getEnabledModuleIds(db, tenantId),
    ])

    const rowByModule = new Map(rows.map((r) => [r.moduleId, r]))

    // Build the reverse dependency index: moduleId -> modules that depend on it
    const reverseDeps = new Map<ModuleId, Array<{ moduleId: ModuleId; kind: 'hard' | 'soft' }>>()
    for (const id of MODULE_CARD_ORDER) {
      const def = MODULE_BY_ID[id]
      for (const dep of def.dependencies) {
        const existing = reverseDeps.get(dep.moduleId) ?? []
        existing.push({ moduleId: id, kind: dep.kind })
        reverseDeps.set(dep.moduleId, existing)
      }
    }

    const modules = MODULE_CARD_ORDER.map((id) => {
      const def = MODULE_BY_ID[id]
      const row = rowByModule.get(id)
      const isEnabled = enabledIds.includes(id)

      const canEnableResult = canEnable(id, enabledIds)

      return {
        id,
        displayName: def.displayName,
        enabled: isEnabled,
        enabledAt: row?.enabledAt?.toISOString() ?? null,
        disabledAt: row?.disabledAt?.toISOString() ?? null,
        managedBySystemAdmin: row?.overrideBySystemAdmin ?? false,
        alwaysOn: def.alwaysOn,
        canDisable: !def.alwaysOn && isEnabled,
        canEnable: canEnableResult.allowed,
        blockedBy: canEnableResult.missingHardDeps,
        dependencies: def.dependencies.map((dep) => ({
          moduleId: dep.moduleId,
          kind: dep.kind,
          enabled: enabledIds.includes(dep.moduleId),
        })),
        dependedOnBy: (reverseDeps.get(id) ?? []).map((rev) => ({
          moduleId: rev.moduleId,
          kind: rev.kind,
          enabled: enabledIds.includes(rev.moduleId),
        })),
      }
    })

    return c.json({ modules })
  },
)

// ---------------------------------------------------------------------------
// GET /api/settings/modules/:moduleId/impact
// ---------------------------------------------------------------------------
settingsModulesRoutes.get(
  '/:moduleId/impact',
  requirePermission('settings:modules:read'),
  async (c) => {
    const session = c.get('session') as SessionPayload
    const tenantId = session.tid!
    const moduleId = c.req.param('moduleId') as ModuleId

    // Validate query params
    const queryResult = impactQuerySchema.safeParse(
      Object.fromEntries(new URL(c.req.url).searchParams),
    )
    if (!queryResult.success) {
      return c.json({ error: 'invalid_query', issues: queryResult.error.issues }, 400)
    }
    const { action } = queryResult.data

    // Validate moduleId is known
    if (!(moduleId in MODULE_BY_ID)) {
      return c.json({ error: 'not_found' }, 404)
    }
    const def = MODULE_BY_ID[moduleId]
    if (def.alwaysOn) {
      return c.json({ error: 'module_always_on', module: moduleId }, 422)
    }

    const db = c.get('db')
    const enabledIds = await getEnabledModuleIds(db, tenantId)

    if (action === 'disable') {
      const cascadeDisables = getCascadeDisables(moduleId, enabledIds)
      const softImpacts = getSoftImpacts(moduleId, enabledIds)
      return c.json({
        action,
        moduleId,
        cascadeDisables,
        softImpacts,
        blockedBy: [] as ModuleId[],
      })
    } else {
      const result = canEnable(moduleId, enabledIds)
      return c.json({
        action,
        moduleId,
        cascadeDisables: [] as ModuleId[],
        softImpacts: [] as Array<{ moduleId: ModuleId; impactDescription: string }>,
        blockedBy: result.missingHardDeps,
      })
    }
  },
)

// ---------------------------------------------------------------------------
// PATCH /api/settings/modules/:moduleId
// ---------------------------------------------------------------------------
settingsModulesRoutes.patch(
  '/:moduleId',
  requirePermission('settings:modules:write'),
  async (c) => {
    const session = c.get('session') as SessionPayload
    const tenantId = session.tid!
    const actorUserId = session.sub
    const moduleId = c.req.param('moduleId') as ModuleId

    // Validate request body
    const body = await c.req.json().catch(() => null)
    const bodyResult = patchBodySchema.safeParse(body)
    if (!bodyResult.success) {
      return c.json({ error: 'invalid_body', issues: bodyResult.error.issues }, 400)
    }
    const { enabled } = bodyResult.data

    // Validate moduleId is known
    if (!(moduleId in MODULE_BY_ID)) {
      return c.json({ error: 'not_found' }, 404)
    }

    // system module cannot be toggled
    if (moduleId === 'system') {
      return c.json({ error: 'module_always_on', module: 'system' }, 422)
    }

    const db = c.get('db')

    // Check admin override
    const rows = await getTenantModules(db, tenantId)
    const row = rows.find((r) => r.moduleId === moduleId)
    if (row?.overrideBySystemAdmin) {
      return c.json({ error: 'forbidden' }, 403)
    }

    const enabledIds = await getEnabledModuleIds(db, tenantId)

    if (enabled) {
      // Enabling: check hard dependencies
      const result = canEnable(moduleId, enabledIds)
      if (!result.allowed) {
        return c.json(
          { error: 'missing_hard_dependencies', missingDeps: result.missingHardDeps },
          400,
        )
      }

      await setModuleStates(db, tenantId, [{ moduleId, enabled: true }], actorUserId)

      return c.json({ updated: [moduleId], softImpacts: [] })
    } else {
      // Disabling: cascade hard-deps and report soft impacts
      const cascadeDisables = getCascadeDisables(moduleId, enabledIds)
      const softImpacts = getSoftImpacts(moduleId, enabledIds)

      const allToDisable: ModuleId[] = [moduleId, ...cascadeDisables]
      await setModuleStates(
        db,
        tenantId,
        allToDisable.map((id) => ({ moduleId: id, enabled: false })),
        actorUserId,
      )

      return c.json({ updated: allToDisable, softImpacts })
    }
  },
)
