import { config } from './config.js';
import { hydrateRequestAuth } from './auth-context.js';
import { getHeader, HttpError, type RequestContext } from './http.js';

export type JsonObject = Record<string, unknown>;

function legacyTenantIdFrom(ctx: RequestContext) {
  return getHeader(ctx.req, 'x-tenant-id') || ctx.url.searchParams.get('tenantId');
}

function legacyUserIdFrom(ctx: RequestContext, body?: JsonObject) {
  return (
    getHeader(ctx.req, 'x-user-id') ||
    ctx.url.searchParams.get('userId') ||
    (typeof body?.userId === 'string' ? body.userId : '')
  );
}

export async function tenantIdFrom(ctx: RequestContext) {
  const auth = await hydrateRequestAuth(ctx);
  const legacyTenantId = legacyTenantIdFrom(ctx);
  if (auth.session) {
    if (legacyTenantId && legacyTenantId !== auth.session.tenantId) {
      throw new HttpError(403, 'Request tenant does not match the authenticated session', 'AUTH_TENANT_MISMATCH');
    }
    if (!auth.session.tenantId) {
      throw new HttpError(400, 'Tenant context is required for this authenticated request', 'TENANT_ID_REQUIRED');
    }
    return auth.session.tenantId;
  }

  if (!legacyTenantId) {
    throw new HttpError(400, 'x-tenant-id header or tenantId query is required', 'TENANT_ID_REQUIRED');
  }
  return legacyTenantId;
}

export async function userIdFrom(ctx: RequestContext, body?: JsonObject) {
  const auth = await hydrateRequestAuth(ctx);
  if (auth.session) {
    const legacyUserId = legacyUserIdFrom(ctx, body);
    if (legacyUserId && legacyUserId !== auth.session.id) {
      throw new HttpError(403, 'Request user identity does not match the authenticated session', 'AUTH_USER_MISMATCH');
    }
    return auth.session.id;
  }
  if (auth.bearerToken) {
    throw new HttpError(401, 'Invalid or expired session', 'AUTH_SESSION_INVALID');
  }

  const userId = legacyUserIdFrom(ctx, body);
  if (!userId) {
    throw new HttpError(400, 'x-user-id header, userId query, or userId body is required', 'USER_ID_REQUIRED');
  }
  if (!config.allowLegacyAuthHeaders) {
    throw new HttpError(401, 'User identity must be resolved from a trusted session in this environment', 'TRUSTED_USER_REQUIRED');
  }
  return userId;
}

export async function optionalUserIdFrom(ctx: RequestContext, body?: JsonObject) {
  const auth = await hydrateRequestAuth(ctx);
  if (auth.session) {
    const legacyUserId = legacyUserIdFrom(ctx, body);
    if (legacyUserId && legacyUserId !== auth.session.id) {
      throw new HttpError(403, 'Request user identity does not match the authenticated session', 'AUTH_USER_MISMATCH');
    }
    return auth.session.id;
  }
  if (auth.bearerToken) {
    throw new HttpError(401, 'Invalid or expired session', 'AUTH_SESSION_INVALID');
  }
  if (!config.allowLegacyAuthHeaders) return '';
  return legacyUserIdFrom(ctx, body);
}

export function intParam(ctx: RequestContext, name: string, fallback: number, max = 500) {
  const value = Number(ctx.url.searchParams.get(name) || fallback);
  if (!Number.isFinite(value) || value <= 0) return fallback;
  return Math.min(Math.trunc(value), max);
}

export function stringParam(ctx: RequestContext, name: string) {
  return ctx.url.searchParams.get(name)?.trim() || '';
}

export interface ReadJsonBodyOptions {
  maxBytes?: number;
}

export async function readJsonBody(ctx: RequestContext, options: ReadJsonBodyOptions = {}): Promise<JsonObject> {
  const maxBytes = options.maxBytes ?? config.maxJsonBodyBytes;
  const contentLength = Number(getHeader(ctx.req, 'content-length') || 0);
  if (Number.isFinite(contentLength) && contentLength > maxBytes) {
    throw new HttpError(413, `JSON body is too large. Max ${maxBytes} bytes.`, 'JSON_BODY_TOO_LARGE');
  }

  const chunks: Buffer[] = [];
  let totalBytes = 0;

  for await (const chunk of ctx.req) {
    const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
    totalBytes += buffer.length;
    if (totalBytes > maxBytes) {
      ctx.req.destroy();
      throw new HttpError(413, `JSON body is too large. Max ${maxBytes} bytes.`, 'JSON_BODY_TOO_LARGE');
    }
    chunks.push(buffer);
  }

  const raw = Buffer.concat(chunks).toString('utf8').trim();
  if (!raw) return {};

  try {
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
      throw new HttpError(400, 'JSON body must be an object', 'INVALID_JSON_BODY');
    }
    return parsed as JsonObject;
  } catch (error) {
    if (error instanceof HttpError) throw error;
    throw new HttpError(400, 'Invalid JSON body', 'INVALID_JSON_BODY');
  }
}

export function requiredString(body: JsonObject, key: string) {
  const value = body[key];
  if (typeof value !== 'string' || !value.trim()) {
    throw new HttpError(400, `${key} is required`, 'REQUIRED_FIELD');
  }
  return value.trim();
}

export function optionalString(body: JsonObject, key: string) {
  const value = body[key];
  return typeof value === 'string' && value.trim() ? value.trim() : '';
}

export function optionalInteger(body: JsonObject, key: string, fallback: number) {
  const value = Number(body[key] ?? fallback);
  return Number.isFinite(value) ? Math.trunc(value) : fallback;
}

export function optionalStringArray(body: JsonObject, key: string): string[] {
  const value = body[key];
  if (!Array.isArray(value)) return [];
  return value.map(item => String(item)).filter(Boolean);
}

function platformPermissionAllowed(permissions: Record<string, unknown> | null | undefined, permission: string) {
  if (!permission) return true;
  if (!permissions || typeof permissions !== 'object') return false;
  if (permissions['*'] === true) return true;
  if (permissions[permission] === true) return true;

  const parts = permission.split(':');
  for (let i = parts.length - 1; i >= 1; i -= 1) {
    const wildcard = `${parts.slice(0, i).join(':')}:*`;
    if (permissions[wildcard] === true) return true;
  }
  return false;
}

export async function requirePlatformAdmin(ctx: RequestContext, permission = '') {
  const auth = await hydrateRequestAuth(ctx);
  if (auth.session?.primaryRole === 'platform_admin') {
    if (!permission || platformPermissionAllowed(auth.session.platformPermissions, permission)) return;
    throw new HttpError(403, `Platform permission ${permission} is required`, 'PLATFORM_PERMISSION_REQUIRED');
  }
  if (auth.bearerToken) {
    throw new HttpError(403, 'Platform admin access is required', 'PLATFORM_ADMIN_REQUIRED');
  }

  const provided = getHeader(ctx.req, 'x-platform-admin-key');
  if (provided && !config.allowPlatformAdminKey) {
    throw new HttpError(401, 'Platform admin API key is disabled in this environment', 'PLATFORM_ADMIN_KEY_DISABLED');
  }
  if (!provided || provided !== config.platformAdminApiKey) {
    throw new HttpError(403, 'Platform admin access is required', 'PLATFORM_ADMIN_REQUIRED');
  }
}
