<?php

/**
 * ShareLink Unit Test
 *
 * Tests pure-logic methods that require no HTTP context:
 *   - generateSlug()   — format, uniqueness, entropy
 *   - validateLink()   — all branches: not_found, archived, disabled, expired, needs_password, valid
 *   - verifyPassword() — correct / wrong / no-password links
 */

putenv('DB_ENV=test');

if (!defined('APP_ROOT')) {
    define('APP_ROOT', dirname(__DIR__, 2));
}
if (!defined('STORAGE_ROOT')) {
    define('STORAGE_ROOT', APP_ROOT . '/storage');
}

require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ShareLink.php';

// ── Helpers ───────────────────────────────────────────────────────────────────

function slAssert(bool $cond, string $label): void
{
    if ($cond) {
        echo "  ✓ $label\n";
    } else {
        throw new RuntimeException("FAIL: $label");
    }
}

function slAssertEq(mixed $expected, mixed $actual, string $label): void
{
    if ($expected === $actual) {
        echo "  ✓ $label\n";
    } else {
        $e = var_export($expected, true);
        $a = var_export($actual, true);
        throw new RuntimeException("FAIL $label\n    expected: $e\n    actual:   $a");
    }
}

// ── Setup ─────────────────────────────────────────────────────────────────────

echo "ShareLink Unit Test\n";
echo "===================\n\n";

$db    = Database::getInstance();
$model = new ShareLink($db);

// We need a dummy admin user id — just use 1 (or any int; share_links.created_by is not FK-checked)
$adminId   = 1;
$createdIds = [];

try {

    // =========================================================================
    // TEST 1: generateSlug — format YYYYMMDD-XXXXXXXX
    // =========================================================================
    echo "Test 1: generateSlug — format\n";
    $slug = ShareLink::generateSlug();
    slAssert(
        (bool) preg_match('/^\d{8}-[A-Z2-7]{8}$/', $slug),
        "slug matches YYYYMMDD-[BASE32]{8}: $slug"
    );
    $year  = (int) substr($slug, 0, 4);
    $month = (int) substr($slug, 4, 2);
    $day   = (int) substr($slug, 6, 2);
    slAssert($year >= 2020 && $year <= 2100, 'year in plausible range');
    slAssert($month >= 1 && $month <= 12,    'month in range');
    slAssert($day   >= 1 && $day   <= 31,    'day in range');
    echo "\n";

    // =========================================================================
    // TEST 2: generateSlug — two calls produce different slugs
    // =========================================================================
    echo "Test 2: generateSlug — uniqueness\n";
    $slugs = [];
    for ($i = 0; $i < 20; $i++) {
        $slugs[] = ShareLink::generateSlug();
    }
    slAssertEq(count($slugs), count(array_unique($slugs)), '20 consecutive slugs are all unique');
    echo "\n";

    // =========================================================================
    // TEST 3: validateLink — not_found on missing slug
    // =========================================================================
    echo "Test 3: validateLink — not_found on missing slug\n";
    $result = $model->validateLink('NONEXISTENT-SLUG');
    slAssertEq(false,        $result['valid'],  'valid=false');
    slAssertEq('not_found',  $result['reason'], 'reason=not_found');

    $result = $model->validateLink(null);
    slAssertEq(false,       $result['valid'],  'null slug: valid=false');
    slAssertEq('not_found', $result['reason'], 'null slug: reason=not_found');

    $result = $model->validateLink('');
    slAssertEq(false,       $result['valid'],  'empty slug: valid=false');
    slAssertEq('not_found', $result['reason'], 'empty slug: reason=not_found');
    echo "\n";

    // =========================================================================
    // TEST 4: validateLink — valid active link with no password
    // =========================================================================
    echo "Test 4: validateLink — valid active link\n";
    $link = $model->create($adminId, null, null);
    $createdIds[] = $link['id'];

    $result = $model->validateLink($link['slug']);
    slAssertEq(true,  $result['valid'], 'valid=true');
    slAssert(isset($result['link']),    'link row returned');
    echo "\n";

    // =========================================================================
    // TEST 5: validateLink — disabled link
    // =========================================================================
    echo "Test 5: validateLink — disabled link\n";
    $model->toggleActive($link['id']); // deactivate
    $result = $model->validateLink($link['slug']);
    slAssertEq(false,      $result['valid'],  'valid=false after disable');
    slAssertEq('disabled', $result['reason'], 'reason=disabled');
    $model->toggleActive($link['id']); // restore
    echo "\n";

    // =========================================================================
    // TEST 6: validateLink — archived link
    // =========================================================================
    echo "Test 6: validateLink — archived link\n";
    $archivedLink = $model->create($adminId, null, null);
    $createdIds[] = $archivedLink['id'];
    $model->archive($archivedLink['id']);
    $result = $model->validateLink($archivedLink['slug']);
    slAssertEq(false,      $result['valid'],  'valid=false for archived');
    slAssertEq('archived', $result['reason'], 'reason=archived');
    echo "\n";

    // =========================================================================
    // TEST 7: validateLink — expired link
    // =========================================================================
    echo "Test 7: validateLink — expired link\n";
    $pastDate   = date('Y-m-d H:i:s', strtotime('-1 day'));
    $expiredLink = $model->create($adminId, null, $pastDate);
    $createdIds[] = $expiredLink['id'];
    $result = $model->validateLink($expiredLink['slug']);
    slAssertEq(false,     $result['valid'],  'valid=false for expired');
    slAssertEq('expired', $result['reason'], 'reason=expired');
    echo "\n";

    // =========================================================================
    // TEST 8: validateLink — not expired (future date)
    // =========================================================================
    echo "Test 8: validateLink — future expiry is still valid\n";
    $futureDate  = date('Y-m-d H:i:s', strtotime('+30 days'));
    $futureLink  = $model->create($adminId, null, $futureDate);
    $createdIds[] = $futureLink['id'];
    $result = $model->validateLink($futureLink['slug']);
    slAssertEq(true, $result['valid'], 'valid=true for future expiry');
    echo "\n";

    // =========================================================================
    // TEST 9: validateLink — needs_password when password is set
    // =========================================================================
    echo "Test 9: validateLink — needs_password\n";
    $pwLink = $model->create($adminId, 'secret123', null);
    $createdIds[] = $pwLink['id'];
    $result = $model->validateLink($pwLink['slug']);
    slAssertEq(false,            $result['valid'],  'valid=false (needs password)');
    slAssertEq('needs_password', $result['reason'], 'reason=needs_password');
    slAssert(isset($result['link']), 'link row returned even when password needed');
    echo "\n";

    // =========================================================================
    // TEST 10: verifyPassword — correct password
    // =========================================================================
    echo "Test 10: verifyPassword — correct password\n";
    $pwLinkRow = $model->findBySlug($pwLink['slug']);
    slAssertEq(true,  $model->verifyPassword($pwLinkRow, 'secret123'), 'correct password accepted');
    slAssertEq(false, $model->verifyPassword($pwLinkRow, 'wrongpass'), 'wrong password rejected');
    slAssertEq(false, $model->verifyPassword($pwLinkRow, ''),          'empty password rejected');
    echo "\n";

    // =========================================================================
    // TEST 11: verifyPassword — link with no password always passes
    // =========================================================================
    echo "Test 11: verifyPassword — no password set always returns true\n";
    $noPwRow = $model->findBySlug($link['slug']);
    slAssertEq(true, $model->verifyPassword($noPwRow, ''),         'no-pw link: empty string passes');
    slAssertEq(true, $model->verifyPassword($noPwRow, 'anything'), 'no-pw link: any string passes');
    echo "\n";

    // =========================================================================
    // TEST 12: incrementUsage — counter goes up
    // =========================================================================
    echo "Test 12: incrementUsage — counter increments\n";
    $fresh = $model->findById($link['id']);
    $before = (int)$fresh['usage_count'];
    $model->incrementUsage($link['id']);
    $model->incrementUsage($link['id']);
    $after = (int)($model->findById($link['id'])['usage_count'] ?? 0);
    slAssertEq($before + 2, $after, 'usage_count incremented by 2');
    echo "\n";

    // =========================================================================
    // TEST 13: objet_restriction is stored and returned
    // =========================================================================
    echo "Test 13: objet_restriction stored correctly\n";
    $restrictedLink = $model->create($adminId, null, null, 'tfe');
    $createdIds[] = $restrictedLink['id'];
    slAssertEq('tfe', $restrictedLink['objet_restriction'], 'objet_restriction=tfe stored');

    $anyLink = $model->create($adminId, null, null, 'invalid_value');
    $createdIds[] = $anyLink['id'];
    slAssertEq('tfe', $anyLink['objet_restriction'], 'invalid objet_restriction defaults to tfe');
    echo "\n";

    echo "✅ All ShareLink tests passed!\n";
    $result = true;

} catch (Exception $e) {
    echo '❌ FAIL: ' . $e->getMessage() . "\n";
    $result = false;
} finally {
    $pdo = $db->getConnection();
    foreach ($createdIds as $id) {
        try {
            $pdo->prepare('DELETE FROM share_links WHERE id = ?')->execute([$id]);
        } catch (Exception $e) { /* ignore */ }
    }
}

return $result ?? false;
