#!/usr/bin/env bash
#
# Backfill the new renewal_state columns on whp.haproxy_domains using current
# Let's Encrypt + DNS reality. Idempotent — safe to re-run.
#
# Decision tree per row:
#   - No site binding (orphan)                     → renewal_state='deferred'
#   - DNS unresolvable (NXDOMAIN/no A record)      → renewal_state='unreachable'
#                                                    next_renewal_attempt_at=+24h
#   - LE cert exists and renewed < 30 days ago     → renewal_state='ok'
#                                                    next_renewal_attempt_at=ssl_last_renewal+60d
#   - LE cert exists but is older                  → renewal_state='pending'
#   - LE cert missing entirely                     → renewal_state='pending'
#
# This script does NOT delete certificate files or renewal configs — it only
# updates DB state. Manual cleanup of orphan /etc/letsencrypt/live/ lineages is
# a separate step.

set -euo pipefail

WEB_DIR="/docker/whp/web"

# Cert lineages live inside the haproxy-manager container's /etc/letsencrypt
# volume. List them out to a temp file so the PHP block below can read them
# without needing docker access from PHP.
LINEAGE_FILE="/tmp/whp-ssl-lineages.$$"
trap 'rm -f "${LINEAGE_FILE}"' EXIT

# Try the docker exec directly — it'll fail silently if the container isn't
# present, and we treat that as "no certs to enumerate" (DNS reachability
# alone still drives a useful first-pass state).
docker exec haproxy-manager ls /etc/letsencrypt/live/ 2>/dev/null \
    | grep -v '^README$' > "${LINEAGE_FILE}" || true

if [ ! -s "${LINEAGE_FILE}" ]; then
    echo "Note: no certificate lineages enumerated (haproxy-manager not running, or no certs yet)."
fi

php <<PHP
<?php
require_once('${WEB_DIR}/configs/config.php');

// config.php only defines constants (MYSQL_PASS etc) — instantiate PDO here.
// (cf. feedback_php_cli_wrappers.md — same pattern other WHP CLI scripts follow.)
\$db = new PDO('mysql:host=127.0.0.1;dbname=whp;charset=utf8mb4', 'root', trim(MYSQL_PASS), [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

\$lineage_file = '${LINEAGE_FILE}';
\$cert_lineages = [];
if (is_file(\$lineage_file)) {
    foreach (file(\$lineage_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as \$entry) {
        // Strip the certbot -NNNN suffix to match haproxy_domains.domain_name
        \$base = preg_replace('/-\d{4,}\$/', '', trim(\$entry));
        \$cert_lineages[\$base] = trim(\$entry);
    }
}

\$rows = \$db->query("
    SELECT id, domain_name, is_wildcard, ssl_enabled, ssl_last_renewal, renewal_state
    FROM whp.haproxy_domains
")->fetchAll(PDO::FETCH_ASSOC);

\$counts = [
    'examined' => 0,
    'set_ok' => 0,
    'set_unreachable' => 0,
    'set_pending' => 0,
    'set_deferred' => 0,
];

foreach (\$rows as \$row) {
    \$counts['examined']++;
    \$id = (int)\$row['id'];
    \$name = \$row['domain_name'];
    \$is_wildcard = (int)\$row['is_wildcard'] === 1;

    \$owner_stmt = \$db->prepare("
        SELECT s.id FROM whp.sites s
        JOIN whp.site_domains sd ON sd.site_id = s.id
        JOIN whp.domains d ON d.id = sd.domain_id
        WHERE COALESCE(sd.fqdn, d.domain_name) = ?
        LIMIT 1
    ");
    \$owner_stmt->execute([\$name]);
    \$has_site = (bool)\$owner_stmt->fetchColumn();

    if (!\$has_site) {
        \$db->prepare("
            UPDATE whp.haproxy_domains
            SET renewal_state = 'deferred',
                last_renewal_error = 'No site binding found at backfill time',
                next_renewal_attempt_at = NULL
            WHERE id = ?
        ")->execute([\$id]);
        \$counts['set_deferred']++;
        continue;
    }

    \$check_name = \$is_wildcard && str_starts_with(\$name, '*.')
        ? substr(\$name, 2)
        : \$name;
    \$resolved = @gethostbyname(\$check_name);
    \$reachable = (\$resolved !== \$check_name && filter_var(\$resolved, FILTER_VALIDATE_IP));

    if (!\$reachable) {
        \$db->prepare("
            UPDATE whp.haproxy_domains
            SET renewal_state = 'unreachable',
                last_renewal_error = ?,
                next_renewal_attempt_at = (NOW() + INTERVAL 24 HOUR),
                dns_validation = ?
            WHERE id = ?
        ")->execute([
            "Backfill: DNS lookup for \$check_name returned no A record",
            \$is_wildcard ? 1 : 0,
            \$id,
        ]);
        \$counts['set_unreachable']++;
        continue;
    }

    \$cert_key = \$is_wildcard && str_starts_with(\$name, '*.')
        ? substr(\$name, 2)
        : \$name;
    \$has_cert = isset(\$cert_lineages[\$cert_key]);
    \$last_renewal = \$row['ssl_last_renewal'];

    if (\$has_cert && \$last_renewal && (strtotime(\$last_renewal) > strtotime('-30 days'))) {
        \$db->prepare("
            UPDATE whp.haproxy_domains
            SET renewal_state = 'ok',
                last_renewal_error = NULL,
                consecutive_failures = 0,
                next_renewal_attempt_at = (? + INTERVAL 60 DAY),
                dns_validation = ?
            WHERE id = ?
        ")->execute([\$last_renewal, \$is_wildcard ? 1 : 0, \$id]);
        \$counts['set_ok']++;
    } else {
        \$db->prepare("
            UPDATE whp.haproxy_domains
            SET renewal_state = 'pending',
                next_renewal_attempt_at = NOW(),
                dns_validation = ?
            WHERE id = ?
        ")->execute([\$is_wildcard ? 1 : 0, \$id]);
        \$counts['set_pending']++;
    }
}

echo "ssl-renewal-state-backfill: " . json_encode(\$counts) . "\n";
PHP

echo "Backfill complete."
