<?php
/**
 * TraderAlign — Configuration
 *
 * Database credentials, API keys, and environment settings.
 * This file lives outside the web root in production (Truehost cPanel).
 * For local XAMPP development, it's in config/ — blocked from direct
 * browser access via the front controller (and, since 2026-08-19, also by
 * config/.htaccess directly, since the front controller only intercepts
 * requests for paths that aren't real files — this one is).
 *
 * 2026-08-19 (pre-launch security audit, remediation pass) — environment-
 * specific values (DB credentials, CRON_SECRET, ACCOUNT_CREDENTIAL_KEY,
 * every external API key, APP_ENV) now load from config/config.local.php
 * FIRST, if that file exists. config.local.php is listed in .gitignore and
 * is never committed — each environment (this machine's local dev, staging,
 * production) gets its own real copy with its own real secrets, so a real
 * secret can never end up permanently baked into git history the way it
 * would if it were only ever set directly in this tracked file. Every
 * environment-specific constant below is wrapped in `if (!defined(...))`
 * so config.local.php's own define() (which runs first) always wins; the
 * values below are the safe, production-leaning fallback for a checkout
 * that has no config.local.php yet. See config.local.example.php for the
 * template — copy it to config.local.php and fill in real values before
 * running this app anywhere.
 */

if (file_exists(__DIR__ . '/config.local.php')) {
    require_once __DIR__ . '/config.local.php';
}

// -----------------------------------------------------------------------------
// Timezone
// -----------------------------------------------------------------------------
// 2026-08-18 fix: this app never explicitly declared its own timezone
// anywhere, so PHP silently fell back to whatever date.timezone happens to
// be set in php.ini (confirmed on this install: Europe/Berlin, an arbitrary
// default the app itself never chose). This is a real bug, not just a
// cosmetic one, confirmed live: with the real machine's actual timezone
// being Africa/Nairobi (UTC+3, confirmed via the OS), the Berlin default
// sat 1-2 hours behind real local time, which is exactly what produced the
// reported "Good morning" showing at 12:45 PM (in Berlin's clock that was
// still 11:45 AM). Every part of this app that reads "now" (Trader
// Profile's greeting, Reports' Weekly/Monthly/Quarterly/Annual period
// boundaries, the letter grace-window timing, streak/session logic, etc.)
// depends on PHP's own idea of "now" being correct, so this is fixed once,
// here, rather than patched at each call site.
if (!defined('APP_TIMEZONE')) {
    define('APP_TIMEZONE', 'Africa/Nairobi');
}
date_default_timezone_set(APP_TIMEZONE);

// -----------------------------------------------------------------------------
// Database — environment-specific, override in config.local.php
// -----------------------------------------------------------------------------
if (!defined('DB_HOST')) { define('DB_HOST', 'localhost'); }
if (!defined('DB_PORT')) { define('DB_PORT', '3306'); }
if (!defined('DB_NAME')) { define('DB_NAME', 'traderalign'); }
if (!defined('DB_USER')) { define('DB_USER', 'root'); }         // XAMPP default — override in config.local.php for production
if (!defined('DB_PASS')) { define('DB_PASS', ''); }              // XAMPP default has no password — override in config.local.php for production

// -----------------------------------------------------------------------------
// Application
// -----------------------------------------------------------------------------
if (!defined('APP_URL')) { define('APP_URL', 'http://localhost:8080/traderalign'); }
if (!defined('APP_NAME')) { define('APP_NAME', 'TraderAlign'); }
// Safe-by-default: 'production' unless a local override explicitly says
// otherwise. Previously hardcoded to 'development' directly in this tracked
// file — meaning a forgotten flip-back-to-production step (a real,
// documented pre-launch checklist item) would have shipped with
// display_errors on and full PHP stack traces visible to any visitor. Now
// the *tracked* default is the safe one; config.local.php sets
// APP_ENV='development' for this machine's own local dev use only, and
// config.local.php is never committed, so production never inherits it.
if (!defined('APP_ENV')) { define('APP_ENV', 'production'); }

// -----------------------------------------------------------------------------
// Session
// -----------------------------------------------------------------------------
define('SESSION_LIFETIME', 86400);   // 24 hours
define('SESSION_NAME', 'traderalign_session');

// Reject any session ID the client presents that PHP doesn't already
// recognize (instead of silently adopting it as a new session) — closes
// off session-fixation at the engine level. Must be set before any
// session_start() call; config.php is require_once'd first by every real
// entry point in this app (auth pages, index.php, every API endpoint), so
// setting it here applies it everywhere uniformly. Also worth setting in
// php.ini directly (session.use_strict_mode = On) for the handful of
// public/*.php files that can technically be requested directly and start
// a session before config.php ever loads.
ini_set('session.use_strict_mode', '1');

// -----------------------------------------------------------------------------
// Security
// -----------------------------------------------------------------------------
define('CSRF_TOKEN_NAME', 'csrf_token');
define('PASSWORD_HASH_ALGO', PASSWORD_ARGON2ID);

// -----------------------------------------------------------------------------
// Email verification (Section 5 Step 2 — Option C)
// -----------------------------------------------------------------------------
define('VERIFICATION_GRACE_HOURS', 24);
define('VERIFICATION_HARD_DELETE_DAYS', 30);

// -----------------------------------------------------------------------------
// Soft-delete grace period (Principle 14)
// -----------------------------------------------------------------------------
define('SOFT_DELETE_GRACE_DAYS', 30);

// DRAFT_EXPIRY — DELETED 2026-08-08 (Single Source of Truth migration
// Wave 6, §Consolidated #9). Confirmed via a full-repo grep to have had
// zero consumers anywhere in the codebase — the two real, live draft-
// expiry implementations are public/drafts.php's own $urgencyThresholds
// (UI urgency badge, in hours, fixed the same session to use these same
// scalper/day_trader/swing/position keys — see that array's own comment)
// and cron/daily.php's separate keyed-by-user_profiles.trading_style
// implementation (a different table, see that file's own comment) — this
// constant was never wired into either.

// -----------------------------------------------------------------------------
// Limits
// -----------------------------------------------------------------------------
define('MAX_DRAFTS_PER_USER', 20);
define('MT5_MAX_FILE_SIZE', 10 * 1024 * 1024);  // 10 MB
define('MT5_MAX_ROWS_PER_IMPORT', 5000);
define('MT5_BATCH_SIZE', 500);

// -----------------------------------------------------------------------------
// External Services — environment-specific, override in config.local.php
// -----------------------------------------------------------------------------
if (!defined('UPSTASH_REDIS_URL'))   { define('UPSTASH_REDIS_URL', ''); }
if (!defined('UPSTASH_REDIS_TOKEN')) { define('UPSTASH_REDIS_TOKEN', ''); }
if (!defined('RESEND_API_KEY'))      { define('RESEND_API_KEY', ''); }
if (!defined('POSTHOG_API_KEY'))     { define('POSTHOG_API_KEY', ''); }
if (!defined('ANTHROPIC_API_KEY'))   { define('ANTHROPIC_API_KEY', ''); }
if (!defined('MPESA_CONSUMER_KEY'))    { define('MPESA_CONSUMER_KEY', ''); }
if (!defined('MPESA_CONSUMER_SECRET')) { define('MPESA_CONSUMER_SECRET', ''); }
if (!defined('MPESA_PASSKEY'))         { define('MPESA_PASSKEY', ''); }
if (!defined('MPESA_SHORTCODE'))       { define('MPESA_SHORTCODE', ''); }
if (!defined('MPESA_CALLBACK_URL'))    { define('MPESA_CALLBACK_URL', APP_URL . '/api/billing/mpesa/callback'); }
if (!defined('R2_ACCESS_KEY')) { define('R2_ACCESS_KEY', ''); }
if (!defined('R2_SECRET_KEY')) { define('R2_SECRET_KEY', ''); }
if (!defined('R2_BUCKET'))     { define('R2_BUCKET', ''); }
if (!defined('R2_ENDPOINT'))   { define('R2_ENDPOINT', ''); }

// -----------------------------------------------------------------------------
// Colex AI model strings (env-var pattern — Section 8)
// Renamed 2026-08-10 from the pre-rebrand ALIGNEX_MODEL_* names — these
// were dead/unused (confirmed via a full-repo grep before renaming; the
// live COLEX_MODEL constant actually in use lives in
// src/ColexProvider.php, currently a single hardcoded model with no
// tiering). Kept here as the two real candidates for a future
// Haiku-default / Sonnet-higher-tier routing scheme (explicitly
// requested, not yet built — see the "model tiering" investigation).
// ALIGNEX_MODEL_OPUS dropped outright rather than renamed: the stated
// tiering plan only ever named Haiku (default) and Sonnet (higher
// tier) — Opus was never part of it, so carrying it forward under a new
// name would just be re-planting the same kind of unused placeholder
// this rename is cleaning up. SONNET's value updated to match
// ColexProvider's own already-current 'claude-sonnet-5' (was a stale
// 'claude-sonnet-4-6' placeholder); HAIKU's value was already correct.
// -----------------------------------------------------------------------------
define('COLEX_MODEL_SONNET', 'claude-sonnet-5');
define('COLEX_MODEL_HAIKU', 'claude-haiku-4-5-20251001');

// -----------------------------------------------------------------------------
// Cron secret (used to authenticate internal cron endpoints) —
// environment-specific, override in config.local.php.
//
// 2026-08-19 (pre-launch security audit): this was confirmed LIVE
// EXPLOITABLE while still set to the placeholder value below — a real
// request to cron/daily.php using this exact literal string authenticated
// successfully and ran real DB mutations. A real, unique-per-environment
// value now lives in config.local.php (this machine) / must be set the
// same way in every other environment before that endpoint is trusted. As
// a second, independent layer of defense, cron/daily.php and
// cron/hourly.php now both also hard-refuse to run if CRON_SECRET still
// equals this exact placeholder string, regardless of what secret value is
// submitted — so a deployment that forgot to create config.local.php fails
// closed instead of silently exploitable.
// -----------------------------------------------------------------------------
if (!defined('CRON_SECRET')) { define('CRON_SECRET', 'change-this-to-a-random-string-in-production'); }

// -----------------------------------------------------------------------------
// Account credential encryption key (Right Now Journal Account Summary —
// broker platform login/password, personal reference only, never used for
// automated login). AES-256-GCM via src/CredentialCrypto.php.
// Environment-specific, override in config.local.php — a real generated
// value now lives there for this machine. If this is ever left at the
// placeholder below in a real deployment, every stored broker password is
// only as safe as this file's own text, which (until 2026-08-19) was
// directly downloadable over the web — treat any account credential
// encrypted under the placeholder key as already compromised.
// -----------------------------------------------------------------------------
if (!defined('ACCOUNT_CREDENTIAL_KEY')) { define('ACCOUNT_CREDENTIAL_KEY', 'change-this-to-a-random-string-in-production'); }

// -----------------------------------------------------------------------------
// Tier Gating — BETA / usability-testing policy (2026-08-18)
// -----------------------------------------------------------------------------
// The real commercial gating (Free, Pro, Elite, with real feature
// restrictions between them) is NOT built yet, that comes later, once the
// BETA/usability-testing period ends. For now, every new account is granted
// Elite automatically so testers hit zero artificial tier restrictions
// while the full product experience is being tested. src/Auth.php::signUp()
// reads this constant when inserting the new user_profiles row; the
// user_profiles.tier column's own DB-level DEFAULT was also updated to match
// (see migration_beta_default_tier_elite.sql) as a defense-in-depth net for
// any other insert path. When real commercial tiers launch, flip this back
// to 'free', that one change is the entire BETA to production tier switch.
// 2026-08-18: Starter was retired as a customer-facing plan (Pricing is now
// Free / Pro / Elite only). 'starter' remains a technically-valid legacy
// value in user_profiles.tier (no real DB enum) purely for any pre-existing
// row that already carries it; it is never offered again anywhere.
define('DEFAULT_SIGNUP_TIER', 'elite');

// ── Paid plan pricing (single source of truth) ──────────────────────────
// Real, finalized USD prices for the two customer-facing paid plans, used by
// public/pricing.php (and available to any other page that needs to state a
// price rather than re-typing a dollar figure). Free is $0 forever and has
// no entry here. Annual prices are the actual amount charged once a year,
// not a monthly-equivalent, per the product decision to always show the real
// annual charge (with the savings called out) instead of an implied
// per-month rate.
define('PRICE_PRO_MONTHLY', 29.99);
define('PRICE_PRO_ANNUAL', 239.88);
// Stated literally rather than derived (monthly * 12 - annual), since the
// derived figure for Pro is $120.00, twelve cents off the approved copy of
// "$119.88/year" — the approved marketing figure wins, it is what customers
// are told they save, not a value re-derived from the two other numbers.
define('PRICE_PRO_ANNUAL_SAVINGS', 119.88);
define('PRICE_ELITE_MONTHLY', 45.99);
define('PRICE_ELITE_ANNUAL', 431.88);
define('PRICE_ELITE_ANNUAL_SAVINGS', 120.00);

// Ultimate is reserved exclusively for the future "Full Automation" release
// and is currently dormant — not for sale, not user-selectable, and must not
// appear anywhere in user-facing UI (pricing page, upsell copy, etc.) while
// this is false. The underlying tier itself is NOT deleted — 'ultimate'
// stays a valid user_profiles.tier value, and its tier_quotas/
// colex_plan_limits rows stay intact — so it can be switched back on later
// by flipping this one constant, with no data migration needed. Any
// user-facing surface that would otherwise display or offer Ultimate must
// check this flag rather than hardcoding its own tier === 'ultimate' logic.
// (Internal "is this account at least as capable as Elite" checks, e.g.
// reports.php's $isEliteOrUltimate, are unaffected by this flag — those are
// capability checks, not an offer to buy Ultimate, and must keep treating a
// legacy/future ultimate account as Elite-or-above.)
define('ULTIMATE_TIER_ENABLED', false);

// -----------------------------------------------------------------------------
// Error reporting
// -----------------------------------------------------------------------------
if (APP_ENV === 'development') {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
} else {
    error_reporting(E_ALL);
    ini_set('display_errors', '0');
    ini_set('log_errors', '1');
}
