/**
 * Contractor portal auth middleware — contractor-portal spec (Task 2).
 *
 * Responsibilities, in order:
 *   1. Origin allow-list on state-mutating methods (POST/PATCH/DELETE) — 403
 *      runs BEFORE any auth work (CSRF defence-in-depth alongside SameSite).
 *   2. Extract the contractor JWT from the `zync_contractor` httpOnly cookie.
 *   3. verifyContractorSession (HS256) — 401 on missing/invalid/expired.
 *   4. Populate c.set('contractor', { contractorId, tenantId }).
 *   5. Renew-on-use: re-issue a fresh 30-day cookie when remaining TTL < 7 days.
 */
import type { MiddlewareHandler } from 'hono'
import { setCookie } from 'hono/cookie'
import type { TenantId } from '@zync/types'
import {
  CONTRACTOR_COOKIE_NAME,
  CONTRACTOR_COOKIE_OPTS,
  CONTRACTOR_RENEW_THRESHOLD_SECONDS,
  CONTRACTOR_SESSION_TTL_SECONDS,
  parseContractorCookie,
  signContractorSession,
  verifyContractorSession,
} from '../contractor-session'

const MUTATING = new Set(['POST', 'PATCH', 'DELETE'])

const CONTRACTOR_APP_ORIGINS = [
  'https://app.zync.is',
  'https://app.dev.zync.is',
  'https://dev.zync.is',
] as const

const WORKERS_DEV_CONTRACTOR_ORIGINS = [
  'https://zync-app.dry-salad-ffa1.workers.dev',
] as const

function isProductionDeploy(environment?: string): boolean {
  if (environment === 'production') return true
  if (environment && environment !== 'production') return false
  return true
}

function getContractorAppOrigins(environment?: string): Set<string> {
  const origins = new Set<string>(CONTRACTOR_APP_ORIGINS)
  if (!isProductionDeploy(environment)) {
    for (const origin of WORKERS_DEV_CONTRACTOR_ORIGINS) origins.add(origin)
  }
  return origins
}

/** Hono Variables contract the contractor portal routes depend on. */
export type ContractorAuthVariables = {
  contractor: { contractorId: string; tenantId: TenantId }
}

type ContractorAuthBindings = {
  JWT_SECRET: string
  ENVIRONMENT?: string
}

export const contractorAuthMiddleware: MiddlewareHandler<{
  Bindings: ContractorAuthBindings
  Variables: ContractorAuthVariables
}> = async (c, next) => {
  if (MUTATING.has(c.req.method)) {
    const origin = c.req.header('Origin')
    const allowedOrigins = getContractorAppOrigins(c.env.ENVIRONMENT)
    if (!origin || !allowedOrigins.has(origin)) {
      return c.json({ error: 'Forbidden: invalid Origin' }, 403)
    }
  }

  const token = parseContractorCookie(c.req.header('Cookie'))
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  let session
  try {
    session = await verifyContractorSession(token, c.env.JWT_SECRET)
  } catch {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  if (session.role !== 'contractor') {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  c.set('contractor', {
    contractorId: session.sub,
    tenantId: session.tenantId,
  })

  const now = Math.floor(Date.now() / 1000)
  const remaining = session.exp - now
  if (remaining < CONTRACTOR_RENEW_THRESHOLD_SECONDS) {
    const renewed = await signContractorSession(
      { sub: session.sub, tenantId: session.tenantId },
      c.env.JWT_SECRET,
    )
    setCookie(c, CONTRACTOR_COOKIE_NAME, renewed, {
      ...CONTRACTOR_COOKIE_OPTS,
      maxAge: CONTRACTOR_SESSION_TTL_SECONDS,
    })
  }

  await next()
}
