<?php
declare(strict_types=1);

$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$guest = (string)($argv[3] ?? '');
if (!in_array($phase, ['--audit', '--run'], true)
    || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^guest_cdx[a-f0-9]{16}$/D', $guest)
    || ($phase === '--run' && getenv('CDX_CHAT_MIGRATE_RUN') !== 'OWN_EMPTY_GUEST_AND_LOGIN_V1')) {
    fwrite(STDERR, "DENIED business_http=0 business_dml=0\n"); exit(78);
}
$suffix = substr($guest, -16);
$bot = 'cdxmigbot';
$conversation = 'cdxmig_' . $suffix;
$actorName = $lane === 'B' ? 'CDXPARMOB426CA4B0' : 'CDXPARMOB9411C2CB';
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$loadedRoot = $lane === 'B'
    ? '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/ai_api'
    : '/tmp/zhct-dm-poc-20260928.fcpiiN/ai_api';
$port = $lane === 'B' ? 18101 : 18102;
foreach (['app/api/service/user/AiConversation.php' => '4431e6451ac088ad325a23626ee669996dc8fde1e92e721b1eccad085bd3e2fc',
    'app/api/controller/user/AiConversation.php' => '9ab9c645e754edb8afe7ac80fef8fadc3b5cf6232af2239b7e49d1cf1db8c3ab'] as $file => $sha) {
    if (hash_file('sha256', $loadedRoot . '/' . $file) !== $sha) {
        throw new RuntimeException('isolated Composer business source changed');
    }
}
if (!is_file($root . '/public/index.php') || !is_file($root . '/vendor/autoload.php')) {
    throw new RuntimeException('isolated worker root absent');
}
if ($lane === 'B') {
    $ini = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW);
    $main = $ini['DATABASE'] ?? null;
    $store = $ini['DATABASE_ZHCT'] ?? null;
    if (!is_array($main) || !is_array($store)
        || strtolower((string)($main['TYPE'] ?? '')) !== 'mysql'
        || (string)($main['HOSTPORT'] ?? '') !== '3306'
        || hash('sha256', (string)($main['HOSTNAME'] ?? '')) !== '47e8be48b0cfd56dcdb9d9d426e6637b245f340b4efad4cd2f084ec5851ecc8e'
        || hash('sha256', (string)($main['DATABASE'] ?? '')) !== 'cf7756b3bb52cf72f625689134015b877fdab3ad8eb3bb2ddd962bb35f6efd66') {
        throw new RuntimeException('B code-local development database changed');
    }
    foreach (['POC_DIALECT' => 'mysql', 'POC_AI_HOST' => $main['HOSTNAME'],
        'POC_AI_PORT' => $main['HOSTPORT'], 'POC_AI_SCHEMA' => $main['DATABASE'],
        'POC_AI_USER' => $main['USERNAME'], 'POC_AI_PASSWORD' => $main['PASSWORD'],
        'POC_HOST' => $store['HOSTNAME'], 'POC_PORT' => $store['HOSTPORT'],
        'POC_SCHEMA' => $store['DATABASE'], 'POC_USER' => $store['USERNAME'],
        'POC_PASSWORD' => $store['PASSWORD']] as $key => $value) putenv($key . '=' . $value);
    $db = new PDO('mysql:host=' . $main['HOSTNAME'] . ';port=3306;dbname=' . $main['DATABASE']
        . ';charset=' . $main['CHARSET'], $main['USERNAME'], $main['PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
} else {
    if (getenv('POC_DIALECT') !== 'dm' || getenv('POC_AI_HOST') !== '127.0.0.1'
        || getenv('POC_AI_PORT') !== '5238' || getenv('POC_AI_SCHEMA') !== 'AIZHCT_APP'
        || getenv('POC_AI_USER') !== 'AIZHCT_APP'
        || getenv('POC_HOST') !== '127.0.0.1' || getenv('POC_PORT') !== '5237'
        || getenv('POC_SCHEMA') !== 'ZHCT_APP') {
        throw new RuntimeException('C isolated two-database target changed');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C effective AI schema changed');
    }
}
$all = static function (string $sql, array $args = []) use ($db): array {
    $stmt = $db->prepare($sql); $stmt->execute($args);
    return array_map(static function (array $row): array { return array_change_key_case($row, CASE_LOWER); },
        $stmt->fetchAll(PDO::FETCH_ASSOC));
};
$actor = $all('SELECT user_id FROM yoshop_user WHERE nick_name=? AND store_id=10001 AND is_delete=0', [$actorName]);
if (count($actor) !== 1 || (int)$actor[0]['user_id'] <= 0) throw new RuntimeException('synthetic actor unavailable');
$userId = (int)$actor[0]['user_id'];
$token = 'CDXMIGTK' . strtoupper(hash('sha256', $guest));
$messages = static function () use ($all, $bot, $conversation): array {
    return $all('SELECT id,user_id,store_id,bot_id,conversation_id,message_key,content,content_file_url,is_delete'
        . ' FROM yoshop_ai_conversation WHERE store_id=10001 AND bot_id=? AND conversation_id=?',
        [$bot, $conversation]);
};
$sessions = static function () use ($all, $bot, $conversation): array {
    return $all('SELECT id,user_id,ai_user_id,store_id,bot_id,conversation_id,message_count,is_current,is_delete'
        . ' FROM yoshop_ai_conversation_session WHERE store_id=10001 AND bot_id=? AND conversation_id=?',
        [$bot, $conversation]);
};
$identities = static function () use ($all, $userId): array {
    return $all('SELECT id,user_id,ai_user_id,guest_id,is_delete FROM yoshop_ai_user_identity'
        . ' WHERE store_id=10001 AND user_id=?', [$userId]);
};
define('TP_ENV', 'poc'); define('BUSINESS', 'aizhct'); define('ENTERPRISE_NO', '10001');
require_once $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();
if (strtolower((string)\think\facade\Config::get('cache.default')) !== 'file'
    || realpath((string)\think\facade\Config::get('cache.stores.file.path')) !== realpath($root . '/runtime/cache')) {
    throw new RuntimeException('isolated File cache changed');
}
$mapped = \think\facade\Db::connect('mysql_zhct')->name('user')
    ->where(['ai_user_id' => $userId, 'is_delete' => 0])->find();
if (empty($mapped) || !isset($mapped['password_version'])) throw new RuntimeException('Store actor mapping unavailable');
$ormUser = \app\api\model\User::detail($userId);
if (empty($ormUser) || (int)$ormUser['user_id'] !== $userId) throw new RuntimeException('actor ORM mismatch');
$initial = [count($messages()), count($sessions()), count($identities())];
$hasToken = \think\facade\Cache::has($token);
if ($phase === '--audit') {
    echo json_encode(['lane' => $lane, 'phase' => 'audit',
        'guest_sha12' => substr(hash('sha256', $guest), 0, 12),
        'owned_message_session_identity_counts' => $initial,
        'owned_file_token_exists' => $hasToken,
        'business_http' => 0, 'business_dml' => 0]), PHP_EOL;
    exit($initial === [0, 0, 0] && !$hasToken ? 0 : 2);
}
if ($initial !== [0, 0, 0] || $hasToken) throw new RuntimeException('synthetic actor/marker is not isolated');
$http = static function (array $form) use ($port, $token): array {
    $rawForm = json_encode($form, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if (!is_string($rawForm)) throw new RuntimeException('request encode failed');
    $ctx = stream_context_create(['http' => ['method' => 'POST',
        'header' => 'Access-Token: ' . $token . "\r\nplatform: MP-WEIXIN\r\n"
            . "Content-Type: application/json\r\n",
        'content' => $rawForm, 'ignore_errors' => true,
        'timeout' => 20, 'follow_location' => 0]]);
    $handle = @fopen('http://127.0.0.1:' . $port
        . '/api/user.AiConversation/migrateGuest?storeId=10001', 'rb', false, $ctx);
    if (!is_resource($handle)) throw new RuntimeException('isolated migrateGuest HTTP unavailable');
    $raw = stream_get_contents($handle, 524289); fclose($handle);
    if (!is_string($raw) || strlen($raw) > 524288) throw new RuntimeException('migrateGuest response cap');
    $body = json_decode($raw, true);
    if (!is_array($body)) throw new RuntimeException('migrateGuest response not JSON');
    return $body;
};
$typeGraph = static function ($value) use (&$typeGraph) {
    if (!is_array($value)) return gettype($value);
    if (!$value) return [];
    if (array_keys($value) === range(0, count($value) - 1)) return array_map($typeGraph, $value);
    ksort($value); return array_map($typeGraph, $value);
};
$messageId = 0; $sessionId = 0; $identityId = 0; $targetAiUserId = '';
$error = null; $result = [];
try {
    $stmt = $db->prepare('INSERT INTO yoshop_ai_conversation'
        . ' (user_id,store_id,open_id,bot_id,conversation_id,message_key,content_file_url,'
        . 'upload_file_url,content,deal_flag,is_delete) VALUES (?,10001,\'\',?,?,?,\'\',\'\',\'[]\',0,0)');
    $stmt->execute([$guest, $bot, $conversation, md5('guest|' . $guest)]);
    $stmt = $db->prepare('INSERT INTO yoshop_ai_conversation_session'
        . ' (store_id,user_id,ai_user_id,open_id,bot_id,conversation_id,title,last_message,'
        . 'message_count,is_current,is_delete,create_time,update_time)'
        . ' VALUES (10001,0,?,\'\',?,?,\'\',\'\',0,1,0,?,?)');
    $now = time(); $stmt->execute([$guest, $bot, $conversation, $now, $now]);
    $beforeMessage = $messages(); $beforeSession = $sessions();
    if (count($beforeMessage) !== 1 || count($beforeSession) !== 1
        || (string)$beforeMessage[0]['user_id'] !== $guest
        || (int)$beforeSession[0]['user_id'] !== 0
        || (string)$beforeSession[0]['ai_user_id'] !== $guest) {
        throw new RuntimeException('guest fixture original rows mismatch');
    }
    $messageId = (int)$beforeMessage[0]['id'];
    $sessionId = (int)$beforeSession[0]['id'];
    if (!\think\facade\Cache::set($token, ['user' => $ormUser, 'store_id' => 10001,
        'is_login' => true, 'password_version' => (int)$mapped['password_version'],
        'login_type' => 'sms'], 300) || !\think\facade\Cache::has($token)) {
        throw new RuntimeException('isolated short File token failed');
    }
    $first = $http(['guest_ai_user_id' => $guest]);
    $identity = $identities(); $afterMessage = $messages(); $afterSession = $sessions();
    if ((int)($first['status'] ?? 0) !== 200 || count($identity) !== 1
        || count($afterMessage) !== 1 || count($afterSession) !== 1) {
        throw new RuntimeException('migrateGuest first HTTP or row count failed status='
            . (string)($first['status'] ?? 'nonjson'));
    }
    $identityId = (int)$identity[0]['id'];
    $targetAiUserId = (string)$identity[0]['ai_user_id'];
    $expectedMessageKey = md5('10001|' . $bot . '|' . $conversation . '|[]');
    if ($targetAiUserId === '' || $targetAiUserId === $guest
        || strpos($targetAiUserId, 'aiu_') !== 0
        || (string)$identity[0]['guest_id'] !== $guest
        || (int)$identity[0]['user_id'] !== $userId
        || (int)$identity[0]['is_delete'] !== 0
        || (string)($first['data']['ai_user_id'] ?? '') !== $targetAiUserId
        || (int)($first['data']['migrated_messages'] ?? -1) !== 1
        || (int)($first['data']['migrated_sessions'] ?? -1) !== 1
        || (int)$afterMessage[0]['id'] !== $messageId
        || (string)$afterMessage[0]['user_id'] !== $targetAiUserId
        || (string)$afterMessage[0]['message_key'] !== $expectedMessageKey
        || (string)$afterMessage[0]['content_file_url'] !== ''
        || (int)$afterMessage[0]['is_delete'] !== 0
        || (int)$afterSession[0]['id'] !== $sessionId
        || (int)$afterSession[0]['user_id'] !== $userId
        || (string)$afterSession[0]['ai_user_id'] !== $targetAiUserId
        || (int)$afterSession[0]['is_current'] !== 1
        || (int)$afterSession[0]['is_delete'] !== 0) {
        throw new RuntimeException('migrateGuest original-table source-to-target mismatch');
    }
    $second = $http(['guest_ai_user_id' => $guest]);
    if ((int)($second['status'] ?? 0) !== 200
        || (string)($second['data']['ai_user_id'] ?? '') !== $targetAiUserId
        || (int)($second['data']['migrated_messages'] ?? -1) !== 0
        || (int)($second['data']['migrated_sessions'] ?? -1) !== 0
        || count($messages()) !== 1 || count($sessions()) !== 1 || count($identities()) !== 1) {
        throw new RuntimeException('migrateGuest retry/idempotency mismatch');
    }
    $result = ['http_business_ok' => 2, 'first_migrated_messages' => 1,
        'first_migrated_sessions' => 1, 'repeat_migrated_messages' => 0,
        'repeat_migrated_sessions' => 0, 'message_session_identity_source_equal' => true,
        'first_response_type_sha256' => hash('sha256', json_encode($typeGraph($first))),
        'repeat_response_type_sha256' => hash('sha256', json_encode($typeGraph($second)))];
} catch (Throwable $caught) {
    $error = $caught;
} finally {
    if ($db->inTransaction()) $db->rollBack();
    foreach ($messages() as $row) {
        if ($messageId && (int)$row['id'] !== $messageId) continue;
        $stmt = $db->prepare('DELETE FROM yoshop_ai_conversation'
            . ' WHERE id=? AND store_id=10001 AND bot_id=? AND conversation_id=?');
        $stmt->execute([(int)$row['id'], $bot, $conversation]);
    }
    foreach ($sessions() as $row) {
        if ($sessionId && (int)$row['id'] !== $sessionId) continue;
        $stmt = $db->prepare('DELETE FROM yoshop_ai_conversation_session'
            . ' WHERE id=? AND store_id=10001 AND bot_id=? AND conversation_id=?');
        $stmt->execute([(int)$row['id'], $bot, $conversation]);
    }
    foreach ($identities() as $row) {
        if ($identityId && (int)$row['id'] !== $identityId) continue;
        if ((string)$row['guest_id'] !== $guest) continue;
        $stmt = $db->prepare('DELETE FROM yoshop_ai_user_identity'
            . ' WHERE id=? AND store_id=10001 AND user_id=? AND guest_id=?');
        $stmt->execute([(int)$row['id'], $userId, $guest]);
    }
    \think\facade\Cache::delete($token);
}
if ($messages() || $sessions() || $identities() || \think\facade\Cache::has($token)) {
    throw new RuntimeException('owned message/session/identity/token cleanup failed');
}
if ($error !== null) throw $error;
echo json_encode(['lane' => $lane, 'guest_sha12' => substr(hash('sha256', $guest), 0, 12)]
    + $result + ['owned_message_session_identity_token_cleanup_zero' => true,
        'OSS_payment_device_external_calls' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
