import type { CookieOptions } from 'express';
import type { ConfigService } from '@nestjs/config';

/**
 * Session cookie contract — httpOnly, SameSite=Strict, Secure in prod.
 *
 * Two cookies:
 *   dmc_access   — short-lived JWT (matches JWT_EXPIRES_IN). Sent on
 *                  every API call, read by JwtStrategy's custom
 *                  extractor. NOT accessible from JS (httpOnly).
 *   dmc_refresh  — long-lived opaque token, scoped to /api/auth so the
 *                  browser only ships it on refresh/logout. Rotated
 *                  single-use in RefreshTokenEntity.
 *
 * Rationale for splitting: sending the refresh token on every request
 * broadens its exposure surface for no gain — refresh only travels to
 * /auth/refresh + /auth/logout.
 *
 * SameSite=Strict blocks CSRF on cookie-authenticated cross-site
 * navigations. `csrf-csrf` layers a double-submit token on top for
 * state-changing routes, so a compromised sub-domain can't ride the
 * cookie.
 */

export const COOKIE_ACCESS_NAME = 'dmc_access';
export const COOKIE_REFRESH_NAME = 'dmc_refresh';
export const COOKIE_CSRF_NAME = 'dmc_csrf';

// 7 days default — matches security.md refresh-token cap.
const DEFAULT_REFRESH_TTL_SEC = 7 * 24 * 60 * 60;

// Access-token cookie mirrors the JWT lifetime. 1h default per
// security.md A02. Overridable via JWT_EXPIRES_IN parsing below.
const DEFAULT_ACCESS_TTL_SEC = 60 * 60;

/** Parse `1h` / `30m` / `86400` / `1d` style TTLs into seconds. */
export function parseTtlToSeconds(input: string | undefined, fallback: number): number {
  if (!input) return fallback;
  const m = input.trim().match(/^(\d+)\s*([smhd])?$/i);
  if (!m) return fallback;
  const n = Number(m[1]);
  const unit = (m[2] || 's').toLowerCase();
  switch (unit) {
    case 'd':
      return n * 86400;
    case 'h':
      return n * 3600;
    case 'm':
      return n * 60;
    default:
      return n;
  }
}

function baseCookieOpts(config: ConfigService): CookieOptions {
  const isProd = (config.get<string>('NODE_ENV') || 'development') === 'production';
  return {
    httpOnly: true,
    secure: isProd,
    // Strict is the safest default; the FE is same-origin behind the
    // API's reverse proxy in every deployment we run. If a future
    // deployment needs cross-site (e.g. an embedded widget on a
    // partner domain) we'd revisit this per-environment.
    sameSite: isProd ? 'strict' : 'lax',
    path: '/',
  };
}

export function accessCookieOpts(config: ConfigService): CookieOptions {
  return {
    ...baseCookieOpts(config),
    maxAge:
      parseTtlToSeconds(config.get<string>('JWT_EXPIRES_IN'), DEFAULT_ACCESS_TTL_SEC) * 1000,
  };
}

export function refreshCookieOpts(config: ConfigService): CookieOptions {
  return {
    ...baseCookieOpts(config),
    // Scoped to the auth surface — the browser never ships it on
    // regular API calls, shrinking the leak surface.
    path: '/api/auth',
    maxAge:
      parseTtlToSeconds(
        config.get<string>('REFRESH_TOKEN_TTL'),
        DEFAULT_REFRESH_TTL_SEC,
      ) * 1000,
  };
}

/**
 * Clear options — must mirror the set options' path/sameSite/secure
 * so the browser matches them for deletion.
 */
export function clearAccessCookie(config: ConfigService): CookieOptions {
  return { ...baseCookieOpts(config), maxAge: 0 };
}

export function clearRefreshCookie(config: ConfigService): CookieOptions {
  return { ...baseCookieOpts(config), path: '/api/auth', maxAge: 0 };
}

export function refreshTtlSeconds(config: ConfigService): number {
  return parseTtlToSeconds(config.get<string>('REFRESH_TOKEN_TTL'), DEFAULT_REFRESH_TTL_SEC);
}
