<?php
// CLI: Bichon archival reconciliation. Run from bichon-archival-sync.sh.
// Per WHP convention: require config.php FIRST so MYSQL_PASS / NONCE_SALT are defined.

if (php_sapi_name() !== 'cli') { http_response_code(403); exit("CLI only\n"); }

require_once('/docker/whp/web/configs/config.php');
require_once('/docker/whp/web/libs/BichonClient.php');
require_once('/docker/whp/web/libs/emailmgmt.php');
require_once('/docker/whp/web/libs/mysqlmgmt.php');

if (!BichonClient::isConfigured()) {
    fwrite(STDERR, "Bichon not configured; nothing to do.\n");
    exit(0);
}

// 1. Pull all archival-enabled WHP rows (id, username, email_address, bichon_account_id, archival_paused_at)
$mysql = new mysqlmgmt();
$db    = $mysql->getMySQLConnection();
$rows  = $db->query("
    SELECT id, username, email_address, bichon_account_id, archival_paused_at
    FROM whp.email_accounts
    WHERE archival_enabled = 1
      AND bichon_account_id IS NOT NULL
")->fetchAll(PDO::FETCH_ASSOC);

if (empty($rows)) {
    echo "No archival-enabled mailboxes to reconcile.\n";
    exit(0);
}

// 2. Pull Bichon's view in a single listAccounts call (avoids N+1).
$bc       = new BichonClient();
$listResp = $bc->listAccounts();
if (!$listResp['ok']) {
    fwrite(STDERR, "Bichon listAccounts failed: " . $listResp['msg'] . " (HTTP " . $listResp['http'] . ")\n");
    exit(1);
}

// Index Bichon's accounts by id for fast lookup.
// The Bichon response shape: $listResp['raw'] is decoded JSON; we expect either a top-level
// array of account objects or an envelope like {accounts:[...]} or {data:[...]}. Be tolerant.
$bichonAccounts = [];
$listRaw = $listResp['raw'] ?? [];
if (is_array($listRaw) && isset($listRaw[0])) {
    $bichonAccounts = $listRaw;
} elseif (isset($listRaw['items']) && is_array($listRaw['items'])) {
    // Bichon's paginated shape: {"current_page":..., "items":[...]}
    $bichonAccounts = $listRaw['items'];
} elseif (isset($listRaw['accounts']) && is_array($listRaw['accounts'])) {
    $bichonAccounts = $listRaw['accounts'];
} elseif (isset($listRaw['data']) && is_array($listRaw['data'])) {
    $bichonAccounts = $listRaw['data'];
} else {
    // Unknown shape - fall back to per-account GETs below.
    fwrite(STDERR, "Bichon list response shape unrecognized; falling back to per-account GETs.\n");
}

$indexed = [];
foreach ($bichonAccounts as $b) {
    $id = $b['id'] ?? $b['account_id'] ?? null;
    if ($id !== null) $indexed[(string)$id] = $b;
}

// 3. Reconcile.
$EmailMgmt = new emailmgmt();

$paused_count  = 0;
$resumed_count = 0;
$missing_count = 0;
$ok_count      = 0;

foreach ($rows as $row) {
    $bid = (string)$row['bichon_account_id'];
    $bichon = $indexed[$bid] ?? null;

    if ($bichon === null && empty($indexed)) {
        // List had unknown shape; do a per-account GET.
        $g = $bc->getAccount($bid);
        if ($g['ok']) {
            $bichon = $g['raw'] ?? [];
        }
    }

    if ($bichon === null) {
        // Bichon has no record of this account anymore - mark paused with a clear reason.
        $missing_count++;
        $EmailMgmt->markArchivalPaused((int)$row['id'], 'Bichon has no record of this account');
        continue;
    }

    // Heuristic for failing status. Bichon's exact status field name isn't documented;
    // try common candidates, but bias toward "paused/failed" if any of these match.
    $status = $bichon['status']        ?? $bichon['sync_status'] ?? $bichon['state']      ?? '';
    $error  = $bichon['error']         ?? $bichon['last_error']  ?? $bichon['error_msg']  ?? '';
    $statusLow = strtolower((string)$status);
    $isFailing = (
        $statusLow === 'failed' ||
        $statusLow === 'error'  ||
        $statusLow === 'paused' ||
        $statusLow === 'auth_failed' ||
        $statusLow === 'imap_login_failed' ||
        ($error !== '' && stripos($error, 'login') !== false)
    );

    if ($isFailing) {
        if (empty($row['archival_paused_at'])) {
            $paused_count++;
        }
        $reason = $error !== '' ? $error : ('Bichon status: ' . ($status ?: 'unknown'));
        $EmailMgmt->markArchivalPaused((int)$row['id'], $reason);
    } else {
        // Healthy in Bichon's view.
        if (!empty($row['archival_paused_at'])) {
            // Was paused, now back. Clear pause state and audit-log 'resumed'.
            $u = $db->prepare("
                UPDATE whp.email_accounts
                   SET archival_paused_at = NULL, archival_last_error = NULL
                 WHERE id = ?
            ");
            $u->execute([(int)$row['id']]);
            $a = $db->prepare("
                INSERT INTO whp.email_archival_events
                    (email_account_id, username, event_type, bichon_account_id, actor)
                VALUES (?, ?, 'resumed', ?, 'system')
            ");
            $a->execute([(int)$row['id'], $row['username'], $bid]);
            $resumed_count++;
        }
        $ok_count++;
    }

    // Self-heal: ensure sync_folders covers known_folders. Idempotent — the
    // helper short-circuits when coverage is already complete. Catches both
    // archives created before this self-heal logic shipped, AND any newly-
    // discovered folders that haven't been added to sync_folders yet.
    $EmailMgmt->syncBichonFolders($bid);
}

printf("Reconciled %d mailboxes - ok=%d paused=%d resumed=%d missing=%d\n",
    count($rows), $ok_count, $paused_count, $resumed_count, $missing_count);

// Purge soft-disabled archives whose 14-day grace period has elapsed.
$purgeSummary = $EmailMgmt->purgeExpiredArchives();
printf("Purged %d expired archives (failed=%d).\n",
    (int)($purgeSummary['purged'] ?? 0),
    (int)($purgeSummary['failed'] ?? 0));
if (!empty($purgeSummary['errors'])) {
    foreach ($purgeSummary['errors'] as $errLine) {
        fwrite(STDERR, "purge error: " . $errLine . "\n");
    }
}
exit(0);
