<?php
declare(strict_types=1);

$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if (!in_array($phase, ['--run', '--audit'], true) || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXHLTH[A-F0-9]{10}$/D', $marker)
    || ($phase === '--run' && getenv('CDX_HEALTH_RUN') !== 'OWN_STAFF_GOAL_COURSE_V1')) {
    fwrite(STDERR, "DENIED business_http=0 business_dml=0\n"); exit(78);
}
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$port = $lane === 'B' ? 18101 : 18102;
if (!is_file($root . '/public/index.php') || !is_file($root . '/vendor/autoload.php')) {
    throw new RuntimeException('isolated AI worker root absent');
}
if ($lane === 'B') {
    $ini = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW);
    $aiCfg = is_array($ini) ? ($ini['DATABASE'] ?? null) : null;
    $storeCfg = is_array($ini) ? ($ini['DATABASE_ZHCT'] ?? null) : null;
    if (!is_array($aiCfg) || !is_array($storeCfg)
        || strtolower((string)($aiCfg['TYPE'] ?? '')) !== 'mysql'
        || (string)($aiCfg['HOSTPORT'] ?? '') !== '3306'
        || hash('sha256', (string)($aiCfg['HOSTNAME'] ?? '')) !== '47e8be48b0cfd56dcdb9d9d426e6637b245f340b4efad4cd2f084ec5851ecc8e'
        || hash('sha256', (string)($aiCfg['DATABASE'] ?? '')) !== 'cf7756b3bb52cf72f625689134015b877fdab3ad8eb3bb2ddd962bb35f6efd66') {
        throw new RuntimeException('B code-local AI identity drift');
    }
    foreach (['POC_DIALECT' => 'mysql',
        'POC_AI_HOST' => $aiCfg['HOSTNAME'], 'POC_AI_PORT' => $aiCfg['HOSTPORT'],
        'POC_AI_SCHEMA' => $aiCfg['DATABASE'], 'POC_AI_USER' => $aiCfg['USERNAME'],
        'POC_AI_PASSWORD' => $aiCfg['PASSWORD'],
        'POC_HOST' => $storeCfg['HOSTNAME'], 'POC_PORT' => $storeCfg['HOSTPORT'],
        'POC_SCHEMA' => $storeCfg['DATABASE'], 'POC_USER' => $storeCfg['USERNAME'],
        'POC_PASSWORD' => $storeCfg['PASSWORD']] as $name => $value) putenv($name . '=' . $value);
    $db = new PDO('mysql:host=' . $aiCfg['HOSTNAME'] . ';port=3306;dbname='
        . $aiCfg['DATABASE'] . ';charset=' . $aiCfg['CHARSET'],
        $aiCfg['USERNAME'], $aiCfg['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 two-customer-database identity drift');
    }
    $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 drift');
    }
}
$one = static function (string $sql, array $args = []) use ($db) {
    $stmt = $db->prepare($sql);
    $stmt->execute($args);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return is_array($row) ? array_change_key_case($row, CASE_LOWER) : null;
};
$actorName = $lane === 'C' ? 'CDXPARMOB9411C2CB' : 'CDXPARMOB426CA4B0';
$actor = $one('SELECT user_id,nick_name FROM yoshop_user'
    . ' WHERE nick_name=? AND store_id=10001 AND is_delete=0', [$actorName]);
if ($actor === null || (int)$actor['user_id'] <= 0) throw new RuntimeException('synthetic actor absent');
$userId = (int)$actor['user_id'];
$staffName = $marker . 'STAFF';
$token = 'CDXHLTHTK' . strtoupper(hash('sha256', $marker));
$count = static function (string $table) use ($db, $userId): int {
    if (!in_array($table, ['staff', 'user_weight_status', 'user_course'], true)) {
        throw new RuntimeException('unsupported fixture table');
    }
    $stmt = $db->prepare('SELECT COUNT(*) FROM yoshop_' . $table
        . ' WHERE user_id=? AND store_id=10001');
    $stmt->execute([$userId]);
    return (int)$stmt->fetchColumn();
};
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 drift');
}
$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('synthetic Store actor mapping absent');
}
$version = (int)$mapped['password_version'];
$ormUser = \app\api\model\User::detail($userId);
if (empty($ormUser) || (int)$ormUser['user_id'] !== $userId) {
    throw new RuntimeException('isolated ORM actor mismatch');
}
$initial = array_map($count, ['staff', 'user_weight_status', 'user_course']);
$hasToken = \think\facade\Cache::has($token);
$courseColumns = $lane === 'B'
    ? $db->query('SHOW COLUMNS FROM yoshop_drill_course')->fetchAll(PDO::FETCH_COLUMN)
    : $db->query("SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YOSHOP_DRILL_COURSE'")
        ->fetchAll(PDO::FETCH_COLUMN);
$courseColumns = array_map('strtolower', $courseColumns);
$optionalColumns = array_values(array_intersect(
    ['tab_index', 'tab_label', 'segment_serial_no'], $courseColumns));
if ($phase === '--audit') {
    echo json_encode(['lane' => $lane, 'phase' => 'audit', 'marker' => $marker,
        'actor_staff_goal_course_counts' => $initial,
        'formal_course_columns_present' => $optionalColumns,
        'owned_file_token_exists' => $hasToken,
        'business_http' => 0, 'business_dml' => 0]), PHP_EOL;
    exit(array_sum($initial) === 0 && !$hasToken ? 0 : 2);
}
if (array_sum($initial) !== 0 || $hasToken) {
    throw new RuntimeException('synthetic actor is not isolated before health fixture');
}
$courseSql = 'SELECT id,name FROM yoshop_drill_course'
    . ' WHERE store_id=10001 AND is_delete=0 ORDER BY id ASC'
    . ($lane === 'B' ? ' LIMIT 1' : ' FETCH FIRST 1 ROWS ONLY');
$course = $db->query($courseSql)->fetch(PDO::FETCH_ASSOC);
if (!is_array($course)) throw new RuntimeException('natural drill course absent');
$course = array_change_key_case($course, CASE_LOWER);
$http = static function (string $path) use ($port, $token): array {
    if (!in_array($path, ['/api/User/assets', '/api/health.Archive/info',
        '/api/user.UserCourse/schedule'], true)) {
        throw new RuntimeException('unsupported fixed health HTTP route');
    }
    $ctx = stream_context_create(['http' => [
        'method' => 'GET', 'header' => 'Access-Token: ' . $token . "\r\n"
            . "platform: MP-WEIXIN\r\nversion: 2.0.51\r\nenv-version: develop\r\n",
        'ignore_errors' => true, 'timeout' => 20, 'follow_location' => 0,
    ]]);
    $handle = @fopen('http://127.0.0.1:' . $port . $path . '?storeId=10001',
        'rb', false, $ctx);
    if (!is_resource($handle)) throw new RuntimeException('isolated health HTTP unavailable');
    $raw = stream_get_contents($handle, 2097153);
    fclose($handle);
    if (!is_string($raw) || strlen($raw) > 2097152) throw new RuntimeException('health HTTP response cap');
    $body = json_decode($raw, true);
    if (!is_array($body) || (int)($body['status'] ?? 0) !== 200) {
        throw new RuntimeException('health HTTP business status failed at ' . $path
            . ' status=' . (string)($body['status'] ?? 'nonjson')
            . ' message=' . substr((string)($body['message'] ?? ''), 0, 120));
    }
    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);
};
$insertStaff = $db->prepare('INSERT INTO yoshop_staff'
    . ' (user_id,name,store_id,height,weight,gender,birthday,phone,is_delete)'
    . " VALUES (?,?,10001,170,60,1,'1990-01-01','',0)");
$insertGoal = $db->prepare('INSERT INTO yoshop_user_weight_status'
    . ' (user_id,store_id,start_date,initial_weight,end_date,target_weight,'
    . ' status,adjust_speed,delete_flag,create_time,update_time)'
    . " VALUES (?,10001,?,'60.00',0,'60.00',1,0,0,?,?)");
$insertCourse = $db->prepare('INSERT INTO yoshop_user_course'
    . ' (store_id,user_id,user_weight_status_id,date,course_id,is_completed,'
    . ' energy_consumed,follow_count,total_energy,delete_flag)'
    . ' VALUES (10001,?,?,?,?,0,0.00,0,0.00,0)');
$ownStaff = static function () use ($one, $staffName, $userId) {
    return $one('SELECT id,user_id,name,height,weight FROM yoshop_staff'
        . ' WHERE user_id=? AND store_id=10001 AND name=?', [$userId, $staffName]);
};
$staffId = 0;
$goalId = 0;
$planId = 0;
$error = null;
$result = [];
try {
    $now = time();
    $today = date('Y-m-d');
    $start = strtotime($today);
    $db->beginTransaction();
    $insertStaff->execute([$userId, $staffName]);
    $staff = $ownStaff();
    if ($staff === null) throw new RuntimeException('owned staff insert missing');
    $staffId = (int)$staff['id'];
    $insertGoal->execute([$userId, $start, $now, $now]);
    $goal = $one('SELECT id,user_id,status,initial_weight,target_weight'
        . ' FROM yoshop_user_weight_status WHERE user_id=? AND store_id=10001'
        . ' AND start_date=? AND create_time=?', [$userId, $start, $now]);
    if ($goal === null) throw new RuntimeException('owned weight goal insert missing');
    $goalId = (int)$goal['id'];
    $insertCourse->execute([$userId, $goalId, $today, (int)$course['id']]);
    $plan = $one('SELECT id,user_id,user_weight_status_id,course_id,date'
        . ' FROM yoshop_user_course WHERE user_id=? AND store_id=10001'
        . ' AND user_weight_status_id=? AND course_id=?', [$userId, $goalId, $course['id']]);
    if ($plan === null) throw new RuntimeException('owned course plan insert missing');
    $planId = (int)$plan['id'];
    $db->commit();
    if ($initial !== [0, 0, 0]
        || array_map($count, ['staff', 'user_weight_status', 'user_course']) !== [1, 1, 1]) {
        throw new RuntimeException('owned three-table fixture count mismatch');
    }
    $ormGoal = (new \app\api\model\user\UserWeightStatus())->where([
        'user_id' => $userId, 'store_id' => 10001, 'delete_flag' => 0,
    ])->find();
    if (empty($ormGoal) || (int)$ormGoal['id'] !== $goalId) {
        throw new RuntimeException('HTTP ORM cannot see owned goal before request');
    }
    if (!\think\facade\Cache::set($token, ['user' => $ormUser, 'store_id' => 10001,
        'is_login' => true, 'password_version' => $version, 'login_type' => 'sms'], 300)
        || !\think\facade\Cache::has($token)) {
        throw new RuntimeException('short isolated File token unavailable');
    }
    if (!is_array($http('/api/User/assets')['data'] ?? null)) {
        throw new RuntimeException('short token preflight failed');
    }
    $archive = $http('/api/health.Archive/info')['data'] ?? null;
    if (!is_array($archive) || !is_array($archive['basic'] ?? null)
        || (string)($archive['basic']['nick_name'] ?? '') !== $actorName
        || !is_array($archive['weight_status'] ?? null)
        || (int)($archive['weight_status']['id'] ?? 0) !== $goalId
        || !is_array($archive['metrics'] ?? null)) {
        throw new RuntimeException('health archive differs from own user/goal source');
    }
    $schedule = $http('/api/user.UserCourse/schedule')['data'] ?? null;
    if (!is_array($schedule) || !is_array($schedule['weight_status'] ?? null)
        || (int)($schedule['weight_status']['id'] ?? 0) !== $goalId
        || !is_array($schedule['weeks'] ?? null) || !$schedule['weeks']) {
        throw new RuntimeException('course schedule lacks owned goal/week');
    }
    $containsCourse = static function ($value) use (&$containsCourse, $plan): bool {
        if (!is_array($value)) return false;
        if (isset($value['course_id']) && (int)$value['course_id'] === (int)$plan['course_id']) {
            return true;
        }
        foreach ($value as $child) if ($containsCourse($child)) return true;
        return false;
    };
    if (!$containsCourse($schedule['weeks'])) {
        throw new RuntimeException('owned course row absent from four-week schedule');
    }
    if (array_map($count, ['staff', 'user_weight_status', 'user_course']) !== [1, 1, 1]) {
        throw new RuntimeException('health reads unexpectedly wrote another actor row');
    }
    $result = ['http_business_ok' => 2,
        'archive_own_user_goal_source_equal' => true,
        'schedule_own_goal_course_source_equal' => true,
        'formal_course_columns_present' => $optionalColumns,
        'archive_recursive_type_sha256' => hash('sha256', json_encode($typeGraph($archive))),
        'schedule_recursive_type_sha256' => hash('sha256', json_encode($typeGraph($schedule)))];
} catch (Throwable $caught) {
    $error = $caught;
} finally {
    if ($db->inTransaction()) $db->rollBack();
    if ($planId > 0) {
        $stmt = $db->prepare('DELETE FROM yoshop_user_course'
            . ' WHERE id=? AND user_id=? AND user_weight_status_id=? AND course_id=?');
        $stmt->execute([$planId, $userId, $goalId, $course['id']]);
    }
    if ($goalId > 0) {
        $stmt = $db->prepare('DELETE FROM yoshop_user_weight_status'
            . ' WHERE id=? AND user_id=? AND store_id=10001');
        $stmt->execute([$goalId, $userId]);
    }
    if ($staffId > 0) {
        $stmt = $db->prepare('DELETE FROM yoshop_staff'
            . ' WHERE id=? AND user_id=? AND name=? AND store_id=10001');
        $stmt->execute([$staffId, $userId, $staffName]);
    }
    \think\facade\Cache::delete($token);
}
if (array_map($count, ['staff', 'user_weight_status', 'user_course']) !== [0, 0, 0]
    || \think\facade\Cache::has($token)) {
    throw new RuntimeException('owned health fixture/token cleanup not zero');
}
if ($error !== null) throw $error;
echo json_encode(['lane' => $lane, 'marker' => $marker] + $result + [
    'owned_staff_goal_course_token_cleanup_zero' => true,
    'business_dml' => 'three-owned-fixture-inserts-and-exact-deletes-only',
    'third_party_device_payment' => 0,
], JSON_UNESCAPED_SLASHES), PHP_EOL;
