#!/usr/bin/env php
<?php
/**
 * Petition Email CLI Tool
 * 
 * Interactive tool for managing petition emails:
 * - View failed email sends from SMTP log
 * - View unconfirmed signatures from petition CSVs
 * - Retry sending emails with rate limiting (250/hour max)
 * - Mark entries as ignored (for malformed emails etc.)
 * 
 * Usage: php custom/petition-cli.php
 */

// Ensure CLI only
if (php_sapi_name() !== 'cli') {
    die("This script can only be run from the command line.\n");
}

define('BASE_DIR', __DIR__);
define('DATA_DIR', BASE_DIR . '/data');
define('PETITIONS_DIR', DATA_DIR . '/petitions');
define('SMTP_LOG', DATA_DIR . '/smtp-log.csv');
define('IGNORE_LIST', DATA_DIR . '/petition-ignore.csv');

// Rate limit: 250 emails/hour = 1 email per 14.4 seconds, using 15 seconds
define('EMAIL_DELAY_SECONDS', 15);

/**
 * Load SMTP configuration
 */
function loadSmtpConfig(): ?array {
    $configPath = BASE_DIR . '/smtp-config.php';
    if (!file_exists($configPath)) {
        return null;
    }
    return require $configPath;
}

/**
 * Get all petition CSV files
 */
function getPetitionFiles(): array {
    if (!is_dir(PETITIONS_DIR)) {
        return [];
    }
    
    $files = glob(PETITIONS_DIR . '/*.csv');
    return array_map(function($f) {
        return basename($f, '.csv');
    }, $files);
}

/**
 * Load ignore list
 */
function loadIgnoreList(): array {
    if (!file_exists(IGNORE_LIST)) {
        return [];
    }
    
    $ignored = [];
    $fp = fopen(IGNORE_LIST, 'r');
    if (!$fp) {
        return [];
    }
    
    // Skip header
    fgetcsv($fp, null, ',', '"', '');
    
    // CSV format: timestamp, type, petition_id, email, reason
    while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
        if (isset($row[3])) {
            // Key format: type|petition_id|email
            $key = ($row[1] ?? '') . '|' . ($row[2] ?? '') . '|' . strtolower($row[3]);
            $ignored[$key] = [
                'timestamp' => (int)$row[0],
                'type' => $row[1],
                'petition_id' => $row[2],
                'email' => $row[3],
                'reason' => $row[4] ?? ''
            ];
        }
    }
    
    fclose($fp);
    return $ignored;
}

/**
 * Add entry to ignore list
 */
function addToIgnoreList(string $type, string $petitionId, string $email, string $reason): bool {
    $isNewFile = !file_exists(IGNORE_LIST);
    
    $fp = fopen(IGNORE_LIST, 'a');
    if (!$fp) {
        return false;
    }
    
    if (flock($fp, LOCK_EX)) {
        if ($isNewFile) {
            fputcsv($fp, ['timestamp', 'type', 'petition_id', 'email', 'reason'], ',', '"', '');
        }
        
        fputcsv($fp, [
            time(),
            $type,
            $petitionId,
            $email,
            $reason
        ], ',', '"', '');
        
        flock($fp, LOCK_UN);
        fclose($fp);
        return true;
    }
    
    fclose($fp);
    return false;
}

/**
 * Check if entry is ignored
 */
function isIgnored(array $ignoreList, string $type, string $petitionId, string $email): bool {
    $key = $type . '|' . $petitionId . '|' . strtolower($email);
    return isset($ignoreList[$key]);
}

/**
 * Read failed emails from SMTP log
 */
function getFailedEmails(bool $excludeIgnored = true): array {
    if (!file_exists(SMTP_LOG)) {
        return [];
    }
    
    $ignoreList = $excludeIgnored ? loadIgnoreList() : [];
    
    $failed = [];
    $fp = fopen(SMTP_LOG, 'r');
    if (!$fp) {
        return [];
    }
    
    // Skip header
    fgetcsv($fp, null, ',', '"', '');
    
    // CSV format: timestamp, type, petition_id, email, status, error_message
    while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
        if (isset($row[4]) && $row[4] === 'failed') {
            $entry = [
                'timestamp' => (int)$row[0],
                'type' => $row[1],
                'petition_id' => $row[2],
                'email' => $row[3],
                'error' => $row[5] ?? ''
            ];
            
            // Skip if ignored
            if ($excludeIgnored && isIgnored($ignoreList, $entry['type'], $entry['petition_id'], $entry['email'])) {
                continue;
            }
            
            $failed[] = $entry;
        }
    }
    
    fclose($fp);
    
    // Sort by timestamp descending (newest first)
    usort($failed, fn($a, $b) => $b['timestamp'] - $a['timestamp']);
    
    // Remove duplicates (keep only latest failure per email+type+petition)
    $unique = [];
    $seen = [];
    foreach ($failed as $entry) {
        $key = $entry['email'] . '|' . $entry['type'] . '|' . $entry['petition_id'];
        if (!isset($seen[$key])) {
            $seen[$key] = true;
            $unique[] = $entry;
        }
    }
    
    return $unique;
}

/**
 * Get unconfirmed signatures from a petition CSV
 */
function getUnconfirmedSignatures(string $petitionId, bool $excludeIgnored = true): array {
    $csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
    if (!file_exists($csvPath)) {
        return [];
    }
    
    $ignoreList = $excludeIgnored ? loadIgnoreList() : [];
    
    $unconfirmed = [];
    $fp = fopen($csvPath, 'r');
    if (!$fp) {
        return [];
    }
    
    // Skip header
    fgetcsv($fp, null, ',', '"', '');
    
    // CSV format: timestamp, email, firstname, surname, region, display, status, token, token_created, ip_hash
    while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
        if (isset($row[6]) && $row[6] === 'pending') {
            $tokenCreated = isset($row[8]) ? (int)$row[8] : (int)$row[0];
            $entry = [
                'timestamp' => (int)$row[0],
                'email' => $row[1],
                'firstname' => $row[2],
                'surname' => $row[3],
                'region' => $row[4],
                'display' => $row[5],
                'token' => $row[7],
                'token_created' => $tokenCreated,
                'petition_id' => $petitionId
            ];
            
            // Skip if ignored
            if ($excludeIgnored && isIgnored($ignoreList, 'unconfirmed', $petitionId, $entry['email'])) {
                continue;
            }
            
            $unconfirmed[] = $entry;
        }
    }
    
    fclose($fp);
    
    // Sort by timestamp descending
    usort($unconfirmed, fn($a, $b) => $b['timestamp'] - $a['timestamp']);
    
    return $unconfirmed;
}

/**
 * Get all unconfirmed signatures from all petitions
 */
function getAllUnconfirmedSignatures(bool $excludeIgnored = true): array {
    $all = [];
    foreach (getPetitionFiles() as $petitionId) {
        $all = array_merge($all, getUnconfirmedSignatures($petitionId, $excludeIgnored));
    }
    
    // Sort by timestamp descending
    usort($all, fn($a, $b) => $b['timestamp'] - $a['timestamp']);
    
    return $all;
}

/**
 * Send a single email using PHPMailer
 */
function sendEmail(string $to, string $toName, string $subject, string $body): array {
    $config = loadSmtpConfig();
    if (!$config || !$config['enabled']) {
        return ['success' => false, 'error' => 'SMTP ikke konfigurert eller deaktivert'];
    }
    
    $fromEmail = $config['petition']['from_email'] ?? $config['from_email'];
    $fromName = $config['petition']['from_name'] ?? $config['from_name'];
    
    // Get petition-specific SMTP settings (allows separate SMTP account for better deliverability)
    $smtpHost = $config['petition']['host'] ?? $config['host'];
    $smtpPort = $config['petition']['port'] ?? $config['port'];
    $smtpUser = $config['petition']['username'] ?? $config['username'];
    $smtpPass = $config['petition']['password'] ?? $config['password'];
    
    // Pre-flight check
    $fp = @fsockopen($smtpHost, $smtpPort, $errno, $errstr, 10);
    if (!$fp) {
        return ['success' => false, 'error' => "Tilkobling feilet: {$errno} - {$errstr}"];
    }
    fclose($fp);
    
    try {
        require_once BASE_DIR . '/vendor/PHPMailer.Lite.php';
        
        $mail = new \codeworxtech\PHPMailerLite\PHPMailerLite();
        
        $mail->SetSMTPhost($smtpHost);
        $mail->SetSMTPport($smtpPort);
        $mail->SetSMTPuser($smtpUser);
        $mail->SetSMTPpass($smtpPass);
        
        $mail->SetSender([$fromEmail => $fromName]);
        $mail->AddRecipient([$to => $toName]);
        $mail->SetSubject($subject);
        $mail->SetBodyText($body);
        
        ob_start();
        $sent = @$mail->Send('smtp');
        $output = ob_get_clean();
        
        if (!$sent || stripos($output, 'error') !== false || stripos($output, '&#10007;') !== false) {
            return ['success' => false, 'error' => strip_tags($output) ?: 'Sending feilet'];
        }
        
        return ['success' => true, 'error' => ''];
    } catch (\Exception $e) {
        return ['success' => false, 'error' => $e->getMessage()];
    }
}

/**
 * Build confirmation email body
 */
function buildConfirmationEmail(array $signature, string $petitionTitle): array {
    $confirmUrl = "https://stopplidelsen.no/underskriftskampanje/{$signature['petition_id']}/?confirm={$signature['token']}#sign-now";
    
    $body = "Hei {$signature['firstname']}!\n\n";
    $body .= "Takk for at du signerte underskriftskampanjen \"{$petitionTitle}\".\n\n";
    $body .= "Klikk lenken under for a bekrefte signaturen din:\n";
    $body .= "{$confirmUrl}\n\n";
    $body .= "Lenken utloper om 30 dager.\n\n";
    $body .= "Hvis du ikke signerte denne kampanjen, kan du trygt ignorere denne e-posten.\n\n";
    $body .= "---\n\n";
    $body .= "Du kan trekke tilbake signaturen din nar som helst ved a folge lenken i bekreftelsese-posten.\n\n";
    $body .= "Med vennlig hilsen,\n";
    $body .= "Stopp lidelsen\n";
    
    return [
        'subject' => 'Bekreft signaturen din',
        'body' => $body
    ];
}

/**
 * Log email result to SMTP log
 */
function logEmailResult(string $type, string $petitionId, string $email, bool $success, string $error = ''): void {
    $fp = fopen(SMTP_LOG, 'a');
    if (!$fp) {
        return;
    }
    
    if (flock($fp, LOCK_EX)) {
        fputcsv($fp, [
            time(),
            $type,
            $petitionId,
            $email,
            $success ? 'success' : 'failed',
            $error
        ], ',', '"', '');
        flock($fp, LOCK_UN);
    }
    
    fclose($fp);
}

/**
 * Format timestamp for display
 */
function formatDate(int $timestamp): string {
    return date('Y-m-d H:i', $timestamp);
}

/**
 * Print colored output
 */
function printColor(string $text, string $color): void {
    $colors = [
        'red' => "\033[31m",
        'green' => "\033[32m",
        'yellow' => "\033[33m",
        'blue' => "\033[34m",
        'cyan' => "\033[36m",
        'reset' => "\033[0m"
    ];
    
    echo ($colors[$color] ?? '') . $text . $colors['reset'];
}

/**
 * Read user input
 */
function prompt(string $message): string {
    echo $message;
    return trim(fgets(STDIN));
}

/**
 * Confirm action
 */
function confirm(string $message): bool {
    $response = strtolower(prompt($message . ' [j/n]: '));
    return $response === 'j' || $response === 'ja' || $response === 'y' || $response === 'yes';
}

/**
 * Display main menu
 */
function showMenu(): void {
    echo "\n";
    printColor("=== Underskriftskampanje E-post CLI ===\n", 'blue');
    echo "\n";
    echo "Velg handling:\n";
    echo "  1) Vis mislykkede e-poster fra SMTP-logg\n";
    echo "  2) Vis ubekreftede signaturer\n";
    echo "  3) Send e-post pa nytt til mislykkede\n";
    echo "  4) Send bekreftelse pa nytt til ubekreftede\n";
    echo "  5) Send bekreftelse til spesifikke e-postadresser\n";
    echo "  6) Marker oppforinger som ignorert\n";
    echo "  7) Manuell bekreftelse av signaturer\n";
    echo "  8) Slett signaturer\n";
    echo "  9) Avslutt\n";
    echo "\n";
}

/**
 * List failed emails
 */
function listFailedEmails(): void {
    $failed = getFailedEmails();
    
    if (empty($failed)) {
        printColor("\nIngen mislykkede e-poster i loggen.\n", 'green');
        return;
    }
    
    echo "\n";
    printColor("Mislykkede e-poster (" . count($failed) . " stk):\n", 'yellow');
    echo str_repeat('-', 80) . "\n";
    
    foreach ($failed as $i => $entry) {
        $num = $i + 1;
        $date = formatDate($entry['timestamp']);
        $type = $entry['type'] === 'confirmation' ? 'Bekreftelse' : 'Takk';
        echo sprintf(
            "%3d. %-35s %-12s %-20s\n     Feil: %s\n",
            $num,
            $entry['email'],
            $type,
            $date,
            $entry['error'] ?: '(ukjent)'
        );
    }
    echo str_repeat('-', 80) . "\n";
}

/**
 * List unconfirmed signatures
 */
function listUnconfirmedSignatures(): void {
    $unconfirmed = getAllUnconfirmedSignatures();
    
    if (empty($unconfirmed)) {
        printColor("\nIngen ubekreftede signaturer.\n", 'green');
        return;
    }
    
    // Filter out expired tokens (older than 30 days)
    $currentTime = time();
    $valid = array_filter($unconfirmed, function($sig) use ($currentTime) {
        return ($currentTime - $sig['token_created']) <= 2592000; // 30 days
    });
    
    $expired = count($unconfirmed) - count($valid);
    
    echo "\n";
    printColor("Ubekreftede signaturer (" . count($valid) . " gyldige", 'yellow');
    if ($expired > 0) {
        echo ", {$expired} utlopte";
    }
    echo "):\n";
    echo str_repeat('-', 80) . "\n";
    
    foreach ($valid as $i => $sig) {
        $num = $i + 1;
        $date = formatDate($sig['timestamp']);
        $name = $sig['firstname'] . ' ' . $sig['surname'];
        $daysLeft = 30 - floor(($currentTime - $sig['token_created']) / 86400);
        
        echo sprintf(
            "%3d. %-35s %-20s\n     %s (%d dager igjen)\n",
            $num,
            $sig['email'],
            $name,
            $date,
            $daysLeft
        );
    }
    echo str_repeat('-', 80) . "\n";
}

/**
 * Retry sending failed emails
 */
function retryFailedEmails(): void {
    $failed = getFailedEmails();
    
    if (empty($failed)) {
        printColor("\nIngen mislykkede e-poster a sende pa nytt.\n", 'green');
        return;
    }
    
    listFailedEmails();
    
    if (!confirm("\nSende pa nytt til alle " . count($failed) . " adresser?")) {
        echo "Avbrutt.\n";
        return;
    }
    
    $total = count($failed);
    $estimatedMinutes = ceil(($total * EMAIL_DELAY_SECONDS) / 60);
    
    echo "\n";
    printColor("Sender med " . EMAIL_DELAY_SECONDS . " sekunders mellomrom (maks 250/time)...\n", 'blue');
    echo "Estimert tid: ca. {$estimatedMinutes} minutter\n\n";
    
    $success = 0;
    $failures = 0;
    
    foreach ($failed as $i => $entry) {
        $num = $i + 1;
        echo "[{$num}/{$total}] {$entry['email']} ... ";
        
        // For now, we can only retry confirmation emails properly
        // Thank you emails need more context that we don't have
        if ($entry['type'] !== 'confirmation') {
            printColor("HOPPET OVER (type: {$entry['type']})\n", 'yellow');
            continue;
        }
        
        // We need to get the signature data from the petition CSV
        $signatures = getUnconfirmedSignatures($entry['petition_id']);
        $signature = null;
        foreach ($signatures as $sig) {
            if (strtolower($sig['email']) === strtolower($entry['email'])) {
                $signature = $sig;
                break;
            }
        }
        
        if (!$signature) {
            printColor("HOPPET OVER (signatur ikke funnet eller allerede bekreftet)\n", 'yellow');
            continue;
        }
        
        // Check token expiry
        if ((time() - $signature['token_created']) > 2592000) {
            printColor("HOPPET OVER (token utlopt)\n", 'yellow');
            continue;
        }
        
        $email = buildConfirmationEmail($signature, $entry['petition_id']);
        $result = sendEmail(
            $signature['email'],
            $signature['firstname'] . ' ' . $signature['surname'],
            $email['subject'],
            $email['body']
        );
        
        if ($result['success']) {
            printColor("OK\n", 'green');
            logEmailResult('confirmation', $entry['petition_id'], $signature['email'], true);
            $success++;
        } else {
            printColor("FEILET: {$result['error']}\n", 'red');
            logEmailResult('confirmation', $entry['petition_id'], $signature['email'], false, $result['error']);
            $failures++;
        }
        
        // Wait before next email (except for last one)
        if ($i < $total - 1) {
            sleep(EMAIL_DELAY_SECONDS);
        }
    }
    
    echo "\n";
    printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
}

/**
 * Resend confirmation to unconfirmed signatures
 */
function resendToUnconfirmed(): void {
    $unconfirmed = getAllUnconfirmedSignatures();
    
    // Filter out expired tokens
    $currentTime = time();
    $valid = array_filter($unconfirmed, function($sig) use ($currentTime) {
        return ($currentTime - $sig['token_created']) <= 2592000;
    });
    $valid = array_values($valid); // Re-index
    
    if (empty($valid)) {
        printColor("\nIngen gyldige ubekreftede signaturer a sende til.\n", 'green');
        return;
    }
    
    listUnconfirmedSignatures();
    
    if (!confirm("\nSende bekreftelse pa nytt til alle " . count($valid) . " adresser?")) {
        echo "Avbrutt.\n";
        return;
    }
    
    // Ask for custom delay
    $delaySeconds = EMAIL_DELAY_SECONDS;
    $customDelay = prompt("\nForsinkelse mellom e-poster i sekunder [standard: " . EMAIL_DELAY_SECONDS . "]: ");
    if (!empty($customDelay) && is_numeric($customDelay) && (int)$customDelay >= 0) {
        $delaySeconds = (int)$customDelay;
    }
    
    $total = count($valid);
    $estimatedMinutes = $delaySeconds > 0 ? ceil(($total * $delaySeconds) / 60) : 0;
    
    echo "\n";
    printColor("Sender med " . $delaySeconds . " sekunders mellomrom...\n", 'blue');
    if ($estimatedMinutes > 0) {
        echo "Estimert tid: ca. {$estimatedMinutes} minutter\n\n";
    } else {
        echo "\n";
    }
    
    $success = 0;
    $failures = 0;
    
    foreach ($valid as $i => $signature) {
        $num = $i + 1;
        echo "[{$num}/{$total}] {$signature['email']} ... ";
        
        $email = buildConfirmationEmail($signature, $signature['petition_id']);
        $result = sendEmail(
            $signature['email'],
            $signature['firstname'] . ' ' . $signature['surname'],
            $email['subject'],
            $email['body']
        );
        
        if ($result['success']) {
            printColor("OK\n", 'green');
            logEmailResult('confirmation', $signature['petition_id'], $signature['email'], true);
            $success++;
        } else {
            printColor("FEILET: {$result['error']}\n", 'red');
            logEmailResult('confirmation', $signature['petition_id'], $signature['email'], false, $result['error']);
            $failures++;
        }
        
        // Wait before next email (except for last one)
        if ($i < $total - 1 && $delaySeconds > 0) {
            sleep($delaySeconds);
        }
    }
    
    echo "\n";
    printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
}

/**
 * Send confirmation to specific email addresses
 */
function sendToSpecificEmails(): void {
    echo "\n";
    printColor("Send bekreftelse til spesifikke e-postadresser\n", 'cyan');
    echo "\n";
    
    $input = prompt("Skriv inn e-postadresse(r) (kommaseparert): ");
    if (empty(trim($input))) {
        echo "Ingen e-postadresser oppgitt.\n";
        return;
    }
    
    // Parse and clean email addresses
    $emails = array_map('trim', explode(',', $input));
    $emails = array_filter($emails, fn($e) => !empty($e));
    $emails = array_map('strtolower', $emails);
    
    if (empty($emails)) {
        echo "Ingen gyldige e-postadresser oppgitt.\n";
        return;
    }
    
    echo "\n";
    echo "Soker etter " . count($emails) . " e-postadresse(r)...\n\n";
    
    // Find matching unconfirmed signatures
    $found = [];
    $currentTime = time();
    
    foreach (getPetitionFiles() as $petitionId) {
        $signatures = getUnconfirmedSignatures($petitionId, false); // Include ignored
        foreach ($signatures as $sig) {
            if (in_array(strtolower($sig['email']), $emails)) {
                $sig['expired'] = ($currentTime - $sig['token_created']) > 2592000;
                $found[] = $sig;
            }
        }
    }
    
    // Determine which emails were not found
    $foundEmails = array_map(fn($f) => strtolower($f['email']), $found);
    $notFound = [];
    foreach ($emails as $email) {
        if (!in_array($email, $foundEmails)) {
            $notFound[] = $email;
        }
    }
    
    if (!empty($notFound)) {
        printColor("Ikke funnet (eller allerede bekreftet):\n", 'red');
        foreach ($notFound as $email) {
            echo "  - {$email}\n";
        }
        echo "\n";
    }
    
    if (empty($found)) {
        echo "Ingen ubekreftede signaturer a sende til.\n";
        return;
    }
    
    // Show what will be sent
    printColor("Fant folgende ubekreftede signaturer:\n", 'yellow');
    foreach ($found as $entry) {
        $date = formatDate($entry['timestamp']);
        $name = $entry['firstname'] . ' ' . $entry['surname'];
        $status = $entry['expired'] ? ' [UTLOPT]' : '';
        echo "  - {$entry['email']} ({$name}) - {$entry['petition_id']}{$status}\n";
    }
    
    // Filter out expired
    $valid = array_filter($found, fn($f) => !$f['expired']);
    $valid = array_values($valid);
    $expiredCount = count($found) - count($valid);
    
    if ($expiredCount > 0) {
        echo "\n";
        printColor("{$expiredCount} signatur(er) har utlopt token og vil bli hoppet over.\n", 'yellow');
    }
    
    if (empty($valid)) {
        echo "Ingen gyldige signaturer a sende til.\n";
        return;
    }
    
    echo "\n";
    if (!confirm("Sende bekreftelse til " . count($valid) . " adresse(r)?")) {
        echo "Avbrutt.\n";
        return;
    }
    
    // Ask for custom delay
    $delaySeconds = EMAIL_DELAY_SECONDS;
    $customDelay = prompt("\nForsinkelse mellom e-poster i sekunder [standard: " . EMAIL_DELAY_SECONDS . "]: ");
    if (!empty($customDelay) && is_numeric($customDelay) && (int)$customDelay >= 0) {
        $delaySeconds = (int)$customDelay;
    }
    
    $total = count($valid);
    echo "\n";
    printColor("Sender...\n", 'blue');
    echo "\n";
    
    $success = 0;
    $failures = 0;
    
    foreach ($valid as $i => $signature) {
        $num = $i + 1;
        echo "[{$num}/{$total}] {$signature['email']} ... ";
        
        $email = buildConfirmationEmail($signature, $signature['petition_id']);
        $result = sendEmail(
            $signature['email'],
            $signature['firstname'] . ' ' . $signature['surname'],
            $email['subject'],
            $email['body']
        );
        
        if ($result['success']) {
            printColor("OK\n", 'green');
            logEmailResult('confirmation', $signature['petition_id'], $signature['email'], true);
            $success++;
        } else {
            printColor("FEILET: {$result['error']}\n", 'red');
            logEmailResult('confirmation', $signature['petition_id'], $signature['email'], false, $result['error']);
            $failures++;
        }
        
        // Wait before next email (except for last one)
        if ($i < $total - 1 && $delaySeconds > 0) {
            sleep($delaySeconds);
        }
    }
    
    echo "\n";
    printColor("Ferdig: {$success} vellykket, {$failures} mislykket\n", $failures > 0 ? 'yellow' : 'green');
}

/**
 * Atomically modify petition CSV file with a callback function.
 * The callback receives the rows array (without header) by reference and can modify it.
 * Returns the result from the callback, or null on file errors.
 * 
 * @param string $petitionId The petition ID
 * @param callable $callback Function that receives (&$rows, $petitionId) and returns a result
 * @return mixed|null The callback's return value, or null on error
 */
function modifyPetitionFile(string $petitionId, callable $callback): mixed {
    $csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
    if (!file_exists($csvPath)) {
        return null;
    }
    
    // Open with r+ to allow reading and writing, acquire exclusive lock immediately
    // This matches the pattern used in petition-form.php to prevent race conditions
    $fp = fopen($csvPath, 'r+');
    if (!$fp) {
        printColor("  Advarsel: Kunne ikke apne {$petitionId}.csv\n", 'yellow');
        return null;
    }
    
    if (!flock($fp, LOCK_EX)) {
        fclose($fp);
        printColor("  Advarsel: Kunne ikke lase {$petitionId}.csv\n", 'yellow');
        return null;
    }
    
    // Read all rows while holding the lock
    $header = fgetcsv($fp, null, ',', '"', '');
    if ($header === false) {
        flock($fp, LOCK_UN);
        fclose($fp);
        return null;
    }
    
    $rows = [];
    while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
        $rows[] = $row;
    }
    
    // Call the callback to modify rows
    $originalCount = count($rows);
    $result = $callback($rows, $petitionId);
    
    // Write back if rows were modified (count changed or callback signals modification)
    $modified = (count($rows) !== $originalCount);
    if (!$modified && is_array($result) && isset($result['modified'])) {
        $modified = $result['modified'];
    }
    
    if ($modified) {
        rewind($fp);
        ftruncate($fp, 0);
        fputcsv($fp, $header, ',', '"', '');
        foreach ($rows as $row) {
            fputcsv($fp, $row, ',', '"', '');
        }
    }
    
    flock($fp, LOCK_UN);
    fclose($fp);
    
    return $result;
}

/**
 * Manually confirm signatures by email address
 */
function manuallyConfirmSignatures(): void {
    echo "\n";
    printColor("Manuell bekreftelse av signaturer\n", 'cyan');
    echo "\n";
    
    $input = prompt("Skriv inn e-postadresse(r) (kommaseparert): ");
    if (empty(trim($input))) {
        echo "Ingen e-postadresser oppgitt.\n";
        return;
    }
    
    // Parse and clean email addresses
    $emails = array_map('trim', explode(',', $input));
    $emails = array_filter($emails, fn($e) => !empty($e));
    $emails = array_map('strtolower', $emails);
    
    if (empty($emails)) {
        echo "Ingen gyldige e-postadresser oppgitt.\n";
        return;
    }
    
    echo "\n";
    echo "Soker etter " . count($emails) . " e-postadresse(r)...\n\n";
    
    $found = [];
    
    // Search through all petition files
    foreach (getPetitionFiles() as $petitionId) {
        $result = modifyPetitionFile($petitionId, function(&$rows, $petitionId) use ($emails) {
            $found = [];
            $modified = false;
            
            foreach ($rows as &$row) {
                if (!isset($row[1]) || !isset($row[6])) {
                    continue;
                }
                
                $rowEmail = strtolower($row[1]);
                if (in_array($rowEmail, $emails)) {
                    if ($row[6] === 'pending') {
                        $row[6] = 'confirmed';
                        $modified = true;
                        $found[] = [
                            'email' => $row[1],
                            'name' => ($row[2] ?? '') . ' ' . ($row[3] ?? ''),
                            'petition_id' => $petitionId,
                            'was_pending' => true
                        ];
                    } else {
                        $found[] = [
                            'email' => $row[1],
                            'name' => ($row[2] ?? '') . ' ' . ($row[3] ?? ''),
                            'petition_id' => $petitionId,
                            'was_pending' => false,
                            'status' => $row[6]
                        ];
                    }
                }
            }
            unset($row);
            
            return ['found' => $found, 'modified' => $modified];
        });
        
        if ($result && !empty($result['found'])) {
            $found = array_merge($found, $result['found']);
        }
    }
    
    // Determine which emails were not found
    $foundEmails = array_map(fn($f) => strtolower($f['email']), $found);
    $notFound = [];
    foreach ($emails as $email) {
        if (!in_array($email, $foundEmails)) {
            $notFound[] = $email;
        }
    }
    
    // Display results
    if (!empty($found)) {
        printColor("Funnet:\n", 'green');
        foreach ($found as $entry) {
            $status = $entry['was_pending'] 
                ? "BEKREFTET" 
                : "allerede " . $entry['status'];
            echo "  - {$entry['email']} ({$entry['name']}) - {$entry['petition_id']}: ";
            if ($entry['was_pending']) {
                printColor("{$status}\n", 'green');
            } else {
                printColor("{$status}\n", 'yellow');
            }
        }
    }
    
    if (!empty($notFound)) {
        echo "\n";
        printColor("Ikke funnet:\n", 'red');
        foreach ($notFound as $email) {
            echo "  - {$email}\n";
        }
    }
    
    $confirmedCount = count(array_filter($found, fn($f) => $f['was_pending']));
    echo "\n";
    printColor("Ferdig: {$confirmedCount} signatur(er) bekreftet\n", 'green');
}

/**
 * Manually delete signatures by email address
 */
function manuallyDeleteSignatures(): void {
    echo "\n";
    printColor("Slett signaturer\n", 'cyan');
    echo "\n";
    
    $input = prompt("Skriv inn e-postadresse(r) (kommaseparert): ");
    if (empty(trim($input))) {
        echo "Ingen e-postadresser oppgitt.\n";
        return;
    }
    
    // Parse and clean email addresses
    $emails = array_map('trim', explode(',', $input));
    $emails = array_filter($emails, fn($e) => !empty($e));
    $emails = array_map('strtolower', $emails);
    
    if (empty($emails)) {
        echo "Ingen gyldige e-postadresser oppgitt.\n";
        return;
    }
    
    echo "\n";
    echo "Soker etter " . count($emails) . " e-postadresse(r)...\n\n";
    
    $found = [];
    
    // First pass: find all matching signatures (read-only)
    foreach (getPetitionFiles() as $petitionId) {
        $csvPath = PETITIONS_DIR . '/' . $petitionId . '.csv';
        if (!file_exists($csvPath)) {
            continue;
        }
        
        $fp = fopen($csvPath, 'r');
        if (!$fp) {
            continue;
        }
        
        if (!flock($fp, LOCK_SH)) {
            fclose($fp);
            continue;
        }
        
        fgetcsv($fp, null, ',', '"', ''); // Skip header
        while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
            if (!isset($row[1])) {
                continue;
            }
            
            $rowEmail = strtolower($row[1]);
            if (in_array($rowEmail, $emails)) {
                $found[] = [
                    'email' => $row[1],
                    'name' => ($row[2] ?? '') . ' ' . ($row[3] ?? ''),
                    'petition_id' => $petitionId,
                    'status' => $row[6] ?? 'unknown',
                    'timestamp' => (int)($row[0] ?? 0)
                ];
            }
        }
        
        flock($fp, LOCK_UN);
        fclose($fp);
    }
    
    // Determine which emails were not found
    $foundEmails = array_map(fn($f) => strtolower($f['email']), $found);
    $notFound = [];
    foreach ($emails as $email) {
        if (!in_array($email, $foundEmails)) {
            $notFound[] = $email;
        }
    }
    
    if (!empty($notFound)) {
        printColor("Ikke funnet:\n", 'red');
        foreach ($notFound as $email) {
            echo "  - {$email}\n";
        }
        echo "\n";
    }
    
    if (empty($found)) {
        echo "Ingen signaturer a slette.\n";
        return;
    }
    
    // Show what will be deleted
    printColor("Folgende signaturer vil bli slettet:\n", 'yellow');
    foreach ($found as $entry) {
        $date = formatDate($entry['timestamp']);
        echo "  - {$entry['email']} ({$entry['name']}) - {$entry['petition_id']} [{$entry['status']}] {$date}\n";
    }
    
    echo "\n";
    if (!confirm("Er du sikker pa at du vil slette " . count($found) . " signatur(er)?")) {
        echo "Avbrutt.\n";
        return;
    }
    
    // Second pass: delete the signatures
    $deleted = 0;
    $emailsToDelete = array_map(fn($f) => strtolower($f['email']), $found);
    
    foreach (getPetitionFiles() as $petitionId) {
        $result = modifyPetitionFile($petitionId, function(&$rows, $petitionId) use ($emailsToDelete) {
            $deletedCount = 0;
            $rows = array_filter($rows, function($row) use ($emailsToDelete, &$deletedCount) {
                if (isset($row[1]) && in_array(strtolower($row[1]), $emailsToDelete)) {
                    $deletedCount++;
                    return false; // Remove this row
                }
                return true; // Keep this row
            });
            $rows = array_values($rows); // Re-index
            return ['deleted' => $deletedCount];
        });
        
        if ($result && isset($result['deleted'])) {
            $deleted += $result['deleted'];
        }
    }
    
    echo "\n";
    printColor("Ferdig: {$deleted} signatur(er) slettet\n", 'green');
}

/**
 * Interactive ignore marking
 */
function markAsIgnored(): void {
    echo "\n";
    printColor("Marker oppforinger som ignorert\n", 'cyan');
    echo "\n";
    echo "Velg liste:\n";
    echo "  1) Mislykkede e-poster fra SMTP-logg\n";
    echo "  2) Ubekreftede signaturer\n";
    echo "  3) Tilbake\n";
    echo "\n";
    
    $choice = prompt('Valg: ');
    
    if ($choice === '1') {
        markFailedAsIgnored();
    } elseif ($choice === '2') {
        markUnconfirmedAsIgnored();
    }
}

/**
 * Mark failed emails as ignored (one by one)
 */
function markFailedAsIgnored(): void {
    $failed = getFailedEmails();
    
    if (empty($failed)) {
        printColor("\nIngen mislykkede e-poster a markere.\n", 'green');
        return;
    }
    
    echo "\n";
    printColor("Ga gjennom mislykkede e-poster en etter en.\n", 'cyan');
    echo "Trykk 'j' for a ignorere, 'n' for a beholde, 's' for a hoppe over resten.\n\n";
    
    $ignored = 0;
    $kept = 0;
    
    foreach ($failed as $i => $entry) {
        $num = $i + 1;
        $total = count($failed);
        $date = formatDate($entry['timestamp']);
        $type = $entry['type'] === 'confirmation' ? 'Bekreftelse' : 'Takk';
        
        echo str_repeat('-', 60) . "\n";
        echo "[{$num}/{$total}]\n";
        echo "  E-post:    {$entry['email']}\n";
        echo "  Type:      {$type}\n";
        echo "  Kampanje:  {$entry['petition_id']}\n";
        echo "  Dato:      {$date}\n";
        echo "  Feil:      " . ($entry['error'] ?: '(ukjent)') . "\n";
        
        $response = strtolower(prompt("\nIgnorer denne? [j/n/s]: "));
        
        if ($response === 's' || $response === 'stopp' || $response === 'stop') {
            echo "Avslutter gjennomgang.\n";
            break;
        }
        
        if ($response === 'j' || $response === 'ja' || $response === 'y' || $response === 'yes') {
            $reason = prompt("Grunn (valgfritt): ");
            if (addToIgnoreList($entry['type'], $entry['petition_id'], $entry['email'], $reason)) {
                printColor("Markert som ignorert.\n", 'green');
                $ignored++;
            } else {
                printColor("Kunne ikke lagre til ignorliste.\n", 'red');
            }
        } else {
            echo "Beholdt.\n";
            $kept++;
        }
    }
    
    echo "\n";
    printColor("Ferdig: {$ignored} ignorert, {$kept} beholdt\n", 'green');
}

/**
 * Mark unconfirmed signatures as ignored (one by one)
 */
function markUnconfirmedAsIgnored(): void {
    $unconfirmed = getAllUnconfirmedSignatures();
    
    // Include expired for review
    $currentTime = time();
    
    if (empty($unconfirmed)) {
        printColor("\nIngen ubekreftede signaturer a markere.\n", 'green');
        return;
    }
    
    echo "\n";
    printColor("Ga gjennom ubekreftede signaturer en etter en.\n", 'cyan');
    echo "Trykk 'j' for a ignorere, 'n' for a beholde, 's' for a hoppe over resten.\n\n";
    
    $ignored = 0;
    $kept = 0;
    
    foreach ($unconfirmed as $i => $sig) {
        $num = $i + 1;
        $total = count($unconfirmed);
        $date = formatDate($sig['timestamp']);
        $name = $sig['firstname'] . ' ' . $sig['surname'];
        $daysLeft = 30 - floor(($currentTime - $sig['token_created']) / 86400);
        $expired = $daysLeft < 0;
        
        echo str_repeat('-', 60) . "\n";
        echo "[{$num}/{$total}]";
        if ($expired) {
            printColor(" (UTLOPT)", 'red');
        }
        echo "\n";
        echo "  E-post:    {$sig['email']}\n";
        echo "  Navn:      {$name}\n";
        echo "  Kampanje:  {$sig['petition_id']}\n";
        echo "  Dato:      {$date}\n";
        if (!$expired) {
            echo "  Utloper:   om {$daysLeft} dager\n";
        }
        
        $response = strtolower(prompt("\nIgnorer denne? [j/n/s]: "));
        
        if ($response === 's' || $response === 'stopp' || $response === 'stop') {
            echo "Avslutter gjennomgang.\n";
            break;
        }
        
        if ($response === 'j' || $response === 'ja' || $response === 'y' || $response === 'yes') {
            $reason = prompt("Grunn (valgfritt): ");
            if (addToIgnoreList('unconfirmed', $sig['petition_id'], $sig['email'], $reason)) {
                printColor("Markert som ignorert.\n", 'green');
                $ignored++;
            } else {
                printColor("Kunne ikke lagre til ignorliste.\n", 'red');
            }
        } else {
            echo "Beholdt.\n";
            $kept++;
        }
    }
    
    echo "\n";
    printColor("Ferdig: {$ignored} ignorert, {$kept} beholdt\n", 'green');
}

// Main loop
function main(): void {
    // Check requirements
    $config = loadSmtpConfig();
    if (!$config) {
        printColor("Feil: SMTP-konfigurasjon ikke funnet (smtp-config.php)\n", 'red');
        exit(1);
    }
    
    if (!$config['enabled']) {
        printColor("Advarsel: SMTP er deaktivert i konfigurasjonen\n", 'yellow');
    }
    
    while (true) {
        showMenu();
        $choice = prompt('Valg: ');
        
        switch ($choice) {
            case '1':
                listFailedEmails();
                break;
            case '2':
                listUnconfirmedSignatures();
                break;
            case '3':
                retryFailedEmails();
                break;
            case '4':
                resendToUnconfirmed();
                break;
            case '5':
                sendToSpecificEmails();
                break;
            case '6':
                markAsIgnored();
                break;
            case '7':
                manuallyConfirmSignatures();
                break;
            case '8':
                manuallyDeleteSignatures();
                break;
            case '9':
            case 'q':
            case 'quit':
            case 'exit':
                echo "Ha det!\n";
                exit(0);
            default:
                printColor("Ugyldig valg. Velg 1-9.\n", 'red');
        }
    }
}

// Run
main();
