<?php
declare(strict_types=1);

const STORE_ROOT = '/workspace/zhct/zhctproject/store';
const AI_ROOT = '/workspace/zhct/zhctproject/ai_api';
const BASE = 'http://127.0.0.1';
const PREFIX = 'CQHW_E2E_20260728_';
const APPKEY = '1234567890abcdef';

function logLine(string $message): void
{
    echo $message . PHP_EOL;
}

function fail(string $message): void
{
    fwrite(STDERR, '[FAIL] ' . $message . PHP_EOL);
    exit(1);
}

function pass(string $message): void
{
    logLine('[PASS] ' . $message);
}

function assertTrue(bool $condition, string $message): void
{
    $condition ? pass($message) : fail($message);
}

function parseIniEnv(string $path): array
{
    $data = parse_ini_file($path, true, INI_SCANNER_RAW);
    if (!is_array($data)) {
        fail('无法读取环境配置：' . $path);
    }
    return $data;
}

function pdoFrom(array $section): PDO
{
    $dsn = sprintf(
        'mysql:host=%s;port=%s;dbname=%s;charset=%s',
        $section['HOSTNAME'],
        $section['HOSTPORT'] ?? '3306',
        $section['DATABASE'],
        $section['CHARSET'] ?? 'utf8'
    );
    $pdo = new PDO($dsn, $section['USERNAME'], $section['PASSWORD'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    return $pdo;
}

function one(PDO $pdo, string $sql, array $params = []): array
{
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $row = $stmt->fetch();
    return $row ?: [];
}

function allRows(PDO $pdo, string $sql, array $params = []): array
{
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll();
}

function runSql(PDO $pdo, string $sql, array $params = []): void
{
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
}

function signFor(string $timestamp, string $randstr): string
{
    $values = [APPKEY, $timestamp, $randstr];
    sort($values, SORT_STRING);
    return sha1(implode('', $values));
}

function httpJson(string $method, string $url, array $params = [], array $headers = []): array
{
    $ch = curl_init();
    if ($method === 'GET' && $params) {
        $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params);
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);
    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    }
    if ($headers) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    }
    $body = curl_exec($ch);
    if ($body === false) {
        fail('HTTP 请求失败：' . curl_error($ch));
    }
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    $json = json_decode((string)$body, true);
    if (!is_array($json)) {
        fail('HTTP 返回非 JSON，status=' . $status . ' body=' . substr((string)$body, 0, 500));
    }
    return ['status' => $status, 'body' => $json, 'raw' => $body];
}

function parseQrPayload(string $twocode): array
{
    $query = parse_url($twocode, PHP_URL_QUERY);
    parse_str((string)($query ?: $twocode), $params);
    return $params;
}

function applyPrepayMigration(PDO $zhct): void
{
    $exists = one($zhct, "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ydy_prepay_order'");
    if (!$exists) {
        $sql = file_get_contents(STORE_ROOT . '/db/v2/v2.0.35.sql');
        if ($sql === false) {
            fail('无法读取预扣款建表 SQL');
        }
        $lines = array_filter(explode("\n", $sql), static function (string $line): bool {
            return strpos(ltrim($line), '--') !== 0;
        });
        foreach (array_filter(array_map('trim', explode(';', implode("\n", $lines)))) as $statement) {
            if ($statement === '') {
                continue;
            }
            $zhct->exec($statement);
        }
    }
}

function cleanup(PDO $zhct, PDO $ai): void
{
    $prepayIds = allRows($zhct, "SELECT id, meal_order_id FROM ydy_prepay_order WHERE device_order_no LIKE :prefix OR machid LIKE :prefix", [':prefix' => PREFIX . '%']);
    $mealIds = array_values(array_filter(array_map(static function ($row) {
        return (int)$row['meal_order_id'];
    }, $prepayIds)));
    foreach ($mealIds as $mealId) {
        runSql($zhct, "DELETE FROM ydy_meal_order WHERE id = :id", [':id' => $mealId]);
        runSql($zhct, "DELETE FROM ydy_user_balance_log WHERE order_id = :id", [':id' => $mealId]);
    }
    runSql($zhct, "DELETE FROM ydy_prepay_order WHERE device_order_no LIKE :prefix OR machid LIKE :prefix", [':prefix' => PREFIX . '%']);
    runSql($zhct, "DELETE FROM ydy_equipment WHERE sn LIKE :prefix OR code LIKE :prefix", [':prefix' => PREFIX . '%']);
    runSql($zhct, "DELETE FROM ydy_user WHERE account LIKE :prefix OR name LIKE :prefix", [':prefix' => PREFIX . '%']);
    runSql($ai, "DELETE FROM yoshop_user_oauth WHERE oauth_id LIKE :prefix", [':prefix' => PREFIX . '%']);
    runSql($ai, "DELETE FROM yoshop_user_balance_log WHERE `describe` LIKE :prefix", [':prefix' => '%' . PREFIX . '%']);
    runSql($ai, "DELETE FROM yoshop_user WHERE nick_name LIKE :prefix", [':prefix' => PREFIX . '%']);
}

function upsertConfig(PDO $zhct, string $key, string $value, string $desc): void
{
    $row = one($zhct, "SELECT id FROM ydy_config WHERE config_key = :key AND restaurant_id = 1 LIMIT 1", [':key' => $key]);
    if ($row) {
        runSql($zhct, "UPDATE ydy_config SET config_value = :value, config_desc = :desc, status = 1, update_by = 'codex_e2e' WHERE id = :id", [
            ':value' => $value,
            ':desc' => $desc,
            ':id' => $row['id'],
        ]);
        return;
    }
    runSql($zhct, "INSERT INTO ydy_config(config_key, config_value, config_desc, create_by, status, create_time, update_time, restaurant_id, is_show, update_by)
        VALUES(:key, :value, :desc, 'codex_e2e', 1, NOW(), NOW(), 1, 0, 'codex_e2e')", [
        ':key' => $key,
        ':value' => $value,
        ':desc' => $desc,
    ]);
}

function seedRealData(PDO $zhct, PDO $ai): array
{
    $now = time();
    runSql($ai, "INSERT INTO yoshop_user(mobile, nick_name, balance, platform, business, channel, is_delete, store_id, create_time, update_time)
        VALUES('13800000001', :nick_name, 100.00, 'MP-WEIXIN', 'zhct', '10000', 0, 10001, :now, :now)", [
        ':nick_name' => PREFIX . 'USER',
        ':now' => $now,
    ]);
    $aiUserId = (int)$ai->lastInsertId();
    runSql($ai, "INSERT INTO yoshop_user_oauth(user_id, oauth_type, oauth_id, unionid, store_id, is_delete, create_time, update_time)
        VALUES(:user_id, 'MP-WEIXIN', :oauth_id, '', 10001, 0, :now, :now)", [
        ':user_id' => $aiUserId,
        ':oauth_id' => PREFIX . 'OPENID',
        ':now' => $now,
    ]);

    runSql($zhct, "INSERT INTO ydy_user(uuid, department_uuid, station_uuid, account, pwd, salt, name, sex, mobile, email, type, status, user_type, demo, create_time, create_by, staff_uuid, login_check, third_type, openid, nickname, is_wx, balance, pay_money, member_type, is_delete, source, ai_mobile, ai_user_id, password_status, password_version)
        VALUES(UUID(), '', '', :account, 'codex_e2e_pwd', 'codexe2e', :name, 1, '13800000001', '', 1, 1, 1, 'codex e2e test', NOW(), 'codex_e2e', '', 0, 2, :openid, :nickname, 1, 0.00, 0.00, 1, 0, 1, '13800000001', :ai_user_id, 2, 0)", [
        ':account' => PREFIX . 'ACCOUNT',
        ':name' => PREFIX . 'USER',
        ':openid' => PREFIX . 'OPENID',
        ':nickname' => PREFIX . 'USER',
        ':ai_user_id' => $aiUserId,
    ]);
    $zhctUserId = (int)$zhct->lastInsertId();

    runSql($zhct, "INSERT INTO ydy_equipment(code, name, type, sn, restaurant_id, status, matecode, communicationkey, del_flag, mate_flag, hearttime, face_time, create_time, create_by, update_time, update_by)
        VALUES(:code, :name, 60, :sn, 1, 1, '', '', 0, 1, 0, '2001-01-01 00:00:00', NOW(), 'codex_e2e', NOW(), 'codex_e2e')", [
        ':code' => PREFIX . 'COFFEE_01',
        ':name' => PREFIX . '咖啡机',
        ':sn' => PREFIX . 'MACH_001',
    ]);
    $equipmentId = (int)$zhct->lastInsertId();

    return [
        'ai_user_id' => $aiUserId,
        'zhct_user_id' => $zhctUserId,
        'equipment_id' => $equipmentId,
        'machid' => PREFIX . 'MACH_001',
    ];
}

function seedAiLoginToken(int $aiUserId): string
{
    chdir(AI_ROOT);
    require AI_ROOT . '/env.php';
    require AI_ROOT . '/vendor/autoload.php';
    $app = (new think\App())->setEnvName(TP_ENV);
    $app->initialize();
    $token = PREFIX . 'TOKEN_' . $aiUserId;
    think\facade\Cache::set($token, [
        'user' => ['user_id' => $aiUserId],
        'password_version' => 0,
        'login_type' => 'account',
    ], 7200);
    assertTrue(think\facade\Cache::has($token), 'ai_api 登录 token 已写入缓存');
    return $token;
}

function payByRealAiApiService(string $orderNo): void
{
    $service = new app\api\service\meal\PaySuccess();
    $service->setOrderNo($orderNo)
        ->setMethod(app\common\enum\payment\Method::BALANCE)
        ->handle();
}

$env = parseIniEnv(AI_ROOT . '/.env.local');
$ai = pdoFrom($env['DATABASE']);
$zhct = pdoFrom($env['DATABASE_ZHCT']);

applyPrepayMigration($zhct);
cleanup($zhct, $ai);
upsertConfig($zhct, 'coffee_machine_appkey', APPKEY, '咖啡机第三方支付接口默认appkey');
upsertConfig($zhct, 'prepay_order_scan_url', BASE . '/aizhct/api/PrepayOrder/scan', '预扣款小程序扫码入口开发库测试地址');

$seed = seedRealData($zhct, $ai);
$token = seedAiLoginToken($seed['ai_user_id']);

$timestamp = (string)time();
$randstr = PREFIX . 'RAND_A';
$orderId = PREFIX . 'ORDER_10001';
$aParams = [
    'orderid' => $orderId,
    'machid' => $seed['machid'],
    'trackno' => 'LATTE-01',
    'name' => PREFIX . '拿铁',
    'price' => '1200',
    'channelid' => 'BALANCE',
    'timestamp' => $timestamp,
    'randstr' => $randstr,
    'sign' => signFor($timestamp, $randstr),
];
$a = httpJson('POST', BASE . '/api/prepayOrder/code', $aParams);
assertTrue(($a['body']['code'] ?? '') === '1', '接口A真实 HTTP 取码成功');
$torderid = (int)$a['body']['torderid'];
$twocode = (string)$a['body']['twocode'];
assertTrue($torderid > 0 && strpos($twocode, 'torderid=' . $torderid) !== false, '接口A返回真实 torderid 和二维码');

$prepay = one($zhct, "SELECT * FROM ydy_prepay_order WHERE id = :id", [':id' => $torderid]);
assertTrue($prepay && (int)$prepay['prepay_status'] === 2 && (int)$prepay['meal_order_id'] === 0, '开发库产生真实等待付款预扣款订单');

$scanParams = parseQrPayload($twocode);
$scan = httpJson('GET', BASE . '/aizhct/api/PrepayOrder/scan', $scanParams, [
    'Access-Token: ' . $token,
    'storeId: 10001',
    'platform: MP-WEIXIN',
]);
assertTrue((int)($scan['body']['status'] ?? 0) === 200, '小程序扫码真实 HTTP 创建订单成功');
$mealOrderId = (int)($scan['body']['data']['orderId'] ?? 0);
assertTrue($mealOrderId > 0, '扫码返回真实 meal_order_id');

$bound = one($zhct, "SELECT p.id, p.prepay_status, p.meal_order_id, p.meal_order_no, p.user_id, m.order_no, m.source, m.pay_status, m.pay_price
    FROM ydy_prepay_order p JOIN ydy_meal_order m ON p.meal_order_id = m.id WHERE p.id = :id", [':id' => $torderid]);
assertTrue((int)$bound['source'] === 8 && (int)$bound['pay_status'] === 10 && (float)$bound['pay_price'] === 12.00, '开发库正式订单 source=8 且待支付金额 12.00');

payByRealAiApiService($bound['order_no']);
$paid = one($zhct, "SELECT p.prepay_status, m.pay_status, m.order_status FROM ydy_prepay_order p JOIN ydy_meal_order m ON p.meal_order_id = m.id WHERE p.id = :id", [':id' => $torderid]);
$afterBalance = (float)one($ai, "SELECT balance FROM yoshop_user WHERE user_id = :id", [':id' => $seed['ai_user_id']])['balance'];
assertTrue((int)$paid['prepay_status'] === 1 && (int)$paid['pay_status'] === 20 && (int)$paid['order_status'] === 30, '真实 PaySuccess 服务完成手机端余额支付状态流转');
assertTrue(abs($afterBalance - 88.00) < 0.001, '真实 PaySuccess 服务扣减 AI 用户余额 12.00 元');

$timestamp = (string)time();
$randstr = PREFIX . 'RAND_C';
$c = httpJson('POST', BASE . '/api/prepayOrder/payResult', [
    'orderid' => $orderId,
    'machid' => $seed['machid'],
    'torderid' => (string)$torderid,
    'timestamp' => $timestamp,
    'randstr' => $randstr,
    'sign' => signFor($timestamp, $randstr),
]);
assertTrue(($c['body']['code'] ?? '') === '1', '接口C真实 HTTP 查扣款返回 code=1');

$timestamp = (string)time();
$randstr = PREFIX . 'RAND_B';
$b = httpJson('POST', BASE . '/api/prepayOrder/fulfillResult', [
    'orderid' => $orderId,
    'machid' => $seed['machid'],
    'torderid' => (string)$torderid,
    'status' => '1',
    'errinfo' => '',
    'timestamp' => $timestamp,
    'randstr' => $randstr,
    'sign' => signFor($timestamp, $randstr),
]);
assertTrue(($b['body']['code'] ?? '') === '1', '接口B真实 HTTP 报结果成功');
$done = one($zhct, "SELECT prepay_status, ship_status FROM ydy_prepay_order WHERE id = :id", [':id' => $torderid]);
assertTrue((int)$done['prepay_status'] === 5 && (int)$done['ship_status'] === 1, '真实预扣款订单出货完成 code=5');

$timestamp = (string)time();
$randstr = PREFIX . 'RAND_TIMEOUT';
$timeoutOrderId = PREFIX . 'ORDER_TIMEOUT';
$timeoutA = httpJson('POST', BASE . '/api/prepayOrder/code', [
    'orderid' => $timeoutOrderId,
    'machid' => $seed['machid'],
    'trackno' => 'MOCHA-01',
    'name' => PREFIX . '摩卡',
    'price' => '900',
    'channelid' => 'BALANCE',
    'timestamp' => $timestamp,
    'randstr' => $randstr,
    'sign' => signFor($timestamp, $randstr),
]);
$timeoutTorderid = (int)$timeoutA['body']['torderid'];
$timeoutScanParams = parseQrPayload((string)$timeoutA['body']['twocode']);
$timeoutScan = httpJson('GET', BASE . '/aizhct/api/PrepayOrder/scan', $timeoutScanParams, [
    'Access-Token: ' . $token,
    'storeId: 10001',
    'platform: MP-WEIXIN',
]);
$timeoutMealOrderId = (int)($timeoutScan['body']['data']['orderId'] ?? 0);
runSql($zhct, "UPDATE ydy_meal_order SET bind_time = DATE_SUB(NOW(), INTERVAL 40 MINUTE), update_time = NOW() WHERE id = :id", [':id' => $timeoutMealOrderId]);
runSql($zhct, "UPDATE ydy_meal_order SET order_status = 20, update_time = NOW() WHERE id = :id AND pay_status = 10 AND source = 8", [':id' => $timeoutMealOrderId]);
runSql($zhct, "UPDATE ydy_prepay_order SET prepay_status = 3, update_by = 'cron_e2e' WHERE meal_order_id = :id AND prepay_status = 2", [':id' => $timeoutMealOrderId]);
$expired = one($zhct, "SELECT p.prepay_status, m.order_status FROM ydy_prepay_order p JOIN ydy_meal_order m ON p.meal_order_id = m.id WHERE p.id = :id", [':id' => $timeoutTorderid]);
assertTrue((int)$expired['prepay_status'] === 3 && (int)$expired['order_status'] === 20, '真实数据验证已扫码超时未支付更新 code=3');

$timestamp = (string)time();
$randstr = PREFIX . 'RAND_UNSCANNED';
$unscannedOrderId = PREFIX . 'ORDER_UNSCANNED';
$unscannedA = httpJson('POST', BASE . '/api/prepayOrder/code', [
    'orderid' => $unscannedOrderId,
    'machid' => $seed['machid'],
    'trackno' => 'AMERICANO-01',
    'name' => PREFIX . '美式',
    'price' => '800',
    'channelid' => 'BALANCE',
    'timestamp' => $timestamp,
    'randstr' => $randstr,
    'sign' => signFor($timestamp, $randstr),
]);
$unscanned = one($zhct, "SELECT prepay_status, meal_order_id FROM ydy_prepay_order WHERE id = :id", [':id' => (int)$unscannedA['body']['torderid']]);
assertTrue((int)$unscanned['prepay_status'] === 2 && (int)$unscanned['meal_order_id'] === 0, '真实数据验证未扫码预扣款订单保持 code=2');

logLine('');
logLine('REAL_DEVDB_E2E_RESULT=' . json_encode([
    'prefix' => PREFIX,
    'machid' => $seed['machid'],
    'ai_user_id' => $seed['ai_user_id'],
    'zhct_user_id' => $seed['zhct_user_id'],
    'prepay_id_paid_then_done' => $torderid,
    'meal_order_id_paid' => $mealOrderId,
    'device_order_no_paid' => $orderId,
    'qrcode' => $twocode,
    'prepay_id_expired' => $timeoutTorderid,
    'meal_order_id_expired' => $timeoutMealOrderId,
    'device_order_no_expired' => $timeoutOrderId,
    'prepay_id_unscanned' => (int)$unscannedA['body']['torderid'],
    'device_order_no_unscanned' => $unscannedOrderId,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
