<?php
// scripts/test-cpmove-parser.php — standalone smoke tests for cPanel import helpers.
// Run with: php scripts/test-cpmove-parser.php
// Exits 0 on success, 1 on failure.

require_once __DIR__ . '/../web-files/configs/config.php';
require_once __DIR__ . '/../web-files/libs/CpanelImportSummary.php';

$failures = 0;

function assertEquals($expected, $actual, string $label): void {
    global $failures;
    if ($expected !== $actual) {
        echo "  FAIL  $label\n    expected: " . var_export($expected, true) . "\n    actual:   " . var_export($actual, true) . "\n";
        $failures++;
    } else {
        echo "  PASS  $label\n";
    }
}

function assertTrue(bool $cond, string $label): void {
    global $failures;
    if (!$cond) { echo "  FAIL  $label\n"; $failures++; }
    else        { echo "  PASS  $label\n"; }
}

echo "== Encryption roundtrip ==\n";
$importId = 'import_test_' . bin2hex(random_bytes(4));
$plaintext = 'h7@s2P!w-q8Zk_3MnXcv';
$ct = CpanelImportSummary::encryptTempPassword($importId, $plaintext);
assertEquals($plaintext, CpanelImportSummary::decryptTempPassword($importId, $ct), 'roundtrip recovers plaintext');
assertTrue($ct !== $plaintext, 'ciphertext differs from plaintext');
assertTrue(strlen(base64_decode($ct, true)) >= 12 + 16 + strlen($plaintext), 'ciphertext blob has IV+tag overhead');

try {
    CpanelImportSummary::decryptTempPassword('import_other_id', $ct);
    echo "  FAIL  decrypt with wrong importId should throw\n"; $failures++;
} catch (RuntimeException $e) {
    echo "  PASS  decrypt with wrong importId throws (good — per-import key isolation)\n";
}

echo "\n== Parsers (storytel fixture) ==\n";
$fixture = '/workspace/cpmove-storytel.tar.gz';
if (!is_file($fixture)) {
    echo "  SKIP  no fixture at $fixture\n";
} else {
    $tmp = sys_get_temp_dir() . '/cpmove-parser-test-' . bin2hex(random_bytes(3));
    mkdir($tmp);
    foreach ([
        'cpmove-storytel/homedir/etc/storytellersbrewery.com/passwd',
        'cpmove-storytel/homedir/etc/storytellersbrewery.com/quota',
        'cpmove-storytel/va/storytellersbrewery.com',
    ] as $member) {
        $cmd = 'tar -xzf ' . escapeshellarg($fixture) . ' -C ' . escapeshellarg($tmp) . ' ' . escapeshellarg($member);
        passthru($cmd);
    }
    $extractPath = $tmp . '/cpmove-storytel';

    require_once __DIR__ . '/../web-files/libs/CpanelBackupImporter.php';
    $rc = new ReflectionClass('CpanelBackupImporter');
    $importer = $rc->newInstanceWithoutConstructor();
    $rp = $rc->getProperty('extractPath'); $rp->setAccessible(true);
    $rp->setValue($importer, $extractPath);
    $rb = $rc->getProperty('backupRoot'); $rb->setAccessible(true);
    $rb->setValue($importer, $extractPath);

    $mAccounts = $rc->getMethod('parseEmailAccounts'); $mAccounts->setAccessible(true);
    $accounts = $mAccounts->invoke($importer, 'storytellersbrewery.com');
    assertEquals(3, count($accounts), 'parseEmailAccounts: 3 mailboxes');
    $emails = array_column($accounts, 'email'); sort($emails);
    assertEquals(
        ['info@storytellersbrewery.com', 'matthew.cantrell@storytellersbrewery.com', 'randy.cantrell@storytellersbrewery.com'],
        $emails, 'parseEmailAccounts: expected emails'
    );
    $info = array_values(array_filter($accounts, fn($a) => $a['local'] === 'info'))[0];
    assertEquals(1024, $info['quota_mb'], 'parseEmailAccounts: info quota = 1024 MB');

    $mFwd = $rc->getMethod('parseEmailForwarders'); $mFwd->setAccessible(true);
    $fwds = $mFwd->invoke($importer, 'storytellersbrewery.com', $accounts);
    $byType = [];
    foreach ($fwds as $f) { $byType[$f['type']] = ($byType[$f['type']] ?? 0) + 1; }
    assertEquals(6, $byType['forward']  ?? 0, 'parseEmailForwarders: 6 plain forwarders');
    assertEquals(2, $byType['reject']   ?? 0, 'parseEmailForwarders: 2 :fail: rejects');
    assertEquals(1, $byType['catchall'] ?? 0, 'parseEmailForwarders: 1 catchall');
    $catchall = array_values(array_filter($fwds, fn($f) => $f['type'] === 'catchall'))[0];
    assertEquals('*@storytellersbrewery.com', $catchall['source'], 'catchall source rewritten');
    assertEquals(['storytel@storytellersbrewery.com'], $catchall['targets'], 'catchall bare target promoted');

    passthru('rm -rf ' . escapeshellarg($tmp));
}

echo "\n== DNS zone parser (storytel fixture) ==\n";
$fixture = '/workspace/cpmove-storytel.tar.gz';
if (!is_file($fixture)) {
    echo "  SKIP  no fixture at $fixture\n";
} else {
    $tmp2 = sys_get_temp_dir() . '/cpmove-zone-test-' . bin2hex(random_bytes(3));
    mkdir($tmp2);
    passthru('tar -xzf ' . escapeshellarg($fixture) . ' -C ' . escapeshellarg($tmp2)
             . ' cpmove-storytel/dnszones/storytellersbrewery.com.db');
    $extractPath2 = $tmp2 . '/cpmove-storytel';

    $rc = new ReflectionClass('CpanelBackupImporter');
    $importer = $rc->newInstanceWithoutConstructor();
    $rp = $rc->getProperty('extractPath'); $rp->setAccessible(true);
    $rp->setValue($importer, $extractPath2);
    $rb = $rc->getProperty('backupRoot'); $rb->setAccessible(true);
    $rb->setValue($importer, $extractPath2);

    $parseZone = $rc->getMethod('parseCpanelZone'); $parseZone->setAccessible(true);
    $records = $parseZone->invoke($importer, 'storytellersbrewery.com');

    assertTrue(count($records) > 0, 'parseCpanelZone returns records');

    $byType = [];
    foreach ($records as $r) { $byType[$r['type']] = ($byType[$r['type']] ?? 0) + 1; }
    assertTrue(($byType['A'] ?? 0) >= 5, 'parsed at least 5 A records');
    assertTrue(($byType['MX'] ?? 0) >= 1, 'parsed at least 1 MX record');
    assertTrue(($byType['CNAME'] ?? 0) >= 2, 'parsed at least 2 CNAME records (mail, www)');
    assertTrue(($byType['TXT'] ?? 0) >= 1, 'parsed at least 1 TXT record (DKIM)');

    // MX should preserve target verbatim.
    $mx = array_values(array_filter($records, fn($r) => $r['type'] === 'MX'))[0];
    assertEquals('storytellersbrewery-com.mail.protection.outlook.com.', $mx['content'], 'MX target preserved');
    assertEquals(0, $mx['priority'], 'MX priority preserved');

    // cPanel-internal filter should match cpanel/whm/webdisk/webmail/cpcalendars/cpcontacts.
    $isInternal = $rc->getMethod('isCpanelInternalRecord'); $isInternal->setAccessible(true);
    assertTrue($isInternal->invoke($importer, 'cpanel.storytellersbrewery.com', 'storytellersbrewery.com'), 'cpanel filtered');
    assertTrue($isInternal->invoke($importer, 'webmail.files.storytellersbrewery.com', 'storytellersbrewery.com'), 'webmail.files filtered');
    assertTrue(!$isInternal->invoke($importer, 'mail.storytellersbrewery.com', 'storytellersbrewery.com'), 'mail NOT filtered');
    assertTrue(!$isInternal->invoke($importer, 'autodiscover.storytellersbrewery.com', 'storytellersbrewery.com'), 'autodiscover NOT filtered');
    assertTrue(!$isInternal->invoke($importer, 'storytellersbrewery.com', 'storytellersbrewery.com'), 'apex NOT filtered');

    passthru('rm -rf ' . escapeshellarg($tmp2));
}

echo "\n== userdata IP reader ==\n";
$fixture = '/workspace/cpmove-storytel.tar.gz';
if (!is_file($fixture)) {
    echo "  SKIP  no fixture\n";
} else {
    $tmpUd = sys_get_temp_dir() . '/cpmove-userdata-test-' . bin2hex(random_bytes(3));
    mkdir($tmpUd);
    passthru('tar -xzf ' . escapeshellarg($fixture) . ' -C ' . escapeshellarg($tmpUd) .
             ' cpmove-storytel/userdata/storytellersbrewery.com');
    $extractPath3 = $tmpUd . '/cpmove-storytel';

    $rc = new ReflectionClass('CpanelBackupImporter');
    $importer = $rc->newInstanceWithoutConstructor();
    $rp = $rc->getProperty('extractPath'); $rp->setAccessible(true);
    $rp->setValue($importer, $extractPath3);
    $rb = $rc->getProperty('backupRoot'); $rb->setAccessible(true);
    $rb->setValue($importer, $extractPath3);

    $mUd = $rc->getMethod('readCpanelUserdataIp'); $mUd->setAccessible(true);
    $ips = $mUd->invoke($importer, 'storytellersbrewery.com');
    assertEquals('204.13.239.181', $ips['ipv4'], 'readCpanelUserdataIp: ipv4 = 204.13.239.181');
    // ipv6 is `~` in the storytel fixture → should resolve to null
    assertEquals(null, $ips['ipv6'], 'readCpanelUserdataIp: ipv6 absent → null');

    passthru('rm -rf ' . escapeshellarg($tmpUd));
}

echo "\n== Password generator ==\n";
$rc = new ReflectionClass('CpanelBackupImporter');
$importer = $rc->newInstanceWithoutConstructor();
$gen = $rc->getMethod('generateMailboxPassword'); $gen->setAccessible(true);
$pw = $gen->invoke($importer);
assertEquals(20, strlen($pw), 'generated password is 20 chars');
assertTrue(preg_match('/^[A-Za-z0-9!@#%^*+_-]+$/', $pw) === 1, 'password uses only safe alphabet');
assertTrue($pw !== $gen->invoke($importer), 'two consecutive generations differ');

echo "\n== Site helpers ==\n";
$rc = new ReflectionClass('CpanelBackupImporter');
$importer = $rc->newInstanceWithoutConstructor();

$mMap = $rc->getMethod('mapDocumentRootToPath'); $mMap->setAccessible(true);
assertEquals('/public_html', $mMap->invoke($importer, '/home/storytel/public_html'),
    'mapDocumentRootToPath: apex /home/<u>/public_html');
assertEquals('/Files.storytellersbrewery.com', $mMap->invoke($importer, '/home/storytel/Files.storytellersbrewery.com'),
    'mapDocumentRootToPath: subdomain with capitalized non-public_html docroot');
assertEquals('/public_html/sub', $mMap->invoke($importer, '/home/storytel/public_html/sub'),
    'mapDocumentRootToPath: nested under public_html');
assertEquals('/public_html', $mMap->invoke($importer, '/home2/another/public_html'),
    'mapDocumentRootToPath: /home2/<u>/public_html (multi-volume hosts)');
assertEquals(null, $mMap->invoke($importer, '/var/www/something'),
    'mapDocumentRootToPath: rejects non-/home paths');
assertEquals('/public_html', $mMap->invoke($importer, '/public_html'),
    'mapDocumentRootToPath: tolerates legacy already-relative form');

echo "\n== Summary write/read roundtrip ==\n";
$realDir = '/var/lib/whp/cpanel-imports';
if (!is_dir($realDir)) { @mkdir($realDir, 0700, true); }
if (!is_writable($realDir)) {
    echo "  SKIP  $realDir not writable; skipping summary roundtrip\n";
} else {
    $importId = 'import_test_' . bin2hex(random_bytes(4));
    $sample = [
        'import_id' => $importId,
        'mailboxes' => [
            ['email' => 'a@x.com', 'temp_password_encrypted' => CpanelImportSummary::encryptTempPassword($importId, 'pw1')],
        ],
        'dns' => ['original_mx' => [['host' => 'foo.', 'priority' => 0]]],
    ];
    CpanelImportSummary::writeSummary($importId, $sample);
    $back = CpanelImportSummary::readSummary($importId);
    assertEquals('a@x.com', $back['mailboxes'][0]['email'], 'roundtrip read sees mailbox email');
    CpanelImportSummary::clearCredentials($importId, 'tester');
    $back = CpanelImportSummary::readSummary($importId);
    assertEquals(null, $back['mailboxes'][0]['temp_password_encrypted'], 'clearCredentials zeroes the encrypted password');
    assertEquals('tester', $back['credentials_cleared_by'], 'clearCredentials records actor');
    CpanelImportSummary::markMxAction($importId, 'dismissed', 'tester');
    $back = CpanelImportSummary::readSummary($importId);
    assertEquals('dismissed', $back['dns']['mx_repoint_action'], 'markMxAction persists action');
    @unlink(CpanelImportSummary::summaryPath($importId));
}

if ($failures > 0) {
    echo "\n$failures failure(s)\n";
    exit(1);
}
echo "\nAll tests passed.\n";
exit(0);
