<?php
// tests/test-saas-gating.php — SaaS subscription gating: plan limits, billing auth, DB state
require_once __DIR__ . '/run.php';

$base = TEST_BASE_URL;

// ══════════════════════════════════════════════════════════════
// 1. PLAN LIMITS — unit tests (no HTTP, no DB)
// ══════════════════════════════════════════════════════════════

function _tsg_plan_limits(string $plan): array {
    return match($plan) {
        'starter'    => ['clients' => 1,  'proposals' => 3,  'users' => 1,  'modules' => 3],
        'pro'        => ['clients' => 5,  'proposals' => 12, 'users' => 3,  'modules' => 99],
        'agency'     => ['clients' => 7,  'proposals' => 25, 'users' => 8,  'modules' => 99],
        'enterprise' => ['clients' => 15, 'proposals' => 50, 'users' => 20, 'modules' => 99],
        default      => ['clients' => 1,  'proposals' => 1,  'users' => 1,  'modules' => 99],
    };
}

$s = _tsg_plan_limits('starter');
dm_assert('Plan: starter proposals=3',     $s['proposals'] === 3,  "Got {$s['proposals']}");
dm_assert('Plan: starter clients=1',       $s['clients']   === 1,  "Got {$s['clients']}");
dm_assert('Plan: starter modules=3',       $s['modules']   === 3,  "Got {$s['modules']}");

$p = _tsg_plan_limits('pro');
dm_assert('Plan: pro proposals=12',        $p['proposals'] === 12, "Got {$p['proposals']}");
dm_assert('Plan: pro clients=5',           $p['clients']   === 5,  "Got {$p['clients']}");

$ag = _tsg_plan_limits('agency');
dm_assert('Plan: agency proposals=25',     $ag['proposals'] === 25, "Got {$ag['proposals']}");
dm_assert('Plan: agency clients=7',        $ag['clients']   === 7,  "Got {$ag['clients']}");

$en = _tsg_plan_limits('enterprise');
dm_assert('Plan: enterprise proposals=50', $en['proposals'] === 50, "Got {$en['proposals']}");
dm_assert('Plan: enterprise clients=15',   $en['clients']   === 15, "Got {$en['clients']}");
dm_assert('Plan: enterprise users=20',     $en['users']     === 20, "Got {$en['users']}");

// Starter must be strictly less than Pro
dm_assert('Plan: starter < pro (proposals)', $s['proposals'] < $p['proposals'], 'starter >= pro');
dm_assert('Plan: starter < pro (clients)',   $s['clients']   < $p['clients'],   'starter >= pro');

// ══════════════════════════════════════════════════════════════
// 2. BILLING ENDPOINTS — must block without session
// ══════════════════════════════════════════════════════════════

foreach (['get_status', 'create_checkout', 'portal', 'cancel', 'change_plan', 'save_modules'] as $action) {
    $url = "{$base}/api/billing.php?action={$action}";
    $ch  = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER  => true,
        CURLOPT_FOLLOWLOCATION  => false,
        CURLOPT_TIMEOUT         => 6,
        CURLOPT_SSL_VERIFYPEER  => false,
    ]);
    $raw  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    $json    = json_decode($raw, true);
    $blocked = in_array($code, [401, 302, 403])
            || ($json && isset($json['ok']) && $json['ok'] === false);
    dm_assert("Billing auth block: {$action}", $blocked, "Got HTTP {$code}");
}

// ══════════════════════════════════════════════════════════════
// 3. DATABASE — subscription state validation
// ══════════════════════════════════════════════════════════════

if (!file_exists(__DIR__ . '/../config.php')) {
    dm_skip('DB: subscription states', 'config.php not found');
    return;
}

require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../db.php';

if (!$pdo) {
    dm_skip('DB: subscription states', 'No DB connection');
    dm_skip('DB: proposals integrity',  'No DB connection');
    dm_skip('DB: starter modules ≤3',   'No DB connection');
    dm_skip('DB: no stale trials',      'No DB connection');
    dm_skip('DB: no stuck webhooks',    'No DB connection');
    return;
}

// 3a. All subscriptions have valid status values
$valid_statuses = ['trial', 'active', 'past_due', 'canceled', 'blocked', 'manual'];
$subs = $pdo->query("SELECT user_id, plan, status, proposals_used, proposals_limit FROM dm_subscriptions")->fetchAll();
$all_valid = true;
foreach ($subs as $sub) {
    if (!in_array($sub['status'], $valid_statuses)) { $all_valid = false; break; }
}
dm_assert('DB: all subscription statuses are valid', $all_valid,
    'Found invalid status value — check dm_subscriptions.status ENUM');

// 3b. proposals_used never exceeds proposals_limit
$ok = true;
foreach ($subs as $sub) {
    if ((int)$sub['proposals_used'] > (int)$sub['proposals_limit']) { $ok = false; break; }
}
dm_assert('DB: proposals_used ≤ proposals_limit for all users', $ok,
    'A user has proposals_used > proposals_limit — data integrity issue');

// 3c. Starter plans have ≤3 modules chosen
$ok = true;
$starters = $pdo->query(
    "SELECT user_id, modules_chosen FROM dm_subscriptions WHERE plan='starter' AND modules_chosen IS NOT NULL"
)->fetchAll();
foreach ($starters as $row) {
    $mods = json_decode($row['modules_chosen'], true) ?: [];
    if (count($mods) > 3) { $ok = false; break; }
}
dm_assert('DB: starter plan modules_chosen ≤ 3', $ok,
    'A starter user has more than 3 modules — gating bypass or data corruption');

// 3d. No trials expired >24h still in status=trial (billing-cron health)
$stale = (int)$pdo->query(
    "SELECT COUNT(*) FROM dm_subscriptions
     WHERE status='trial' AND trial_ends_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)"
)->fetchColumn();
dm_assert('DB: no stale expired trials', $stale === 0,
    "{$stale} trial(s) past expiry still marked 'trial' — billing-cron may not be running");

// 3e. No webhook events stuck in 'received' for >1 hour
$stuck = (int)$pdo->query(
    "SELECT COUNT(*) FROM dm_webhook_log
     WHERE status='received' AND created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)"
)->fetchColumn();
dm_assert('DB: no stuck webhook events (>1h)', $stuck === 0,
    "{$stuck} Stripe webhook(s) stuck unprocessed — check billing.php webhook handler");
