/**
 * Runtime environment accessor for Cloudflare Workers.
 *
 * Astro 6 removed `Astro.locals.runtime.env`. The new idiom is to import the
 * runtime bindings directly from `cloudflare:workers`. This file augments the
 * raw Cloudflare `Env` with Multideal-specific secret + binding types so every
 * caller gets IntelliSense on DATABASE_URL, R2_BUCKET, etc.
 *
 * Usage:
 *     import { env } from '@/server/env';
 *     const db = createDbService({ DATABASE_URL: env.DATABASE_URL });
 */

import { env as cfEnv } from 'cloudflare:workers';
import { z } from 'zod';
import type { TopicDO } from './do-host/do/TopicDO';
import type { UserSessionDO } from './do-host/do/UserSessionDO';

/**
 * Fully-typed Multideal Cloudflare runtime env. Wraps `cloudflare:workers`'s
 * auto-generated `Env` with the secret + binding names we declared in
 * `wrangler.toml` and pushed via `wrangler secret put`.
 */
export interface MultidealEnv {
  // --- Secrets (wrangler secret put) - typed as required so downstream
  //     consumers don't have to null-check every usage. Runtime will throw
  //     if a secret is missing (secrets are validated at the API boundary).
  DATABASE_URL: string;
  SESSION_SECRET: string;
  /** HS256 signing secret for access JWTs. Set via `wrangler secret put JWT_SECRET`. */
  JWT_SECRET: string;
  /** Previous JWT signing secret — used during key rotation (verifyAccessToken tries both). Optional. */
  JWT_SECRET_PREV?: string;
  QR_SECRET: string;
  /** HMAC-SHA-256 secret for deterministic guest checkout credentials. */
  GUEST_ACCESS_TOKEN_SECRET: string;
  /**
   * Password pepper v1 (hex string of 32+ random bytes). Applied AFTER the KDF as
   * HMAC-SHA256(key = hex-decoded pepper, message = raw PBKDF2 output) to produce
   * `pep1:` hashes. NEVER stored in the DB and NEVER logged. Set via
   * `wrangler secret put PASSWORD_PEPPER_V1`. Losing/rotating it invalidates every
   * `pep1:` hash, so back it up in a durable secret store.
   */
  PASSWORD_PEPPER_V1: string;
  /** HMAC-SHA-256 secret for signing pickup QR tokens. Set via `wrangler secret put PICKUP_QR_SECRET`. */
  PICKUP_QR_SECRET?: string;
  PII_KEY: string;
  /**
   * Cloudflare Turnstile secret key for server-side siteverify on the
   * credential/enumeration endpoints. Set via `wrangler secret put
   * TURNSTILE_SECRET_KEY`. NEVER logged.
   */
  TURNSTILE_SECRET_KEY: string;
  // --- Payment provider ---
  /** Payment provider selector. See wrangler.toml [vars]. */
  PAYMENT_PROVIDER: 'stripe' | 'mock';
  /** Explicit non-production opt-in for synthetic tax documents. */
  ALLOW_MOCK_INVOICING?: string;

  /** Platform fee percentage — CF var is always string; use Number(env.PLATFORM_FEE_PCT) at call site. */
  PLATFORM_FEE_PCT: string;

  // --- Stripe payment provider ---
  /** Stripe secret key (sk_live_... or sk_test_...). Set via `wrangler secret put STRIPE_SECRET_KEY`. */
  STRIPE_SECRET_KEY: string;
  /** Stripe API version pin (e.g. '2026-04-22.dahlia'). Falls back to pinned SDK default if unset. */
  STRIPE_API_VERSION?: string;
  /** Stripe publishable key (pk_live_... or pk_test_...). Safe for client-side use. */
  STRIPE_PUBLISHABLE_KEY: string;
  /**
   * Stripe webhook signing secret for the CONNECT (`connect=true`) endpoint —
   * delivers connected-account events (account.updated, connected payout.*, …).
   */
  STRIPE_WEBHOOK_SECRET: string;
  /**
   * Stripe webhook signing secret for the PLATFORM (`connect=false`) endpoint —
   * delivers platform payment/charge events for destination charges
   * (payment_intent.succeeded, charge.succeeded/refunded, charge.dispute.*).
   * Optional: when unset, only the connect secret is used (single-endpoint setups).
   */
  STRIPE_WEBHOOK_SECRET_PLATFORM?: string;

  RESEND_API_KEY: string;
  RESEND_FROM_EMAIL: string;
  /** Admin fallback recipient when system_config support_notification_emails is empty. */
  ADMIN_EMAIL?: string;
  /** Resend from-address for marketing/lifecycle emails. Must be on news.multi.deal. */
  RESEND_MARKETING_FROM_EMAIL?: string;
  /** Brevo API key for bulk broadcast campaigns. */
  BREVO_API_KEY?: string;
  /** Brevo list ID to sync opted-in marketing subscribers to. */
  BREVO_LIST_ID?: string;
  /** Optional shared secret for Brevo webhook endpoint validation (sent as x-brevo-webhook-secret header). */
  BREVO_WEBHOOK_SECRET?: string;
  VAPID_PUBLIC_KEY: string;
  VAPID_PRIVATE_KEY: string;
  VAPID_SUBJECT: string;
  SENTRY_DSN: string;
  CRON_SECRET: string;
  /** AES-GCM key-encryption-key (32-byte hex/base64) for vendor invoice provider credentials. Required when any vendor uses morning/icount. */
  INVOICE_KEK?: string;
  GOOGLE_API_KEY?: string;
  /** Cloudflare Workers AI binding for on-edge embeddings (kb-search adapter). */
  AI?: {
    run(model: string, input: { text: string[] }): Promise<{ data: number[][] }>;
  };
  /** Shared secret required by `POST /api/test/reset-vendor` (Wave 0 E2E). Endpoint 404s when unset. */
  E2E_SECRET?: string;
  /** Explicit fail-closed gate for proof-protected test routes. Must equal `1`. */
  E2E_ROUTES_ENABLED?: string;
  /** Exact database URL allowed for destructive E2E factory routes. */
  E2E_FACTORY_DATABASE_URL?: string;
  /** Optional real Stripe test Connect account used only by proof-protected E2E factories. */
  E2E_STRIPE_CONNECT_ACCOUNT_ID?: string;

  // --- Wolt Drive carrier ---
  WOLT_DRIVE_MERCHANT_KEY?: string;
  WOLT_DRIVE_MERCHANT_ID?: string;
  WOLT_DRIVE_MOCK_URL?: string;
  /** 'test' uses Wolt dev sandbox; 'production' uses live API. Default: 'test'. */
  WOLT_DRIVE_ENV?: 'test' | 'production';
  WOLT_DRIVE_WEBHOOK_SECRET?: string;

  // --- Customer support contact (used in Wolt delivery order customer_support field) ---
  SUPPORT_EMAIL?: string;
  SUPPORT_PHONE?: string;

  // --- Social sharing ---
  /** Base URL for short share links (marketing domain). Var in wrangler.toml [vars]. */
  SHARE_BASE_URL?: string;

  // --- Feature flags ---
  /** Feature flag for deal auto-translation pipeline. String 'true'|'false'; parse via TranslationEnabledSchema. */
  TRANSLATION_ENABLED?: string;

  /**
   * Stripe payout rail gate for affiliate withdrawals.
   * Default 'false' — keeps withdrawal flow in status='approved' for manual settlement.
   * Flip to 'true' after D.0 probe confirms IL Express accounts are supported.
   * When 'true': runAffiliatePayout fires stripe.transfers.create + stripe.payouts.create.
   */
  STRIPE_PAYOUT_ENABLED?: string;

  /** Club settlement rate (0–1, e.g. '0.15' = 15%). Set via wrangler var. Default '0.15'. */
  CLUB_SETTLEMENT_RATE?: string;

  // --- Referral / affiliate config (parse via ReferralConfigSchema) ---
  /** Reward credited to referrer on successful referral. Agorot. Default 2000 (₪20). */
  REFERRAL_REWARD_AGOROT?: string;
  /** Discount granted to referee on first purchase. Agorot. Default 2000 (₪20). */
  REFERRAL_REFEREE_DISCOUNT_AGOROT?: string;
  /** Affiliate commission percentage (0–100). Default 30. */
  REFERRAL_AFFILIATE_PCT?: string;
  /** Affiliate attribution window in days. Default 30. */
  REFERRAL_AFFILIATE_WINDOW_DAYS?: string;
  /** Max orders an affiliate earns commission on per customer. Default 30. */
  REFERRAL_AFFILIATE_MAX_ORDERS?: string;
  /** Referral tracking cookie lifetime in days. Default 30. */
  REFERRAL_COOKIE_DAYS?: string;
  /** Minimum balance (agorot) required to withdraw affiliate earnings. Default 20000 (₪200). */
  REFERRAL_WITHDRAWAL_MIN_AGOROT?: string;
  /** Secret for E2E geo-guard bypass on /api/referrals/touch. Header x-referral-test-bypass must match. Unset in normal operation. */
  REFERRAL_TEST_BYPASS_SECRET?: string;

  // --- Analytics Engine / AE SQL API ---
  /** Cloudflare account ID for AE SQL API calls. Plaintext var in wrangler.toml [vars]. */
  CF_ACCOUNT_ID?: string;
  /** Cloudflare Analytics GraphQL token for queue op-rate monitoring. Secret: `wrangler secret put CF_ANALYTICS_TOKEN`. */
  CF_ANALYTICS_TOKEN?: string;
  /** Cloudflare Analytics Engine read token. Secret: `wrangler secret put CF_AE_API_TOKEN`. */
  CF_AE_API_TOKEN?: string;

  // --- Vars (wrangler.toml [vars]) ---
  ENVIRONMENT: string;
  PUBLIC_SITE_URL: string;
  PUBLIC_LAUNCH_STATE: LaunchState;
  LOG_LEVEL: string;

  // --- Bindings (wrangler.toml) ---
  R2_BUCKET: R2Bucket;
  ASSETS: Fetcher;

  // --- Queue bindings (Phase 1: producers not yet called; infrastructure only) ---
  OUTBOX_QUEUE: Queue<{ outboxId: string }>;
  LLM_JOBS_QUEUE: Queue<{ jobId: string }>;
  TRANSLATION_QUEUE?: Queue<{ jobId: string }>;

  // --- Durable Object namespace bindings (all classes hosted locally in this worker) ---
  DEAL_DO: DurableObjectNamespace;
  GROUP_DEAL_DO: DurableObjectNamespace;
  PERSONAL_OFFER_DO: DurableObjectNamespace;
  SCHEDULED_PUBLISH_DO: DurableObjectNamespace;
  GOLD_WINDOW_DO: DurableObjectNamespace;
  /** StripeReconcileDO — singleton DO for Stripe payment reconciliation alarms (local to this worker). */
  STRIPE_RECONCILE_DO: DurableObjectNamespace;
  /** SupportTicketDO — entity-alarm DO for support_tickets lifecycle. */
  SUPPORT_TICKET_DO: DurableObjectNamespace;
  /** SupportCaseDO — entity-alarm DO for transaction_cases lifecycle. */
  SUPPORT_CASE_DO: DurableObjectNamespace;
  /** UserSessionDO — per-user WebSocket session tracking DO for live notifications. */
  USER_SESSION_DO: DurableObjectNamespace<UserSessionDO>;
  /** TopicDO — topic-based fan-out DO for live notification routing. */
  TOPIC_DO: DurableObjectNamespace<TopicDO>;
  /** CacheEpochDO — single global catalog-cache epoch counter (local to this worker). */
  CACHE_EPOCH_DO: DurableObjectNamespace;
  /**
   * PasswordHashDO — argon2id password hashing under this worker's 30s DO CPU
   * budget (local, keyed by idFromName(userId)). Optional so callers must
   * guard: a missing binding means defer to pep1/pbkdf2, never crash.
   */
  PASSWORD_HASH_DO?: DurableObjectNamespace;
  /**
   * Shared secret sent in the `x-md-hash-do` header to authorise PasswordHashDO.fetch().
   * Must match `wrangler secret put HASH_DO_SECRET --name multideal-preview`.
   */
  HASH_DO_SECRET?: string;

  // --- RenderDO (local — same script as multideal-preview) ---
  /** Kill-switch: set to "false" to fall back to inline SSR on cache miss. */
  RENDER_DO_ENABLED?: string;
  /** HMAC secret that authorises RenderDO.fetch() calls — must match `wrangler secret put RENDER_DO_SECRET`. */
  RENDER_DO_SECRET?: string;
  /** RenderDO binding — local DO class for SSR offload (30s CPU budget). No script_name. */
  RENDER_DO?: DurableObjectNamespace;

  // --- Analytics Engine bindings ---
  /** MULTIDEAL_DEV — Analytics Engine dataset for referral link click tracking. */
  MULTIDEAL_DEV: AnalyticsEngineDataset;

  /** D1 database for capturing outgoing emails in non-production environments. */
  EMAIL_MOCK_DB?: D1Database;

  // --- Catch-all index signature so downstream generic Env types match ---
  [key: string]:
    | string
    | number
    | R2Bucket
    | Fetcher
    | Queue
    | DurableObjectNamespace
    | DurableObjectNamespace<UserSessionDO>
    | DurableObjectNamespace<TopicDO>
    | AnalyticsEngineDataset
    | D1Database
    | MultidealEnv['AI']
    | undefined;
}

/**
 * Runtime env with Multideal's secret + binding types. Use this in place of the
 * raw `cloudflare:workers` export.
 */
export type LaunchState = 'prelaunch' | 'live';

export function assertRuntimeEnv(runtimeEnv: {
  ENVIRONMENT?: string;
  PUBLIC_SITE_URL?: string;
  PUBLIC_LAUNCH_STATE?: string;
}): asserts runtimeEnv is {
  ENVIRONMENT?: string;
  PUBLIC_SITE_URL?: string;
  PUBLIC_LAUNCH_STATE: LaunchState;
} {
  if (
    runtimeEnv.PUBLIC_LAUNCH_STATE !== undefined &&
    !['prelaunch', 'live'].includes(runtimeEnv.PUBLIC_LAUNCH_STATE)
  ) {
    throw new Error('PUBLIC_LAUNCH_STATE must be "prelaunch" or "live"');
  }
  if (runtimeEnv.ENVIRONMENT === 'production' && !runtimeEnv.PUBLIC_SITE_URL) {
    throw new Error('PUBLIC_SITE_URL must be set in production');
  }
}

assertRuntimeEnv(cfEnv);

export const env = cfEnv as unknown as MultidealEnv;

/** Anything other than an explicit "live" resolves to prelaunch, so an absent value stays non-indexable. */
export const launchState: LaunchState =
  (cfEnv as { PUBLIC_LAUNCH_STATE?: string }).PUBLIC_LAUNCH_STATE === 'live' ? 'live' : 'prelaunch';

/** Re-export the type for function signatures that take env as a parameter. */
export type CloudflareEnv = MultidealEnv;

/**
 * Zod schema for the TRANSLATION_ENABLED env var.
 *
 * Uses z.enum(['true','false']) — NOT z.coerce.boolean() — because coerce.boolean()
 * returns true for any non-empty string, including the literal string "false".
 */
export const TranslationEnabledSchema = z
  .enum(['true', 'false'])
  .default('false')
  .transform((v) => v === 'true');

/**
 * Zod schema for referral / affiliate config vars.
 *
 * All vars are optional in the CF env (strings); this schema coerces them to
 * integers and applies defaults so callers get typed, validated config.
 *
 * Usage:
 *     import { ReferralConfigSchema } from '@/server/env';
 *     const cfg = ReferralConfigSchema.parse(env);
 *     cfg.REFERRAL_REWARD_AGOROT // number
 */
export const ReferralConfigSchema = z.object({
  REFERRAL_REWARD_AGOROT: z.coerce.number().int().default(2000),
  REFERRAL_REFEREE_DISCOUNT_AGOROT: z.coerce.number().int().default(2000),
  REFERRAL_AFFILIATE_PCT: z.coerce.number().int().min(0).max(100).default(30),
  REFERRAL_AFFILIATE_WINDOW_DAYS: z.coerce.number().int().default(30),
  REFERRAL_AFFILIATE_MAX_ORDERS: z.coerce.number().int().default(30),
  REFERRAL_COOKIE_DAYS: z.coerce.number().int().default(30),
  REFERRAL_WITHDRAWAL_MIN_AGOROT: z.coerce.number().int().default(20000),
});

export type ReferralConfig = z.infer<typeof ReferralConfigSchema>;
