/**
 * Tenant onboarding wizard API — onboarding spec (Tasks 2–4).
 *
 * GET   /api/onboarding/state    → OnboardingState
 * PATCH /api/onboarding/progress → { ok: true, onboarding_step: number }
 *
 * OWNER/ADMIN only. Zod validation on PATCH body.
 */
import { Hono } from 'hono'
import type { ModuleId } from '@zync/modules'
import type { SessionPayload } from '@zync/types'
import { authMiddleware } from '../middleware/auth'
import { isOwnerOrAdminRole } from '../lib/system-roles'
import {
  createDb,
  getEnabledModuleIds,
  getTenantById,
} from '@zync/db/queries'
import type { AppEnv } from '../types'
import {
  ONBOARDING_MODULE_SLUGS,
  SLUG_TO_MODULE_ID,
  onboardingProgressSchema,
  onboardingLogoSchema,
  onboardingEmailTestSchema,
  type OnboardingState,
} from './onboarding.schema'
import { countTenantInvitations, deriveEmailAdapter } from '../services/onboarding-queries'
import { persistOnboardingProgress } from '../services/onboarding-business'
import { OnboardingModuleValidationError } from '../services/onboarding-modules'
import { OnboardingSeatCapError } from '../services/onboarding-invites'
import { createOnboardingLogoUpload } from '../services/onboarding-logo'
import { testSmtpConnection } from '../services/onboarding-email-test'

export const onboardingRoute = new Hono<AppEnv>()

onboardingRoute.use('*', authMiddleware)

function requireOwnerOrAdmin(session: SessionPayload | undefined): session is SessionPayload {
  if (!session || session.type !== 'user' || !session.tid) return false
  return isOwnerOrAdminRole(session.role)
}

function buildModulesMap(enabledIds: ModuleId[]): Record<string, boolean> {
  const enabled = new Set(enabledIds)
  const modules: Record<string, boolean> = {}
  for (const slug of ONBOARDING_MODULE_SLUGS) {
    modules[slug] = enabled.has(SLUG_TO_MODULE_ID[slug])
  }
  return modules
}

function readBusinessInfo(
  name: string,
  settings: Record<string, unknown>,
): OnboardingState['data']['business_info'] {
  const businessType = settings['business_type']
  const info: OnboardingState['data']['business_info'] = {}

  const businessName = settings['business_name']
  if (typeof businessName === 'string' && businessName.length > 0) {
    info.business_name = businessName
  } else if (name) {
    info.business_name = name
  }

  if (
    businessType === 'osek_morshe' ||
    businessType === 'company_ltd' ||
    businessType === 'osek_patur'
  ) {
    info.business_type = businessType
  }

  const taxId = settings['tax_id']
  if (typeof taxId === 'string' && taxId.length > 0) info.tax_id = taxId

  const logoUrl = settings['logo_url']
  if (typeof logoUrl === 'string' && logoUrl.length > 0) info.logo_url = logoUrl

  const phone = settings['phone']
  if (typeof phone === 'string' && phone.length > 0) info.phone = phone

  const address = settings['address']
  if (typeof address === 'string' && address.length > 0) info.address = address

  return info
}

// GET /api/onboarding/state
onboardingRoute.get('/state', async (c) => {
  const session = c.get('session') as SessionPayload | undefined
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!requireOwnerOrAdmin(session)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const db = c.get('db') ?? createDb(c.env)
  const tenant = await getTenantById(db, session.tid)
  if (!tenant) {
    return c.json({ error: 'Tenant not found' }, 404)
  }

  const settings = (tenant.settings ?? {}) as Record<string, unknown>
  const [enabledIds, emailAdapter, invitesSent] = await Promise.all([
    getEnabledModuleIds(db, session.tid),
    deriveEmailAdapter(db, session.tid),
    countTenantInvitations(db, session.tid),
  ])

  const state: OnboardingState = {
    onboarding_completed: tenant.onboardingCompleted,
    onboarding_step: tenant.onboardingStep,
    data: {
      business_info: readBusinessInfo(tenant.name, settings),
      modules: buildModulesMap(enabledIds),
      email_adapter: emailAdapter,
      invites_sent: invitesSent,
    },
  }

  return c.json(state, 200)
})

// PATCH /api/onboarding/progress
onboardingRoute.patch('/progress', async (c) => {
  const session = c.get('session') as SessionPayload | undefined
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!requireOwnerOrAdmin(session)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

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

  const db = c.get('db') ?? createDb(c.env)
  const tenant = await getTenantById(db, session.tid)
  if (!tenant) {
    return c.json({ error: 'Tenant not found' }, 404)
  }

  if (tenant.onboardingCompleted) {
    return c.json({ error: 'Onboarding already completed' }, 409)
  }

  const currentStep = tenant.onboardingStep
  const { step } = parsed.data
  if (step !== currentStep && step !== currentStep + 1) {
    return c.json({ error: 'Step out of sequence' }, 400)
  }

  const newStep = Math.max(currentStep, step)
  const markCompleted = parsed.data.step === 5 && parsed.data.complete === true

  const ip = c.req.header('CF-Connecting-IP') ?? null
  const requestId = c.req.header('X-Request-Id') ?? null

  try {
    await persistOnboardingProgress(db, {
      tenantId: session.tid,
      actorId: session.sub,
      step: parsed.data,
      currentStep,
      newStep,
      markCompleted,
      env: c.env,
      actorIp: ip,
      requestId,
    })
  } catch (err) {
    if (err instanceof OnboardingSeatCapError) {
      return c.json({ error: 'Upgrade required', requiredTier: 'business' }, 402)
    }
    if (err instanceof OnboardingModuleValidationError) {
      if (err.code === 'missing_hard_dependencies') {
        return c.json(
          { error: 'missing_hard_dependencies', missingDeps: err.missingDeps },
          400,
        )
      }
      return c.json({ error: err.message }, 400)
    }
    throw err
  }

  return c.json({ ok: true, onboarding_step: newStep }, 200)
})

// POST /api/onboarding/logo
onboardingRoute.post('/logo', async (c) => {
  const session = c.get('session') as SessionPayload | undefined
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!requireOwnerOrAdmin(session)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

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

  const result = await createOnboardingLogoUpload(
    c.env,
    session.tid,
    parsed.data.content_type,
    parsed.data.file_size_bytes,
  )
  return c.json(result, 200)
})

// POST /api/onboarding/email/test
onboardingRoute.post('/email/test', async (c) => {
  const session = c.get('session') as SessionPayload | undefined
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!requireOwnerOrAdmin(session)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'
  const rl = await (async () => {
    try {
      const result = await c.env.RATE_LIMITER_AUTH?.limit({ key: `onboarding-email-test:${ip}` })
      return result ?? { success: true }
    } catch {
      return { success: true }
    }
  })()
  if (!rl.success) {
    return c.json({ error: 'Too many requests' }, 429)
  }

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

  const { host, port, username, password } = parsed.data
  const result = await testSmtpConnection({ host, port, username, password })
  return c.json(result, 200)
})
