<?php
/**
 * 补发历史已支付订单的票（修复 goods_id 从 order_detail 而非 order 主表取值）
 *
 * 问题根因：onOrderPaid 从 vrt_order 表取 goods_id（字段不存在），实际在 vrt_order_detail
 * 修复后：onOrderPaid 已改为从 order_detail 取 goods_id
 * 此脚本用于补发已支付但未生票的历史订单
 *
 * 使用方式（直接在宿主机执行）：
 *   php fix_backlog_tickets.php
 *
 * 或在 Docker PHP 容器内执行：
 *   docker exec shopxo-php php /var/www/html/fix_backlog_tickets.php
 */

echo "========================================\n";
echo "VR票务 - 历史订单补票脚本\n";
echo "========================================\n\n";

// ============================================================
// 连接数据库
// ============================================================
$MYSQL_HOST = '172.23.0.2';
$MYSQL_PORT = 3306;
$MYSQL_USER = 'root';
$MYSQL_PASS = 'shopxo_root_2024';
$MYSQL_DB   = 'vrticket';

// VR_TICKET_SECRET（与 BaseService 保持一致，使用默认值）
$VR_SECRET = '8935b3a3-a7b4-4e3d-8c1f-9b7e2a6f5d4c';

try {
    $pdo = new PDO(
        "mysql:host={$MYSQL_HOST};port={$MYSQL_PORT};dbname={$MYSQL_DB};charset=utf8mb4",
        $MYSQL_USER,
        $MYSQL_PASS,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
} catch (PDOException $e) {
    die("数据库连接失败: " . $e->getMessage() . "\n");
}

echo "[配置] 数据库: {$MYSQL_DB}\n\n";

// ============================================================
// 工具函数
// ============================================================

function vr_now(): int {
    return time();
}

function vr_generate_uuid(): string {
    $data = random_bytes(16);
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

function vr_get_goods_key(int $goods_id, string $secret): string {
    return substr(hash_hmac('sha256', (string)$goods_id, $secret), 0, 16);
}

function vr_feistel_round(int $R, int $round, string $key): int {
    $hmac = hash_hmac('sha256', $R . '.' . $round, $key, true);
    $val = (ord($hmac[0]) << 16) | (ord($hmac[1]) << 8) | ord($hmac[2]);
    return $val & 0x7FFFF;
}

function vr_feistel_encode(int $packed, string $key): string {
    $L = ($packed >> 19) & 0x1FFFFF;
    $R = $packed & 0x7FFFF;
    for ($i = 0; $i < 8; $i++) {
        $round_key = hash_hmac('sha256', pack('V', $i), $key, true);
        $F = (ord($round_key[0]) << 16) | (ord($round_key[1]) << 8) | ord($round_key[2]);
        $L_new = $R;
        $R_new = ($L ^ $F) & 0x7FFFF;
        $L = $L_new;
        $R = $R_new;
    }
    $result = (($L & 0x1FFFFF) << 19) | ($R & 0x7FFFF);
    return base_convert($result, 10, 36);
}

function vr_short_code_encode(int $goods_id, int $ticket_id, string $secret): string {
    if ($goods_id > 0xFFFFFF) {
        throw new Exception("goods_id 超出范围: {$goods_id}");
    }
    if ($ticket_id <= 0) {
        throw new Exception("ticket_id 必须为正整数: {$ticket_id}");
    }
    $goods_part = str_pad(base_convert($goods_id, 10, 36), 4, '0', STR_PAD_LEFT);
    $ticket_int = intval(base_convert($ticket_id, 10, 36));
    $key = vr_get_goods_key($goods_id, $secret);
    $obfuscated = vr_feistel_encode($ticket_int, $key);
    return strtolower($goods_part . $obfuscated);
}

function vr_sign_qr_payload(int $id, int $goods_id, int $now, string $secret): string {
    $sign_str = "{$id}.{$goods_id}.{$now}." . ($now + 1800);
    $sig = substr(hash_hmac('sha256', $sign_str, $secret), 0, 8);
    $payload = json_encode([
        'id'  => $id,
        'g'   => $goods_id,
        'iat' => $now,
        'exp' => $now + 1800,
        'sig' => $sig,
    ], JSON_UNESCAPED_UNICODE);
    return base64_encode($payload);
}

// 解析 spec JSON，提取 $vr-座位号 类型的值
function extract_seat_from_spec(string $spec): string {
    $list = json_decode($spec, true);
    if (!is_array($list)) {
        return '';
    }
    foreach ($list as $item) {
        $type = $item['type'] ?? '';
        $value = $item['value'] ?? '';
        if ($type === '$vr-座位号') {
            return $value;
        }
    }
    // fallback: 取 $vr-分区
    foreach ($list as $item) {
        $type = $item['type'] ?? '';
        $value = $item['value'] ?? '';
        if ($type === '$vr-分区') {
            return $value;
        }
    }
    return '';
}

// 判断是否为票务商品
function is_ticket_goods(PDO $pdo, int $goods_id): bool {
    $stmt = $pdo->prepare("SELECT item_type FROM vrt_goods WHERE id = ?");
    $stmt->execute([$goods_id]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$row) {
        return false;
    }
    return ($row['item_type'] ?? '') === 'ticket';
}

// ============================================================
// 主流程
// ============================================================

// Step 1: 查找已支付订单中，vrt_vr_tickets 里还没有票的订单
$sql = "
SELECT 
    o.id AS order_id,
    o.order_no,
    o.user_id,
    o.extension_data,
    od.id AS detail_id,
    od.goods_id,
    od.title,
    od.price,
    od.spec
FROM vrt_order o
JOIN vrt_order_detail od ON o.id = od.order_id
WHERE o.pay_status = 1
  AND NOT EXISTS (
      SELECT 1 FROM vrt_vr_tickets t
      WHERE t.order_id = o.id
        AND t.seat_info = :seat_placeholder
  )
ORDER BY o.id, od.id
";

$stmt = $pdo->prepare($sql);
$stmt->execute(['seat_placeholder' => '']);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

echo "[Step 1] 找到 " . count($rows) . " 个待处理的订单明细行\n\n";

// 先按 order_id 分组，看看哪些订单需要处理
$ordersById = [];
foreach ($rows as $row) {
    $ordersById[$row['order_id']] = true;
}
echo "涉及订单: " . implode(', ', array_keys($ordersById)) . "\n\n";

// Step 2: 逐行处理，发放票
$totalNewTickets = 0;
$skipped = 0;
$pdo->beginTransaction();

foreach ($rows as $row) {
    $order_id = (int)$row['order_id'];
    $detail_id = (int)$row['detail_id'];
    $goods_id = (int)$row['goods_id'];
    $order_no = $row['order_no'];
    $user_id = (int)$row['user_id'];
    $title = $row['title'];
    $price = (float)$row['price'];
    $spec = $row['spec'];
    $extension_data = $row['extension_data'] ?? '{}';

    // 判断是否为票务商品
    if (!is_ticket_goods($pdo, $goods_id)) {
        echo "  [SKIP] order_id={$order_id} goods_id={$goods_id} 不是票务商品\n";
        $skipped++;
        continue;
    }

    // 解析座位信息
    $seat_info = extract_seat_from_spec($spec);
    if (empty($seat_info)) {
        echo "  [WARN] order_id={$order_id} goods_id={$goods_id} 无法从 spec 提取座位信息\n";
        $skipped++;
        continue;
    }

    // 幂等检查：同一 order_id + seat_info 是否已有票
    $checkStmt = $pdo->prepare("SELECT id FROM vrt_vr_tickets WHERE order_id = ? AND seat_info = ?");
    $checkStmt->execute([$order_id, $seat_info]);
    if ($checkStmt->fetch()) {
        echo "  [SKIP] order_id={$order_id} seat_info={$seat_info} 已存在票，幂等跳过\n";
        $skipped++;
        continue;
    }

    // 生成票数据
    $ticket_code = vr_generate_uuid();
    $now = vr_now();
    $goods_snapshot = json_encode([
        'goods_name' => $title,
        'spec_name'  => $seat_info,
        'price'      => $price,
    ], JSON_UNESCAPED_UNICODE);

    // 插入票
    $insertSql = "INSERT INTO vrt_vr_tickets 
        (order_id, order_no, goods_id, goods_snapshot, user_id, ticket_code, qr_data,
         seat_info, spec_base_id, real_name, phone, id_card, verify_status, issued_at, created_at, updated_at)
        VALUES
        (:order_id, :order_no, :goods_id, :goods_snapshot, :user_id, :ticket_code, :qr_data,
         :seat_info, :spec_base_id, :real_name, :phone, :id_card, 0, :issued_at, :created_at, :updated_at)";

    $insertStmt = $pdo->prepare($insertSql);

    // 生成短码和 QR payload（先插入获取自增 ID）
    // 短码需要 ticket_id，所以分两步：先插入占位，再更新

    $insertStmt->execute([
        ':order_id'       => $order_id,
        ':order_no'       => $order_no,
        ':goods_id'       => $goods_id,
        ':goods_snapshot' => $goods_snapshot,
        ':user_id'        => $user_id,
        ':ticket_code'    => $ticket_code,
        ':qr_data'        => '', // 占位
        ':seat_info'      => $seat_info,
        ':spec_base_id'   => 0,
        ':real_name'      => '',
        ':phone'          => '',
        ':id_card'        => '',
        ':issued_at'      => $now,
        ':created_at'     => $now,
        ':updated_at'     => $now,
    ]);

    $ticket_id = (int)$pdo->lastInsertId();

    // 生成短码和 QR payload
    $short_code = vr_short_code_encode($goods_id, $ticket_id, $VR_SECRET);
    $qr_payload = vr_sign_qr_payload($ticket_id, $goods_id, $now, $VR_SECRET);
    $qr_data = $short_code . '|' . $qr_payload;

    // 更新 qr_data
    $updateStmt = $pdo->prepare("UPDATE vrt_vr_tickets SET qr_data = :qr_data WHERE id = :id");
    $updateStmt->execute([':qr_data' => $qr_data, ':id' => $ticket_id]);

    // 写入观演人信息
    $attendee = ['real_name' => '', 'phone' => '', 'id_card' => ''];
    $extData = json_decode($extension_data, true);
    if (isset($extData['attendee'])) {
        $attendee = array_merge($attendee, $extData['attendee']);
    }

    $updateAttendee = $pdo->prepare("UPDATE vrt_vr_tickets SET real_name = :rn, phone = :ph, id_card = :ic WHERE id = :id");
    $updateAttendee->execute([
        ':rn' => $attendee['real_name'] ?? '',
        ':ph' => $attendee['phone'] ?? '',
        ':ic' => $attendee['id_card'] ?? '',
        ':id' => $ticket_id,
    ]);

    $totalNewTickets++;
    echo "  [OK] order_id={$order_id}, ticket_id={$ticket_id}, goods_id={$goods_id}, seat={$seat_info}\n";
}

$pdo->commit();

echo "\n========================================\n";
echo "完成！\n";
echo "补发票数: {$totalNewTickets}\n";
echo "跳过数: {$skipped}\n";
echo "========================================\n";
