#!/usr/bin/env php
<?php
/**
 * scheduled-weekly-summary.php
 *
 * Builds and queues a single "Weekly Site Health Summary" email per
 * active ai_monitor_subscriptions row. Runs Mondays at 07:00 UTC via
 * cron (see scripts/updates/staging/setup-weekly-summary-cron.sh).
 *
 * Pipeline:
 *   1. SELECT all active subscriptions (status='active') with a non-empty
 *      notification_email.
 *   2. For each, compute domain (WHOIS), SSL, and DNS health for the
 *      primary site domain. WHOIS results are memoised per apex per
 *      script run so one apex is never queried twice.
 *   3. Count issues opened and resolved in the past 7 days from
 *      ai_monitor_issues. Pull the top 5 by severity (critical > warning
 *      > info, ties broken by recency).
 *   4. Render via render_weekly_summary() and INSERT one row into
 *      ai_monitor_email_queue (status='queued'). The drainer
 *      (process-email-queue.php) sends it.
 *   5. Skip the queue insert when the subscription has nothing to say:
 *      no actionable site data AND no opened/resolved issues.
 *
 * Idempotency: before queueing we check ai_monitor_email_queue for an
 * existing row with subject LIKE 'Weekly Site Health Summary%' AND
 * created_at >= NOW() - INTERVAL 6 DAY for the same subscription. If
 * present we skip — re-running the cron the same week never
 * double-queues.
 *
 * Helpers strategy: this script and scheduled-site-checks.php both
 * need whp_probe_ssl_cert / whp_whois_expiry / DNS resolution. Because
 * scheduled-site-checks.php has CLI side effects on inclusion (parses
 * $argv, opens the DB, runs the work loop), we DO NOT require_once it.
 * Instead the helpers were extracted to
 * /docker/whp/web/libs/site_health_helpers.php (source:
 * web-files/libs/site_health_helpers.php) and both scripts can pull
 * from there. This script require_once's that shared lib.
 *
 * CLI flags:
 *   --dry-run    Compute and print the per-subscription summary but do
 *                NOT INSERT into the email queue.
 */

if (PHP_SAPI !== 'cli') {
    fwrite(STDERR, "scheduled-weekly-summary.php is a CLI script.\n");
    exit(2);
}

$dryRun = false;
foreach (array_slice($argv, 1) as $arg) {
    if ($arg === '--dry-run') {
        $dryRun = true;
    } elseif ($arg === '--help' || $arg === '-h') {
        echo "Usage: scheduled-weekly-summary.php [--dry-run]\n";
        exit(0);
    }
}

// ---------------------------------------------------------------------
// Resolve include paths. The script lives at /root/whp/scripts/ on the
// server, while runtime web files are rsync'd to /docker/whp/web/.
// Prefer the production location; fall back to the repo path for any
// dev/staging context where /docker/whp/web/ doesn't exist yet.
// ---------------------------------------------------------------------
$webDir = is_dir('/docker/whp/web/libs')
    ? '/docker/whp/web'
    : __DIR__ . '/../web-files';

require_once $webDir . '/libs/site_health_helpers.php';
require_once $webDir . '/includes/email-templates/weekly-summary.php';
require_once __DIR__ . '/whp_panel_url.php';

// ---------------------------------------------------------------------
// DB bootstrap (mirrors scheduled-site-checks.php).
// Reads root MySQL password from /root/.my.cnf via the same canonical
// pattern used elsewhere in the project (process-email-queue.php,
// scheduled-site-checks.php). The shell command takes no user input.
// ---------------------------------------------------------------------
try {
    $cnf = @file_get_contents('/root/.my.cnf');
    $password = '';
    if ($cnf && preg_match('/^\s*password\s*=\s*(.+)$/m', $cnf, $m)) {
        $password = trim($m[1]);
    }
    $db = new PDO('mysql:host=127.0.0.1;dbname=whp;charset=utf8mb4', 'root', $password);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Throwable $e) {
    fwrite(STDERR, "[fatal] MySQL connect failed: " . $e->getMessage() . "\n");
    exit(1);
}

$serverIp = whp_resolve_server_ip();

$subs = $db->query("
    SELECT sub.id AS subscription_id,
           sub.site_id,
           sub.username,
           sub.notification_email,
           s.site_name,
           COALESCE(sd.fqdn, d.domain_name) AS primary_domain
    FROM whp.ai_monitor_subscriptions sub
    JOIN whp.sites   s ON sub.site_id = s.id
    JOIN whp.domains d ON s.primary_domain_id = d.id
    LEFT JOIN whp.site_domains sd ON sd.site_id = s.id AND sd.is_primary = 1
    WHERE sub.status = 'active'
      AND sub.notification_email IS NOT NULL
      AND sub.notification_email <> ''
")->fetchAll(PDO::FETCH_ASSOC);

if (!$subs) {
    echo date('Y-m-d H:i:s') . " No active subscriptions with a notification_email.\n";
    exit(0);
}

echo date('Y-m-d H:i:s') . " weekly-summary: " . count($subs)
   . " subscription(s); server IP=" . ($serverIp ?: 'unknown')
   . ($dryRun ? ' [DRY RUN]' : '') . "\n";

// Per-script-run WHOIS cache. Keyed by apex; values are int|false (false
// = lookup attempted, no expiry parsable). Saves a `whois` shell-out
// when multiple subs share the same apex.
$whoisCache = [];

// "Week of" label = the Monday of this week (UTC).
$weekOf = date('Y-m-d', strtotime('monday this week'));

foreach ($subs as $sub) {
    $line = process_subscription($db, $sub, $serverIp, $whoisCache, $weekOf, $dryRun);
    echo $line . "\n";
}

echo date('Y-m-d H:i:s') . " weekly-summary complete.\n";

// =====================================================================
// Per-subscription work
// =====================================================================

function process_subscription(PDO $db, array $sub, ?string $serverIp,
                              array &$whoisCache, string $weekOf, bool $dryRun): string {
    $subId  = (int)$sub['subscription_id'];
    $domain = (string)$sub['primary_domain'];
    $name   = (string)($sub['site_name'] ?: $domain);

    // 1. Idempotency check: don't queue twice in the same ~week.
    if (!$dryRun && weekly_summary_already_queued($db, $subId)) {
        return sprintf('  sub=%d %-40s SKIP (already queued in last 6d)', $subId, $name);
    }

    // 2. Probe domain health.
    $whois = probe_whois($domain, $whoisCache);
    $ssl   = probe_ssl($domain);
    $dns   = whp_check_dns_health($domain, $serverIp);

    // 3. Issue counts and top issues.
    $opened   = count_issues_opened($db, $subId);
    $resolved = count_issues_resolved($db, $subId);
    $top      = top_issues($db, $subId, $name);

    // 4. Build advisories.
    $advisories = build_advisories($name, $domain, $whois, $ssl, $dns);

    // 5. "Substance" gate: skip when nothing actionable.
    $hasSubstance = $opened > 0
                 || $resolved > 0
                 || !empty($advisories)
                 || ($whois['status'] ?? '') === 'expiring' || ($whois['status'] ?? '') === 'expired'
                 || ($ssl['status']   ?? '') === 'expiring' || ($ssl['status']   ?? '') === 'expired'
                 || in_array(($dns['status'] ?? ''), ['mismatch', 'nxdomain'], true);

    if (!$hasSubstance) {
        return sprintf('  sub=%d %-40s SKIP (nothing actionable: 0 issues, all health green)',
            $subId, $name);
    }

    // 6. Build the data payload.
    $dashboardUrl = whp_panel_url('page=ai-monitor-customer');

    $payload = [
        'week_of'         => $weekOf,
        'sites'           => [[
            'site_name' => $name,
            'domain'    => $domain,
            'whois'     => $whois,
            'ssl'       => $ssl,
            'dns'       => $dns,
        ]],
        'issues_opened'   => $opened,
        'issues_resolved' => $resolved,
        'top_issues'      => $top,
        'advisories'      => $advisories,
        'dashboard_url'   => $dashboardUrl,
    ];

    $rendered = render_weekly_summary($payload);

    $subject = "Weekly Site Health Summary — " . $name . " — week of " . $weekOf;

    if ($dryRun) {
        return sprintf('  sub=%d %-40s DRY [opened=%d resolved=%d advisories=%d]',
            $subId, $name, $opened, $resolved, count($advisories));
    }

    $db->prepare("
        INSERT INTO whp.ai_monitor_email_queue
            (subscription_id, recipient_email, subject, body_html, body_text,
             status, created_at, max_attempts)
        VALUES (?, ?, ?, ?, ?, 'queued', NOW(), 3)
    ")->execute([
        $subId,
        $sub['notification_email'],
        $subject,
        $rendered['html'],
        $rendered['text'],
    ]);

    return sprintf('  sub=%d %-40s QUEUED [opened=%d resolved=%d advisories=%d]',
        $subId, $name, $opened, $resolved, count($advisories));
}

// =====================================================================
// Probes
// =====================================================================

function probe_whois(string $domain, array &$cache): array {
    $apex = whp_apex_for_whois($domain);
    if (!$apex) {
        return ['status' => 'skipped', 'days_left' => null, 'apex' => null];
    }
    if (!array_key_exists($apex, $cache)) {
        $cache[$apex] = whp_whois_expiry($apex) ?: false;
    }
    $expiresAt = $cache[$apex];
    if ($expiresAt === false) {
        return ['status' => 'unparsed', 'days_left' => null, 'apex' => $apex];
    }
    $daysLeft = (int)floor(($expiresAt - time()) / 86400);
    if ($daysLeft < 0) {
        return ['status' => 'expired', 'days_left' => $daysLeft, 'apex' => $apex];
    }
    if ($daysLeft <= 30) {
        return ['status' => 'expiring', 'days_left' => $daysLeft, 'apex' => $apex];
    }
    return ['status' => 'ok', 'days_left' => $daysLeft, 'apex' => $apex];
}

function probe_ssl(string $domain): array {
    if (strpos($domain, '*') !== false) {
        return ['status' => 'unreachable', 'days_left' => null];
    }
    $info = whp_probe_ssl_cert($domain);
    if (!$info) {
        return ['status' => 'unreachable', 'days_left' => null];
    }
    $daysLeft = (int)floor(($info['expires_at'] - time()) / 86400);
    if ($daysLeft < 0) return ['status' => 'expired',  'days_left' => $daysLeft];
    if ($daysLeft <= 30) return ['status' => 'expiring', 'days_left' => $daysLeft];
    return ['status' => 'ok', 'days_left' => $daysLeft];
}

// =====================================================================
// Issue queries
// =====================================================================

function count_issues_opened(PDO $db, int $subId): int {
    $stmt = $db->prepare("
        SELECT COUNT(*) FROM whp.ai_monitor_issues
        WHERE subscription_id = ?
          AND created_at >= NOW() - INTERVAL 7 DAY
    ");
    $stmt->execute([$subId]);
    return (int)$stmt->fetchColumn();
}

function count_issues_resolved(PDO $db, int $subId): int {
    $stmt = $db->prepare("
        SELECT COUNT(*) FROM whp.ai_monitor_issues
        WHERE subscription_id = ?
          AND status IN ('fixed','ignored')
          AND status_changed_at IS NOT NULL
          AND status_changed_at >= NOW() - INTERVAL 7 DAY
    ");
    $stmt->execute([$subId]);
    return (int)$stmt->fetchColumn();
}

function top_issues(PDO $db, int $subId, string $siteFallback): array {
    // Severity ordering: critical > warning > info, ties by recency.
    $stmt = $db->prepare("
        SELECT severity, title
        FROM whp.ai_monitor_issues
        WHERE subscription_id = ?
          AND created_at >= NOW() - INTERVAL 7 DAY
        ORDER BY FIELD(severity, 'critical','warning','info'), last_seen DESC
        LIMIT 5
    ");
    $stmt->execute([$subId]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    $out = [];
    foreach ($rows as $r) {
        $out[] = [
            'severity' => $r['severity'] ?? 'info',
            'title'    => $r['title']    ?? '',
            'site'     => $siteFallback,
        ];
    }
    return $out;
}

// =====================================================================
// Advisories — plain-English bullets driven off probe results
// =====================================================================

function build_advisories(string $siteName, string $domain,
                          array $whois, array $ssl, array $dns): array {
    $out = [];

    // Domain expiry advisories
    if (($whois['status'] ?? '') === 'expired') {
        $abs = abs((int)($whois['days_left'] ?? 0));
        $out[] = "Domain {$domain} EXPIRED {$abs} days ago — renew immediately at your registrar before it lapses.";
    } elseif (($whois['status'] ?? '') === 'expiring') {
        $d = (int)($whois['days_left'] ?? 0);
        $out[] = "Domain {$domain} expires in {$d} days — schedule a renewal at your registrar.";
    }

    // SSL expiry advisories
    if (($ssl['status'] ?? '') === 'expired') {
        $abs = abs((int)($ssl['days_left'] ?? 0));
        $out[] = "SSL certificate for {$domain} EXPIRED {$abs} days ago — force a Let's Encrypt renewal from the SSL panel.";
    } elseif (($ssl['status'] ?? '') === 'expiring') {
        $d = (int)($ssl['days_left'] ?? 0);
        if ($d < 14) {
            $out[] = "SSL on {$domain} expires in {$d} days — schedule renewal now (Let's Encrypt usually auto-renews around 30 days out; if it hasn't, force it from the SSL panel).";
        } else {
            $out[] = "SSL on {$domain} expires in {$d} days — Let's Encrypt should auto-renew, but keep an eye on it.";
        }
    } elseif (($ssl['status'] ?? '') === 'unreachable') {
        $out[] = "SSL for {$domain} could not be probed — verify the site is reachable on port 443.";
    }

    // DNS advisories
    if (($dns['status'] ?? '') === 'nxdomain') {
        $out[] = "DNS for {$domain} is not resolving — check your registrar / DNS provider has an A record set.";
    } elseif (($dns['status'] ?? '') === 'mismatch') {
        $first = !empty($dns['ips']) ? $dns['ips'][0] : 'unexpected IP';
        $out[] = "DNS for {$domain} points to {$first} instead of this server — update the A record at your DNS provider.";
    }

    return $out;
}

// =====================================================================
// Idempotency
// =====================================================================

function weekly_summary_already_queued(PDO $db, int $subId): bool {
    $stmt = $db->prepare("
        SELECT COUNT(*) FROM whp.ai_monitor_email_queue
        WHERE subscription_id = ?
          AND subject LIKE 'Weekly Site Health Summary%'
          AND created_at >= NOW() - INTERVAL 6 DAY
    ");
    $stmt->execute([$subId]);
    return ((int)$stmt->fetchColumn()) > 0;
}
