<?php
declare(strict_types=1);

$stage = 'default_deny';
$journalPath = null;
if (!hash_equals('CDX_CAT8F_FIXTURE_20261002_V1',
    (string)getenv('CDX_CAT8F_APPROVAL'))) {
    echo json_encode(['status' => 'default_deny', 'database_connected' => false,
        'cache_accessed' => false, 'business_http_sent' => false,
        'dml_sent' => false]), PHP_EOL;
    exit(78);
}
$redactedError = static function (Throwable $error) use (&$stage, &$journalPath): void {
    echo json_encode(['status' => 'redacted_error', 'stage' => $stage,
        'class' => get_class($error), 'journal_path' => $journalPath,
        'cleanup_required' => $journalPath !== null]), PHP_EOL;
    exit(2);
};
set_exception_handler($redactedError);
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted_warning', 0, $severity);
});
$stage = 'source_review';
require __DIR__ . '/common.php';
cat8f_approved('CDX_CAT8F_FIXTURE_20261002_V1', __FILE__);
$mode = (string)getenv('CDX_CAT8F_MODE');
if (!in_array($mode, ['create', 'renew', 'cleanup'], true)) {
    throw new RuntimeException('Explicit fixture mode required');
}
$target = (string)getenv('CDX_CAT8F_TARGET');
$stage = 'isolated_runtime';
try {
    $ctx = cat8_context($target);
} catch (Throwable $error) {
    $redactedError($error);
}
set_exception_handler($redactedError);
/** @var PDO $db */
$db = $ctx['pdo'];
foreach ([
    'yoshop_store_user' => 'STORE_USER_ID,USER_NAME,PASSWORD,REAL_NAME,IS_SUPER,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME,NODE,CREATE_BY',
    'yoshop_store_user_role' => 'STORE_USER_ID,ROLE_ID',
    'yoshop_store_user_relation' => 'STORE_USER_ID,RELATION_STORE_USER_ID',
    'yoshop_staff_store_user' => 'STORE_USER_ID',
] as $table => $columns) {
    $db->query('SELECT ' . $columns . ' FROM ' . $table . ' WHERE 1=0');
}

if ($mode === 'create') {
    $stage = 'exclusive_marker_preflight';
    if (cat8_count($db, 'SELECT COUNT(*) FROM yoshop_store_user'
        . " WHERE USER_NAME LIKE 'CDXCAT8U%' AND STORE_ID=10001") !== 0) {
        throw new RuntimeException('Earlier Catalog8 synthetic user needs independent cleanup');
    }
    $marker = 'CDXCAT8U' . strtoupper(bin2hex(random_bytes(8)));
    $token = 'CDXCAT8T' . strtoupper(bin2hex(random_bytes(16)));
    $directory = '/tmp/cdx-cat8-' . bin2hex(random_bytes(8));
    $stage = 'journal_before_dml';
    if (!mkdir($directory, 0700)) throw new RuntimeException('Private journal directory failed');
    $path = $directory . '/manifest.json';
    $journalPath = $path;
    $seed = tempnam($directory, 'seed-');
    if (!is_string($seed) || !chmod($seed, 0600) || !rename($seed, $path)) {
        throw new RuntimeException('Private journal seed failed');
    }
    $data = ['_path' => $path, 'fixture_mode' => CAT8F_MODE,
        'target' => $target, 'schema' => $ctx['schema'],
        'store_id' => CAT8F_STORE_ID, 'user_name' => $marker,
        'token' => $token, 'store_user_id' => 0,
        'status' => 'creating', 'phase' => 'before_dml',
        'ttl_seconds' => 300, 'created_at' => time(),
        'hard_stop_at' => time() + 3600, 'expires_at' => 0];
    cat8_save_manifest($data);
    $stage = 'one_row_transaction';
    $db->beginTransaction();
    try {
        $password = password_hash(bin2hex(random_bytes(24)), PASSWORD_BCRYPT);
        if (!is_string($password)) throw new RuntimeException('Password hash failed');
        $stmt = $db->prepare('INSERT INTO yoshop_store_user'
            . ' (USER_NAME,PASSWORD,REAL_NAME,IS_SUPER,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME,NODE,CREATE_BY)'
            . ' VALUES (?,?,?,1,0,10001,?,?,?,?)');
        $stmt->execute([$marker, $password, $marker, time(), time(),
            'CDXCAT8', $marker]);
        if ($stmt->rowCount() !== 1) throw new RuntimeException('Insert count mismatch');
        $row = cat8_one($db, 'SELECT STORE_USER_ID,USER_NAME,REAL_NAME,IS_SUPER,IS_DELETE,STORE_ID'
            . ' FROM yoshop_store_user WHERE USER_NAME=? AND STORE_ID=10001', [$marker]);
        if (!cat8f_owned($row, $data)) throw new RuntimeException('Inserted account mismatch');
        $userId = (int)$row['store_user_id'];
        if ($userId <= 0 || array_sum(cat8f_refs($db, $userId)) !== 0) {
            throw new RuntimeException('Synthetic account has unexpected references');
        }
        $db->commit();
    } catch (Throwable $error) {
        if ($db->inTransaction()) $db->rollBack();
        throw $error;
    }
    $data['store_user_id'] = $userId;
    $data['phase'] = 'db_committed';
    cat8_save_manifest($data);
    $stage = 'isolated_300s_file_cache';
    $session = cat8f_session($data, $userId);
    if (!\think\facade\Cache::set($token, $session, 300)
        || \think\facade\Cache::get($token) !== $session) {
        throw new RuntimeException('Short session not proven');
    }
    $ttl = cat8_cache_ttl($token);
    $data['expires_at'] = $ttl['expires_at'];
    $data['status'] = 'ready';
    $data['phase'] = 'session_ready';
    cat8_save_manifest($data);
    echo json_encode(['status' => 'fixture_ready', 'target' => $target,
        'journal_path' => $path, 'ttl_seconds' => 300,
        'token_output' => false]), PHP_EOL;
    exit(0);
}

$stage = 'private_journal_and_exact_account';
$data = cat8f_manifest();
$journalPath = $data['_path'];
if ($data['target'] !== $target || $data['schema'] !== $ctx['schema']) {
    throw new RuntimeException('Fixture target or schema drift');
}
$rows = cat8f_operator($db, $data['user_name']);
if (count($rows) > 1 || (count($rows) === 1 && !cat8f_owned($rows[0], $data))) {
    throw new RuntimeException('Synthetic account ownership ambiguous');
}
$userId = count($rows) === 1 ? (int)$rows[0]['store_user_id']
    : (int)($data['store_user_id'] ?? 0);
if ($mode === 'renew') {
    $stage = 'renew_same_token_300s';
    if (($data['status'] ?? null) !== 'ready' || count($rows) !== 1
        || time() + 300 >= (int)$data['hard_stop_at']
        || array_sum(cat8f_refs($db, $userId)) !== 0) {
        throw new RuntimeException('Synthetic session not renewable');
    }
    $cache = cat8f_cache_state($data);
    if ($cache['live'] && ($cache['session'] ?? null) !== cat8f_session($data, $userId)) {
        throw new RuntimeException('Existing cache key ownership mismatch');
    }
    $data['status'] = 'renewing';
    $data['phase'] = 'renew_before_cache_set';
    cat8_save_manifest($data);
    if (!\think\facade\Cache::set($data['token'], cat8f_session($data, $userId), 300)) {
        throw new RuntimeException('Renew cache set failed');
    }
    $ttl = cat8_cache_ttl($data['token']);
    $data['expires_at'] = $ttl['expires_at'];
    $data['status'] = 'ready';
    $data['phase'] = 'session_renewed';
    cat8_save_manifest($data);
    echo json_encode(['status' => 'session_renewed', 'target' => $target,
        'journal_path' => $data['_path'], 'ttl_seconds' => 300,
        'token_output' => false]), PHP_EOL;
    exit(0);
}

$stage = 'revoke_exact_token_before_account_delete';
$cache = cat8f_cache_state($data);
if ($cache['exists'] && !isset($cache['expires_at'])) {
    throw new RuntimeException('Unrecognized cache file; do not delete');
}
if (($cache['live'] ?? false)
    && ($cache['session'] ?? null) !== cat8f_session($data, $userId)) {
    throw new RuntimeException('Cache key belongs to unexpected identity');
}
if ($cache['exists']) {
    if (!\think\facade\Cache::delete($data['token'])
        || is_file(\think\facade\Cache::store('file')->getCacheKey($data['token']))) {
        throw new RuntimeException('Exact token revocation failed');
    }
}
$data['status'] = 'cleaning';
$data['phase'] = 'token_absent_before_db_cleanup';
cat8_save_manifest($data);
$stage = 'delete_one_exact_account';
if (count($rows) === 1) {
    if (array_sum(cat8f_refs($db, $userId)) !== 0) {
        throw new RuntimeException('Synthetic account references need manual review');
    }
    $db->beginTransaction();
    try {
        $stmt = $db->prepare('DELETE FROM yoshop_store_user'
            . ' WHERE STORE_USER_ID=? AND USER_NAME=? AND REAL_NAME=?'
            . ' AND STORE_ID=10001 AND IS_SUPER=1 AND IS_DELETE=0');
        $stmt->execute([$userId, $data['user_name'], $data['user_name']]);
        if ($stmt->rowCount() !== 1) throw new RuntimeException('Delete count mismatch');
        $db->commit();
    } catch (Throwable $error) {
        if ($db->inTransaction()) $db->rollBack();
        throw $error;
    }
}
if (count(cat8f_operator($db, $data['user_name'])) !== 0
    || ($userId > 0 && array_sum(cat8f_refs($db, $userId)) !== 0)) {
    throw new RuntimeException('Account deletion readback not zero');
}
$data['status'] = 'cleaned';
$data['phase'] = 'independent_zero_required';
cat8_save_manifest($data);
echo json_encode(['status' => 'cleanup_committed', 'target' => $target,
    'journal_path' => $data['_path'], 'independent_zero_required' => true]), PHP_EOL;
