import dotenv from 'dotenv';
import { z } from 'zod';

dotenv.config();

const envSchema = z.object({
  // Server
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: z.coerce.number().default(4000),
  API_URL: z.string().default('http://localhost:4000'),
  ADMIN_URL: z.string().default('http://localhost:3000'),
  DOMAIN: z.string().default('cmlite.org'),

  // Logging
  LOG_DIR: z.string().default('/app/logs'),

  // Security
  CSP_ENABLED: z.string().default('false'),

  // Bunker Ops (Fleet Management)
  INSTANCE_LABEL: z.string().default(''),
  BUNKER_OPS_ENABLED: z.string().default('false'),
  BUNKER_OPS_REMOTE_WRITE_URL: z.string().default(''),

  // Database
  DATABASE_URL: z.string(),

  // Redis
  REDIS_URL: z.string().default('redis://redis-changemaker:6379'),

  // JWT
  JWT_ACCESS_SECRET: z.string().min(32),
  JWT_REFRESH_SECRET: z.string().min(32),
  JWT_INVITE_SECRET: z.string().min(32),
  JWT_ACCESS_EXPIRY: z.string().default('15m'),
  // Reduced 2026-04-12 from 7d → 24h. Stolen refresh tokens have a much tighter
  // exploitation window now; combined with device-fingerprint binding in
  // auth.service.ts, theft is materially harder to monetize.
  JWT_REFRESH_EXPIRY: z.string().default('24h'),

  // Encryption (for DB-stored secrets like SMTP password — required for all environments)
  ENCRYPTION_KEY: z.string().min(32, 'ENCRYPTION_KEY must be at least 32 characters'),

  // Gitea SSO cookie signing secret — MUST be distinct from JWT secrets.
  // Breaking change 2026-04-12: previously fell back to JWT_ACCESS_SECRET, which
  // meant a JWT leak compromised SSO cookies too. Now required (min 32 chars).
  GITEA_SSO_SECRET: z.string().min(32, 'GITEA_SSO_SECRET must be ≥32 chars; generate with: openssl rand -hex 32'),
  // Salt for deriving deterministic service passwords (Gitea, Rocket.Chat).
  // Breaking change 2026-04-12: previously fell back to JWT_ACCESS_SECRET. Now required.
  SERVICE_PASSWORD_SALT: z.string().min(32, 'SERVICE_PASSWORD_SALT must be ≥32 chars; generate with: openssl rand -hex 32'),

  // Initial Super Admin (auto-created during database seeding)
  INITIAL_ADMIN_EMAIL: z.string().email().default('admin@cmlite.org'),
  INITIAL_ADMIN_PASSWORD: z.string().min(12).default('REQUIRED_STRONG_PASSWORD_CHANGE_THIS')
    .refine(
      (val) => val !== 'REQUIRED_STRONG_PASSWORD_CHANGE_THIS',
      { message: 'INITIAL_ADMIN_PASSWORD must be changed from the default placeholder value' },
    ),

  // SMTP
  SMTP_HOST: z.string().default('mailhog-changemaker'),
  SMTP_PORT: z.coerce.number().default(1025),
  SMTP_USER: z.string().default(''),
  SMTP_PASS: z.string().default(''),
  SMTP_FROM: z.string().default('noreply@cmlite.org'),
  SMTP_FROM_NAME: z.string().default('Changemaker Lite'),
  EMAIL_TEST_MODE: z.string().default('true'),
  TEST_EMAIL_RECIPIENT: z.string().default('admin@cmlite.org'),

  // Listmonk
  LISTMONK_URL: z.string().default('http://listmonk-app:9000'),
  LISTMONK_ADMIN_USER: z.string().default('admin'),
  LISTMONK_ADMIN_PASSWORD: z.string().default(''),
  LISTMONK_SYNC_ENABLED: z.string().default('false'),
  LISTMONK_WEBHOOK_SECRET: z.string().default(''),
  LISTMONK_PROXY_PORT: z.coerce.number().default(9002),

  // Represent API (Canadian electoral data)
  REPRESENT_API_URL: z.string().default('https://represent.opennorth.ca'),

  // CORS
  CORS_ORIGINS: z.string().default('http://localhost:3000'),

  // Rate Limiting
  RATE_LIMIT_WINDOW_MS: z.coerce.number().default(15 * 60 * 1000),
  RATE_LIMIT_MAX: z.coerce.number().default(500),

  // Geocoding
  MAPBOX_API_KEY: z.string().optional(),
  GEOCODING_RATE_LIMIT_MS: z.coerce.number().default(1100),
  GEOCODING_CACHE_ENABLED: z.string().default('true'),
  GEOCODING_CACHE_TTL_HOURS: z.coerce.number().default(24),
  // Phase 2: Performance & Accuracy
  GOOGLE_MAPS_API_KEY: z.string().optional(),
  GOOGLE_MAPS_ENABLED: z.string().default('false'),
  GEOCODING_PARALLEL_ENABLED: z.string().default('true'),
  GEOCODING_BATCH_SIZE: z.coerce.number().default(10),

  // Bulk Re-Geocoding (Phase 3)
  BULK_GEOCODE_ENABLED: z.string().default('true'),
  BULK_GEOCODE_MAX_BATCH: z.coerce.number().default(5000),

  // Platform Services (NocoDB, n8n, Gitea)
  NOCODB_URL: z.string().default('http://changemaker-v2-nocodb:8080'),
  NOCODB_PORT: z.coerce.number().default(8091),
  NOCODB_EMBED_PORT: z.coerce.number().default(8881),
  N8N_URL: z.string().default('http://n8n-changemaker:5678'),
  N8N_PORT: z.coerce.number().default(5678),
  N8N_EMBED_PORT: z.coerce.number().default(8882),
  GITEA_URL: z.string().default('http://gitea-changemaker:3000'),
  GITEA_PORT: z.coerce.number().default(3030),
  GITEA_EMBED_PORT: z.coerce.number().default(8883),

  // MailHog (email testing UI)
  MAILHOG_URL: z.string().default('http://mailhog-changemaker:8025'),
  MAILHOG_EMBED_PORT: z.coerce.number().default(8884),

  // Mini QR (QR code generator)
  MINI_QR_URL: z.string().default('http://mini-qr:8080'),
  MINI_QR_PORT: z.coerce.number().default(8089),
  MINI_QR_EMBED_PORT: z.coerce.number().default(8885),

  // Excalidraw (collaborative whiteboard)
  EXCALIDRAW_URL: z.string().default('http://excalidraw-changemaker:80'),
  EXCALIDRAW_PORT: z.coerce.number().default(8090),
  EXCALIDRAW_EMBED_PORT: z.coerce.number().default(8886),

  // Homepage (service dashboard)
  HOMEPAGE_URL: z.string().default('http://homepage-changemaker:3000'),
  HOMEPAGE_EMBED_PORT: z.coerce.number().default(8887),

  // Vaultwarden (password manager)
  VAULTWARDEN_URL: z.string().default('http://vaultwarden-changemaker:80'),
  VAULTWARDEN_ADMIN_TOKEN: z.string().default(''),
  VAULTWARDEN_EMBED_PORT: z.coerce.number().default(8890),

  // Rocket.Chat (team chat)
  ROCKETCHAT_URL: z.string().default('http://rocketchat-changemaker:3000'),
  ROCKETCHAT_ADMIN_USER: z.string().default(''),
  ROCKETCHAT_ADMIN_PASSWORD: z.string().default(''),
  ROCKETCHAT_EMBED_PORT: z.coerce.number().default(8891),
  ENABLE_CHAT: z.string().default('false'),

  // Gancio (event management)
  GANCIO_URL: z.string().default('http://gancio-changemaker:13120'),
  GANCIO_PORT: z.coerce.number().default(8092),
  GANCIO_EMBED_PORT: z.coerce.number().default(8892),
  GANCIO_ADMIN_USER: z.string().default('admin'),
  GANCIO_ADMIN_PASSWORD: z.string().default(''),
  GANCIO_SYNC_ENABLED: z.string().default('false'),

  // Jitsi Meet (video conferencing)
  ENABLE_MEET: z.string().default('false'),
  JITSI_APP_ID: z.string().default('changemaker'),
  JITSI_APP_SECRET: z.string().default(''),
  JITSI_URL: z.string().default('http://jitsi-web-changemaker:80'),
  JITSI_EMBED_PORT: z.coerce.number().default(8893),

  // Pangolin (tunnel / reverse proxy)
  PANGOLIN_API_URL: z.string()
    .default('')
    .refine(
      (url) => !url || url.startsWith('https://'),
      { message: 'PANGOLIN_API_URL must use HTTPS for secure credential transmission' }
    ),
  PANGOLIN_API_KEY: z.string().default(''),
  PANGOLIN_ORG_ID: z.string().default(''),
  PANGOLIN_SITE_ID: z.string().default(''),
  PANGOLIN_ENDPOINT: z.string().default(''),
  PANGOLIN_NEWT_ID: z.string().default(''),
  PANGOLIN_NEWT_SECRET: z.string().default(''),

  // NAR (National Address Register)
  NAR_DATA_DIR: z.string().default('/data'),

  // Overpass / Area Import
  OVERPASS_API_URL: z.string().default('https://overpass-api.de/api/interpreter'),
  OVERPASS_MIN_DELAY_MS: z.coerce.number().default(30000),
  AREA_IMPORT_MAX_GRID_POINTS: z.coerce.number().default(500),

  // Payments (Stripe)
  ENABLE_PAYMENTS: z.string().default('false'),

  // Media Management
  ENABLE_MEDIA_FEATURES: z.string().default('false'),
  MEDIA_API_PORT: z.coerce.number().default(4100),
  MEDIA_API_PUBLIC_URL: z.string().default('http://media-api:4100'),
  MEDIA_ROOT: z.string().default('/media/library'),
  MEDIA_UPLOADS: z.string().default('/media/uploads'),
  MAX_UPLOAD_SIZE_GB: z.coerce.number().default(10),
  // HLS adaptive bitrate transcoding. When false, uploads are not enqueued
  // for transcoding (the worker stays registered so PENDING jobs from a
  // previous run still process if the flag is flipped back on). MP4 range-
  // request streaming continues to work as a fallback for un-transcoded
  // videos regardless of this flag.
  ENABLE_HLS_TRANSCODE: z.string().default('false'),

  // Container Registry (remote — gitea.bnkops.com)
  GITEA_REGISTRY: z.string().default('gitea.bnkops.com/admin'),
  GITEA_REGISTRY_USER: z.string().default(''),
  GITEA_REGISTRY_PASS: z.string().default(''),
  GITEA_REGISTRY_API_TOKEN: z.string().default(''), // For release uploads (build-release.sh)

  // Gitea Docs Comments (local platform instance)
  GITEA_COMMENTS_ENABLED: z.string().default('false'),
  GITEA_API_TOKEN: z.string().default(''), // Local Gitea — NOT the remote registry
  GITEA_COMMENTS_REPO_OWNER: z.string().default(''),
  GITEA_COMMENTS_REPO_NAME: z.string().default('docs-comments'),
  GITEA_OAUTH_CLIENT_ID: z.string().default(''),
  GITEA_OAUTH_CLIENT_SECRET: z.string().default(''),

  // Gitea Docs Version History
  GITEA_DOCS_REPO: z.string().default('admin/changemaker.lite'),
  GITEA_DOCS_PREFIX: z.string().default('mkdocs/docs'),
  GITEA_DOCS_BRANCH: z.string().default('v2'),

  // Gitea Auto-Setup (password used once to create API token, then cleared)
  GITEA_ADMIN_PASSWORD: z.string().default(''),

  // SMS Campaigns (Termux Android bridge)
  ENABLE_SMS: z.string().default('false'),

  // Social, People, Analytics (initial defaults; DB authoritative once admin saves)
  ENABLE_SOCIAL: z.string().default('false'),
  ENABLE_PEOPLE: z.string().default('false'),
  ENABLE_ANALYTICS: z.string().default('false'),

  // CCP Agent (remote management)
  ENABLE_CCP_AGENT: z.string().default('false'),
  CCP_URL: z.string().default(''),
  CCP_AGENT_URL: z.string().default(''),
  COMPOSE_PROFILES: z.string().default(''),
  TERMUX_API_URL: z.string().default('http://10.0.0.193:5001'),
  TERMUX_API_KEY: z.string().default(''),
  SMS_DELAY_BETWEEN_MS: z.coerce.number().default(3000),
  SMS_MAX_RETRIES: z.coerce.number().default(3),
  SMS_RESPONSE_SYNC_INTERVAL_MS: z.coerce.number().default(30000),
  SMS_DEVICE_MONITOR_INTERVAL_MS: z.coerce.number().default(30000),

  // Docs / Code Server
  CODE_SERVER_URL: z.string().default('http://code-server-changemaker:8443'),
  CODE_SERVER_PORT: z.coerce.number().default(8888),
  MKDOCS_PREVIEW_URL: z.string().default('http://mkdocs-changemaker:8000'),
  MKDOCS_PORT: z.coerce.number().default(4003),
  MKDOCS_DOCS_PATH: z.string().default('/mkdocs/docs'),
  MKDOCS_CONFIG_PATH: z.string().default('/mkdocs/mkdocs.yml'),
  MKDOCS_CONTAINER_NAME: z.string().default('mkdocs-changemaker'),
  MKDOCS_SITE_SERVER_URL: z.string().default('http://mkdocs-site-server-changemaker:80'),
  MKDOCS_SITE_SERVER_PORT: z.coerce.number().default(4004),

  // Docker (container status dashboard + service management)
  DOCKER_PROXY_URL: z.string().default('http://docker-socket-proxy:2375'),
  DOCKER_NETWORK_NAME: z.string().default('changemaker-lite'),
  NEWT_CONTAINER_NAME: z.string().default('newt-changemaker'),
  NEWT_COMPOSE_SERVICE: z.string().default('newt'),

  // Monitoring Services (behind 'monitoring' profile)
  PROMETHEUS_URL: z.string().default('http://prometheus-changemaker:9090'),
  PROMETHEUS_PORT: z.coerce.number().default(9090),
  GRAFANA_URL: z.string().default('http://grafana-changemaker:3000'),
  GRAFANA_PORT: z.coerce.number().default(3005),
  GRAFANA_EMBED_PORT: z.coerce.number().default(8894),
  ALERTMANAGER_URL: z.string().default('http://alertmanager-changemaker:9093'),
  ALERTMANAGER_PORT: z.coerce.number().default(9093),
  ALERTMANAGER_EMBED_PORT: z.coerce.number().default(8895),
  CADVISOR_URL: z.string().default('http://cadvisor-changemaker:8080'),
  CADVISOR_PORT: z.coerce.number().default(8086),
  NODE_EXPORTER_URL: z.string().default('http://node-exporter-changemaker:9100'),
  NODE_EXPORTER_PORT: z.coerce.number().default(9100),
  REDIS_EXPORTER_URL: z.string().default('http://redis-exporter-changemaker:9121'),
  REDIS_EXPORTER_PORT: z.coerce.number().default(9121),
  GOTIFY_URL: z.string().default('http://gotify-changemaker:80'),
  GOTIFY_PORT: z.coerce.number().default(8889),

  // GeoIP (MaxMind GeoLite2)
  MAXMIND_ACCOUNT_ID: z.string().default(''),
  MAXMIND_LICENSE_KEY: z.string().default(''),
  GEOIP_DB_PATH: z.string().default('/data/geoip/GeoLite2-City.mmdb'),

  // JSN -> cmlite bridge shared secret. Must match JSN's CMLITE_BRIDGE_SECRET.
  // Required for /api/auth/bridge/users and other /api/*/bridge/* endpoints to
  // accept calls. Generate with: openssl rand -hex 32
  JSN_BRIDGE_SECRET: z.string().min(32).optional(),

  // SSO redirect bridge: signs the short-lived (60s) one-time-use JWT issued
  // by /api/auth/bridge/issue-sso-token and verified by /api/auth/bridge/sso.
  // Lives only on cmlite — JSN never sees this secret; it asks cmlite to
  // mint a URL and opens that URL in the user's browser. Generate with:
  // openssl rand -hex 32. Distinct from JSN_BRIDGE_SECRET and JWT_*.
  SSO_BRIDGE_SECRET: z.string().min(32).optional(),

  // RC channel that every JSN-bridged supporter is invited into by the
  // identity bridge after RC user provisioning. Belt-and-braces alongside RC's
  // default-channel flag (which only fires at user creation time, leaving
  // existing-user backfill as a separate concern). Default 'supporters'.
  RC_SUPPORTERS_CHANNEL: z.string().default('supporters'),
});

export type Env = z.infer<typeof envSchema>;

function validateEnv(): Env {
  const result = envSchema.safeParse(process.env);
  if (!result.success) {
    console.error('Invalid environment variables:');
    console.error(result.error.flatten().fieldErrors);
    process.exit(1);
  }

  // GITEA_SSO_SECRET and SERVICE_PASSWORD_SALT are now validated as required
  // via .min(32) above — no more silent JWT_ACCESS_SECRET fallback. If either is
  // missing, the schema check above exits with a clear error.
  return result.data;
}

export const env = validateEnv();
