import { prisma } from '@/lib/db'
import { AUDIT_ACTIONS, writeAudit } from '@/lib/audit'
import { apiRoute } from '@/lib/api/handler'
import { ApiError, ERROR_CODES } from '@/lib/api/errors'
import { settingsUpdateSchema, type SettingsUpdateInput } from '@/lib/api/schemas'
import { invalidateGridCaches } from '@/lib/services/grid-service'

export const dynamic = 'force-dynamic'

export const GET = apiRoute({ permission: 'settings.read' }, async () => {
  return prisma.appSetting.findMany({ orderBy: [{ groupKey: 'asc' }, { key: 'asc' }] })
})

/**
 * Update platform settings.
 *
 * Bounds declared on each setting row are enforced here rather than trusted from the
 * form, and the risk-weight cache is invalidated on write so the next request scores
 * with the new values. Both before and after are audited: a change to a risk weight
 * changes every number the platform reports, and that must be traceable.
 */
export const PATCH = apiRoute<Record<string, string>, SettingsUpdateInput>(
  { permission: 'settings.manage', bodySchema: settingsUpdateSchema },
  async ({ body, user, request }) => {
    const keys = Object.keys(body.values)
    const existing = await prisma.appSetting.findMany({ where: { key: { in: keys } } })
    const byKey = new Map(existing.map((setting) => [setting.key, setting]))

    const invalid: Record<string, string> = {}
    for (const [key, raw] of Object.entries(body.values)) {
      const setting = byKey.get(key)
      if (!setting) {
        invalid[key] = 'Unknown setting'
        continue
      }
      if (setting.valueType === 'number') {
        const value = Number(raw)
        if (!Number.isFinite(value)) {
          invalid[key] = 'Must be a number'
        } else if (setting.minValue !== null && value < setting.minValue) {
          invalid[key] = `Minimum is ${setting.minValue}`
        } else if (setting.maxValue !== null && value > setting.maxValue) {
          invalid[key] = `Maximum is ${setting.maxValue}`
        }
      } else if (setting.valueType === 'boolean' && raw !== 'true' && raw !== 'false') {
        invalid[key] = 'Must be true or false'
      }
    }

    if (Object.keys(invalid).length > 0) {
      throw new ApiError(ERROR_CODES.validationFailed, { details: invalid })
    }

    const before = Object.fromEntries(existing.map((setting) => [setting.key, setting.value]))

    await prisma.$transaction(
      Object.entries(body.values).map(([key, value]) =>
        prisma.appSetting.update({ where: { key }, data: { value } }),
      ),
    )

    invalidateGridCaches('weights')

    await writeAudit({
      action: AUDIT_ACTIONS.settingChanged,
      entity: 'app_setting',
      user,
      request,
      before,
      after: body.values,
      detail: `Updated ${keys.length} setting(s)`,
    })

    return { updated: keys.length }
  },
)
