/**
 * GET /api/share/[token] — Public share link resolution.
 *
 * No session required. Validates the token, applies IP rate limiting,
 * handles the optional email-OTP gate, decrypts if needed, streams content.
 *
 * Security controls (RD-3):
 *   - HMAC-SHA256 token verification via timingSafeEqual.
 *   - Expiry and revocation checks.
 *   - Per-link view-cap with atomic increment + auto-revoke.
 *   - IP rate limit: 100 req/hour per IP.
 *   - Cache-Control: private, no-store (prevents CDN caching).
 *   - Compliance audit row on every access.
 */

import { verifyShareLink, recordShareLinkAccess, checkIpRateLimit } from '@/lib/artifact-acl.js';
import { requestIp } from '@/lib/compliance-audit.js';
import { isInsideAnyArtifactDir } from '@/lib/artifact-paths.js';
import { decryptFromDiskBuffer } from '@/lib/artifact-crypto.js';
import { resolveStoredPath } from '@/lib/paths.js';
import { createHmac, timingSafeEqual } from 'node:crypto';
import path from 'node:path';

const COOKIE_MAX_AGE_MS = 600 * 1000; // 10 minutes

/**
 * Verifies the signed OTP cookie set by POST /api/share/[token]/verify.
 *
 * Cookie value format: <email_b64url>.<issuedAt_unix>.<hmac_hex>
 * HMAC is over "<token>:<email>:<issuedAt>" with MADARX_SHARE_LINK_SECRET.
 *
 * Returns the verified email string on success, or null if absent/invalid/expired.
 */
async function _verifyOtpCookie(req, token) {
  const cookieName = `madarx-share-otp-${token}`;
  const cookieHeader = req?.headers?.get?.('cookie') || '';

  // Escape the token for use in a regex (tokens are base64url — safe chars only).
  const escapedName = cookieName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${escapedName}=([^;]+)`));
  if (!match) return null;

  const cookieValue = decodeURIComponent(match[1]);
  // Format: <email_b64url>.<issuedAt>.<sig>
  const parts = cookieValue.split('.');
  if (parts.length < 3) return null;

  // Last part is the sig (64 hex chars), second-to-last is issuedAt, rest is email_b64url.
  const sig = parts[parts.length - 1];
  const issuedAt = parseInt(parts[parts.length - 2], 10);
  const emailB64 = parts.slice(0, parts.length - 2).join('.');
  if (!emailB64 || !issuedAt || !sig) return null;

  // Expiry check (10 min TTL).
  if (Date.now() - issuedAt * 1000 > COOKIE_MAX_AGE_MS) return null;

  // Decode email.
  let email;
  try {
    email = Buffer.from(emailB64, 'base64url').toString('utf8');
  } catch {
    return null;
  }
  if (!email || !email.includes('@')) return null;

  // Resolve secret.
  let secret = process.env.MADARX_SHARE_LINK_SECRET;
  if (!secret) {
    try {
      const { getSecret } = await import('@/lib/secrets.js');
      secret = await getSecret('MADARX_SHARE_LINK_SECRET', { scope: 'global' });
    } catch { /* fall through */ }
  }
  if (!secret) return null;

  // Constant-time HMAC verification.
  const expected = createHmac('sha256', secret)
    .update(`${token}:${email}:${issuedAt}`)
    .digest('hex');

  const expectedBuf = Buffer.from(expected, 'hex');
  let sigBuf;
  try {
    sigBuf = Buffer.from(sig, 'hex');
  } catch {
    return null;
  }
  if (sigBuf.length !== expectedBuf.length) return null;
  if (!timingSafeEqual(expectedBuf, sigBuf)) return null;

  return email; // verified
}

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function GET(req, { params }) {
  const { token } = await params;

  // IP rate limit: 100 req/hour per IP.
  const ip = requestIp(req);
  const rateCheck = checkIpRateLimit(ip);
  if (!rateCheck.ok) {
    return Response.json(
      { error: 'Too many requests', retry_after_seconds: Math.ceil((rateCheck.retryAfterMs || 3600000) / 1000) },
      {
        status: 429,
        headers: {
          'Cache-Control': 'private, no-store',
          'Retry-After': String(Math.ceil((rateCheck.retryAfterMs || 3600000) / 1000)),
        },
      }
    );
  }

  // Verify the share token.
  const verification = await verifyShareLink(token, req);
  if (!verification.ok) {
    const status = verification.status || 410;
    return Response.json(
      { error: verification.reason },
      { status, headers: { 'Cache-Control': 'private, no-store' } }
    );
  }

  const { link, artifact } = verification;

  // Email-OTP gate (P1-5).
  // OTP code is no longer accepted as URL query params — it must be submitted
  // via POST /api/share/[token]/verify, which sets a signed httpOnly cookie.
  // The GET handler checks that cookie here to grant access.
  if (Boolean(link.require_email_otp)) {
    const verifiedEmail = await _verifyOtpCookie(req, token);
    if (!verifiedEmail) {
      return Response.json(
        {
          status: 'otp_required',
          otp_endpoint: `/api/share/${token}/otp`,
          verify_endpoint: `/api/share/${token}/verify`,
          message: 'This share link requires email verification. POST {email, code} to /api/share/<token>/verify.',
        },
        { status: 401, headers: { 'Cache-Control': 'private, no-store' } }
      );
    }

    // Cookie verified — record access with recipient email.
    recordShareLinkAccess(link.id, req, verifiedEmail);

    return _serveArtifact(artifact, link);
  }

  // No OTP required — record access and serve.
  recordShareLinkAccess(link.id, req, null);
  return _serveArtifact(artifact, link);
}

/**
 * Serves the artifact content with appropriate Content-Type.
 * Handles optional decryption if artifact.encrypted is set.
 *
 * Security (P0-2): resolves artifact.path to an absolute path and verifies it
 * is inside an allowed artifact directory before any readFileSync call.
 */
async function _serveArtifact(artifact, link) {
  const headers = {
    'Cache-Control': 'private, no-store',
    'X-Content-Type-Options': 'nosniff',
  };

  // Determine content to serve.
  // Prefer artifact.content (in-DB preview). Fall back to disk read.
  let body = artifact.content || artifact.source_md || '';
  let contentType = 'text/plain; charset=utf-8';

  // Determine content type from artifact kind.
  const kind = (artifact.kind || '').toLowerCase();
  if (kind === 'html' || kind === 'ui' || kind === 'mines_investor_presentation' || kind === 'mines_project_package') {
    contentType = 'text/html; charset=utf-8';
  } else if (kind === 'md' || kind === 'markdown') {
    contentType = 'text/markdown; charset=utf-8';
  } else if (kind === 'json') {
    contentType = 'application/json; charset=utf-8';
  }

  // Handle encrypted artifacts (Phase 3 feature).
  // Disk format: [12-byte IV][16-byte auth tag][ciphertext...]
  if (Boolean(artifact.encrypted) && artifact.path) {
    // Path traversal guard: resolve to absolute and verify it is inside an
    // allowed artifact directory before reading from disk (P0-2).
    const resolvedPath = resolveStoredPath(artifact.path);
    if (!isInsideAnyArtifactDir(resolvedPath)) {
      console.error('[share] Path traversal blocked for artifact:', artifact.id, resolvedPath);
      try {
        const { appendAudit } = await import('@/lib/runtime.js');
        appendAudit({
          type: 'artifact.path_traversal_blocked',
          summary: `Share link path traversal blocked for artifact ${artifact.id}`,
          status: 'error',
          meta: { artifact_id: artifact.id, resolved_path: resolvedPath },
        });
      } catch { /* audit write must not mask original error */ }
      return Response.json(
        { ok: false, error: 'decryption_failed' },
        { status: 500, headers }
      );
    }
    try {
      const fsNode = await import('node:fs');
      const diskBuf = fsNode.readFileSync(resolvedPath); // binary read
      const orgId = artifact.encryption_key_id || link.org_id || 'personal-org';
      const plain = decryptFromDiskBuffer(orgId, diskBuf);
      body = Buffer.isBuffer(plain) ? plain.toString('utf8') : String(plain);
    } catch (cryptoErr) {
      console.error('[share] Failed to decrypt artifact:', cryptoErr.message);
      // Write audit row for the decryption failure — mask raw GCM message (A5).
      try {
        const { appendAudit } = await import('@/lib/runtime.js');
        appendAudit({
          type: 'artifact.decryption_failed',
          summary: `Share link decryption failed for artifact ${artifact.id}`,
          status: 'error',
          meta: { artifact_id: artifact.id, encryption_key_id: artifact.encryption_key_id, error: 'gcm_auth_failed' },
        });
      } catch { /* audit write must not mask original error */ }
      return Response.json(
        { ok: false, error: 'decryption_failed' },
        { status: 500, headers }
      );
    }
  } else if (!body && artifact.path && !artifact.encrypted) {
    // Plain-text artifact on disk.
    // Path traversal guard (P0-2): verify resolved path is inside allowed dirs.
    const resolvedPath = resolveStoredPath(artifact.path);
    if (!isInsideAnyArtifactDir(resolvedPath)) {
      console.error('[share] Path traversal blocked for artifact:', artifact.id, resolvedPath);
      try {
        const { appendAudit } = await import('@/lib/runtime.js');
        appendAudit({
          type: 'artifact.path_traversal_blocked',
          summary: `Share link path traversal blocked for artifact ${artifact.id}`,
          status: 'error',
          meta: { artifact_id: artifact.id, resolved_path: resolvedPath },
        });
      } catch { /* audit write must not mask original error */ }
      return Response.json(
        { ok: false, error: 'decryption_failed' },
        { status: 500, headers }
      );
    }
    try {
      const fsNode = await import('node:fs');
      body = fsNode.readFileSync(resolvedPath, 'utf8');
    } catch {
      body = '';
    }
  }

  headers['Content-Type'] = contentType;

  return new Response(body, { status: 200, headers });
}
