/**
 * Storage Feature — API Routes
 *
 * Serves files from storage with path-based access control.
 *
 * Auth note: this route is declared as auth: 'public' in the route definition
 * because access policy depends on the file path, not a single role.
 * The handler enforces its own rules:
 *   - Public prefix paths  → no session required
 *   - All other paths      → session required; access governed by registered policies
 *   - Unknown paths        → denied
 *
 * Call registerStoragePolicies() and registerStoragePublicPrefixes() during
 * initializeZen before the first request, following the same pattern as
 * registerFeatureRoutes in core/api/runtime.js.
 */

import { getSessionCookieName } from '@zen/core/shared/config';
import { getSessionResolver } from '../api/router.js';
import { getFile } from './index.js';
import { fail } from '@zen/core/shared/logger';
import { defineApiRoutes } from '../api/define.js';
import { apiError } from '../api/respond.js';
import { getStoragePublicPrefixes, getStorageAccessPolicies } from './storage-config.js';

const COOKIE_NAME = getSessionCookieName();

// ─── Handlers ─────────────────────────────────────────────────────────────────

async function handleGetFile(_request, { wildcard: fileKey }) {
  try {
    if (!fileKey) {
      return apiError('Bad Request', 'File path is required');
    }

    // Reject path traversal sequences, empty segments, and null bytes before
    // passing the key to the storage backend. Next.js decodes percent-encoding
    // before populating [...path], so '..' and '.' arrive as literal values.
    const pathParts = fileKey.split('/');
    if (
      pathParts.some(seg => seg === '..' || seg === '.' || seg === '') ||
      fileKey.includes('\0')
    ) {
      return apiError('Bad Request', 'Invalid file path');
    }

    // Public prefixes: declared by each module via defineModule() storagePublicPrefixes.
    // Files whose path starts with a declared prefix are served without authentication.
    // The path must have at least two segments beyond the prefix
    // ({...prefix}/{id}/{filename}) to prevent unintentional root-level exposure.
    const publicPrefixes = getStoragePublicPrefixes();
    const matchedPrefix  = publicPrefixes.find(prefix => fileKey.startsWith(prefix + '/'));
    if (matchedPrefix) {
      const prefixDepth = matchedPrefix.split('/').length;
      if (pathParts.length < prefixDepth + 2) {
        return apiError('Bad Request', 'Invalid file path');
      }
      return await fetchFile(fileKey);
    }

    // Require authentication for all other paths.
    const { cookies } = await import('next/headers');
    const cookieStore  = await cookies();
    const sessionToken = cookieStore.get(COOKIE_NAME)?.value;

    if (!sessionToken) {
      return apiError('Unauthorized', 'Authentication required to access files');
    }

    const session = await getSessionResolver()(sessionToken);

    if (!session) {
      return apiError('Unauthorized', 'Invalid or expired session');
    }

    // Path-based access control driven by policies declared in each module.
    const policies = getStorageAccessPolicies();
    const policy   = policies.find(p => pathParts[0] === p.prefix);

    if (!policy) {
      return apiError('Forbidden', 'Invalid file path');
    }

    if (policy.type === 'owner') {
      // Owner-scoped: pathParts[1] is the resource owner ID (e.g. users/{userId}/...)
      if (session.user.id !== pathParts[1] && session.user.role !== 'admin') {
        return apiError('Forbidden', 'You do not have permission to access this file');
      }
    } else if (policy.type === 'admin') {
      if (session.user.role !== 'admin') {
        return apiError('Forbidden', 'Admin access required for this file');
      }
    }

    return await fetchFile(fileKey);
  } catch (error) {
    // Log full error server-side; never surface internal details to the client.
    fail(`Storage: error serving file: ${error.message}`);
    return apiError('Internal Server Error', 'Failed to retrieve file');
  }
}

async function fetchFile(fileKey) {
  const result = await getFile(fileKey);

  if (!result.success) {
    if (result.error.includes('NoSuchKey') || result.error.includes('not found')) {
      return apiError('Not Found', 'File not found');
    }
    // Never forward raw storage error messages (which may contain bucket names
    // or internal keys) to the client.
    return apiError('Internal Server Error', 'Failed to retrieve file');
  }

  return {
    success: true,
    file: {
      body:          result.data.body,
      contentType:   result.data.contentType,
      contentLength: result.data.contentLength,
      lastModified:  result.data.lastModified,
    },
  };
}

// auth: 'public' — the handler enforces path-based access control internally.
// The router calls it without a session; the handler reads cookies itself for
// non-public paths.
export const routes = defineApiRoutes([
  { path: '/storage/**', method: 'GET', handler: handleGetFile, auth: 'public' }
]);
