/**
 * Request-level product context helpers.
 *
 * Extract and validate productId from every request:
 *   1. JWT token payload (authenticated requests)
 *   2. X-Product-Id header (unauthenticated requests)
 *   3. PRODUCT_ID env var fallback (backward compat during migration)
 *
 * Validates against the products registry — rejects unknown or disabled products.
 */

import type { FastifyRequest } from 'fastify';
import { BadRequestError, ForbiddenError } from './errors.js';
import { isValidProduct, getProduct } from '../modules/products/cache.js';
import type { ProductDoc } from '../modules/products/types.js';
import { autoRegisterProduct } from './auto-register.js';
import { extractAuth } from './auth.js';

/** JWT payload shape attached to req by the onRequest hook (see Commit 3). */
export interface JwtPayload {
  sub: string;
  email?: string;
  role?: string;
  productId?: string;
  type?: string;
}

// Augment Fastify request to include parsed JWT payload
declare module 'fastify' {
  interface FastifyRequest {
    jwtPayload?: JwtPayload;
  }
}

/**
 * Extract raw productId from request without status validation.
 * Priority: JWT token > X-Product-Id header > env fallback (dev only).
 * Validates the product exists in the registry but does NOT check status.
 */
export async function extractProductIdAsync(req: FastifyRequest): Promise<string> {
  // 1. From JWT (set during login/register, attached by onRequest hook)
  let id = req.jwtPayload?.productId;

  // 2. From header (unauthenticated requests like license activation)
  if (!id) {
    const header = req.headers['x-product-id'];
    if (typeof header === 'string' && header.length > 0) id = header;
  }

  // 3. Fallback to env var (backward compat during migration, dev only)
  if (!id) {
    const envFallback = process.env.PRODUCT_ID;
    if (envFallback) id = envFallback;
  }

  if (!id) throw new BadRequestError('productId is required (via JWT or X-Product-Id header)');

  // Auto-register unknown products when requester has a valid JWT
  if (!isValidProduct(id) && req.jwtPayload?.sub) {
    const result = await autoRegisterProduct(id, req.jwtPayload.sub);
    if (
      !result.registered &&
      result.reason !== 'Already exists' &&
      result.reason !== 'Concurrent registration'
    ) {
      throw new BadRequestError(
        `Unknown product: ${id} (auto-registration failed: ${result.reason})`
      );
    }
  } else if (!isValidProduct(id)) {
    throw new BadRequestError(`Unknown product: ${id}`);
  }

  return id;
}

/** Synchronous wrapper kept for backward compatibility on hot paths. */
function extractProductId(req: FastifyRequest): string {
  // 1. From JWT (set during login/register, attached by onRequest hook)
  let id = req.jwtPayload?.productId;

  // 2. From header (unauthenticated requests like license activation)
  if (!id) {
    const header = req.headers['x-product-id'];
    if (typeof header === 'string' && header.length > 0) id = header;
  }

  // 3. Fallback to env var (backward compat during migration, dev only)
  if (!id) {
    const envFallback = process.env.PRODUCT_ID;
    if (envFallback) id = envFallback;
  }

  if (!id) throw new BadRequestError('productId is required (via JWT or X-Product-Id header)');
  if (!isValidProduct(id)) throw new BadRequestError(`Unknown product: ${id}`);

  return id;
}

/**
 * Extract and validate productId from the request.
 * Blocks: `draft`, `sunset`, `disabled` (only `pre_launch`, `beta`, `active` pass).
 */
export function getRequestProductId(req: FastifyRequest): string {
  const id = extractProductId(req);
  const product = getProduct(id)!;

  const blockedStatuses = ['draft', 'sunset', 'disabled'] as const;
  if ((blockedStatuses as readonly string[]).includes(product.status)) {
    throw new BadRequestError(`Product ${id} is not available (status: ${product.status})`);
  }

  return id;
}

/**
 * Extract productId with relaxed status gating — permits `pre_launch`.
 * Used by public waitlist routes where the product isn't fully operational yet.
 * Blocks: `draft`, `sunset`, `disabled`.
 */
export function getRequestProductIdForPublic(req: FastifyRequest): string {
  const id = extractProductId(req);
  const product = getProduct(id)!;

  if (product.status === 'draft' || product.status === 'sunset' || product.status === 'disabled') {
    throw new BadRequestError(`Product ${id} is not available (status: ${product.status})`);
  }

  return id;
}

/**
 * Get the full product config for the current request's productId.
 * Useful when modules need product-specific values (trial days, device limits, etc.).
 */
export function getRequestProductConfig(req: FastifyRequest): ProductDoc {
  const id = getRequestProductId(req);
  return getProduct(id)!;
}

/**
 * `FLEET_TENANT_ENFORCEMENT` env gate — default OFF. When OFF, tenant scoping is
 * advisory only (the dashboard shows you your projects, but the API does not
 * reject cross-tenant access) — byte-for-byte the current behavior. When ON, the
 * API enforces that a caller may only act on products they own (admins exempt).
 */
export function isTenantEnforcementEnabled(): boolean {
  const v = (process.env.FLEET_TENANT_ENFORCEMENT ?? '').trim().toLowerCase();
  return v === '1' || v === 'true' || v === 'on' || v === 'yes';
}

/**
 * Resolve the request's productId AND authorize the caller for it (multi-tenant
 * guard, §tenancy). Resolution + status gating is unchanged (`getRequestProductId`);
 * the access check is additive and flag-gated:
 *
 *  - enforcement OFF ⇒ returns the resolved id (no behavior change).
 *  - admins / super_admins ⇒ always allowed (operators see everything).
 *  - otherwise ⇒ allowed only when the product is owned by the caller, OR is
 *    owner-less/legacy (grace for products created before ownership tracking;
 *    migrate them to lock down fully). A product owned by SOMEONE ELSE ⇒ 403.
 *
 * Async because it needs the verified auth identity. Use this on tenant-scoped
 * surfaces (e.g. the fleet control plane) in place of `getRequestProductId`.
 */
export async function requireProductAccess(req: FastifyRequest): Promise<string> {
  const id = getRequestProductId(req);
  if (!isTenantEnforcementEnabled()) return id;

  const auth = await extractAuth(req);
  if (auth.role === 'admin' || auth.role === 'super_admin') return id;

  const product = getProduct(id);
  if (product?.ownerId && product.ownerId !== auth.sub) {
    throw new ForbiddenError(`Not authorized for product '${id}'`);
  }
  return id;
}
