<?php
declare(strict_types=1);

// Isolated synthetic 28-day health schedule: one stored suggestion must appear.
// Default invocation never connects or writes.
if (getenv('CDX_READ8_HEALTH') !== 'APPROVED_SYNTHETIC_SCHEDULE_V1') {
    echo json_encode(['status' => 'default_deny', 'db' => 0, 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(78);
}
$target = (string)getenv('CDX_READ8_TARGET');
$expectedSha = (string)getenv('CDX_READ8_SHA');
if (!in_array($target, ['A', 'B', 'C'], true)
    || strlen($expectedSha) !== 64
    || !hash_equals($expectedSha, (string)hash_file('sha256', __FILE__))) {
    throw new RuntimeException('Explicit target and exact script SHA required');
}
putenv('CDX_READ2_TARGET=' . $target);
putenv('CDX_READ2_SCHEMA_PREFLIGHT=1');
require $target === 'C'
    ? '/tmp/zhct-ai-read2-VgKO4CDY/read2_schema_preflight.php'
    : __DIR__ . '/../parallel-ai-next-read-safe4/read2_schema_preflight.php';
$identityFields = ['POC_AI_HOST', 'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER',
    'POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER'];
$identity = array_map(static function (string $key) use ($config): string {
    return (string)($config[$key] ?? '');
}, $identityFields);
$approved = $target === 'C'
    ? 'da9d709b8a8a1163c08e68efcbb69edb85d9d8b365f7b7705a0a2141aa4860bd'
    : 'd908a48732b1a4874c5d8427ab60186eb8272aa4e0c5af34716eadafee482c88';
if (!hash_equals($approved, hash('sha256', json_encode($identity, JSON_UNESCAPED_SLASHES)))
    || ($target === 'C'
        && !hash_equals('bcf83be7714eaec2734fb7dccddabc18c83c34b4201ee567f5044fb4bd54f526',
            hash('sha256', php_uname('n'))))) {
    throw new RuntimeException('Development or DM host identity changed');
}
$ai->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$root = ['A' => '/private/tmp/zhct-http-ai-macos.zjaU8h',
    'B' => '/private/tmp/zhct-http-ai-macos.x2CCZX',
    'C' => '/tmp/zhct-ai-http-20260929.3XDj3w'][$target];
$port = ['A' => 18105, 'B' => 18101, 'C' => 18102][$target];
$cachePath = realpath($root . '/runtime/cache');
if (realpath($root) !== $root || $cachePath !== $root . '/runtime/cache'
    || (((int)fileperms($root) & 0077) !== 0)
    || !hash_equals('ccf1c56eb4178f3eaad8775e26a27742b2cacf662b46fb058cbb91d23783b8c6',
        (string)hash_file('sha256', $root . '/config/cache.php'))
    || is_file($root . '/.env') || is_file($root . '/.env.poc')) {
    throw new RuntimeException('Private File cache boundary changed');
}
$httpEnvironment = $target === 'C' ? ($raw ?? '') : ($cmdline ?? '');
if (!is_string($httpEnvironment)
    || preg_match('/(?:^|[\s\x00])(?:CACHE_DRIVER|cache\.driver)=/i', $httpEnvironment) === 1) {
    throw new RuntimeException('HTTP cache driver override detected');
}
foreach ($config as $key => $value) {
    if (strpos($key, 'POC_') === 0) putenv($key . '=' . $value);
}
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require $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')) !== $cachePath) {
    throw new RuntimeException('Effective File cache changed');
}

$table = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YOSHOP_' . $name : 'yoshop_' . strtolower($name);
};
$tables = ['user' => $table('USER'), 'staff' => $table('STAFF'),
    'goal' => $table('USER_WEIGHT_STATUS'), 'suggestion' => $table('HEALTH_SUGGESTION'),
    'work' => $table('HEALTH_SUGGESTION_WORK')];
foreach ($tables as $name => $tableName) {
    $ai->query('SELECT 1 FROM ' . $tableName . ' WHERE 1=0');
}
$rows = static function (string $sql, array $bind = []) use ($ai): array {
    $statement = $ai->prepare($sql);
    $statement->execute($bind);
    return $statement->fetchAll(PDO::FETCH_ASSOC);
};
$count = static function (string $sql, array $bind = []) use ($rows): int {
    $result = $rows($sql, $bind);
    return count($result) === 1 ? (int)array_values($result[0])[0] : -1;
};
$fingerprint = static function (string $name, int $id) use ($rows, $tables): ?string {
    $pk = $name === 'user' ? 'USER_ID' : 'ID';
    $result = $rows('SELECT * FROM ' . $tables[$name]
        . ' WHERE ' . $pk . '=? AND STORE_ID=10001', [$id]);
    if (count($result) !== 1) return null;
    ksort($result[0]);
    return hash('sha256', json_encode($result[0], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
};
$marker = 'CDXREAD8H' . strtoupper(bin2hex(random_bytes(5)));
$token = 'CDXREAD8T' . strtoupper(bin2hex(random_bytes(18)));
$day = date('Y-m-d', strtotime('+1 day'));
$dayEpoch = strtotime($day);
$now = time();
$nowText = date('Y-m-d H:i:s', $now);
if ($count('SELECT COUNT(*) FROM ' . $tables['user']
    . ' WHERE NICK_NAME=? AND STORE_ID=10001', [$marker]) !== 0
    || $count('SELECT COUNT(*) FROM ' . $tables['staff']
        . ' WHERE NAME=? AND STORE_ID=10001', [$marker]) !== 0) {
    throw new RuntimeException('Random marker collision');
}
$journalDir = sys_get_temp_dir() . '/cdx-ai-read8-' . strtolower(bin2hex(random_bytes(8)));
if (!mkdir($journalDir, 0700) || !chmod($journalDir, 0700)) {
    throw new RuntimeException('Private fixture journal unavailable');
}
$journal = $journalDir . '/token.json';
$tokenIssuedAt = time();
$state = ['target' => $target, 'role' => 'health_schedule', 'root' => $root,
    'token' => $token, 'marker' => $marker, 'created_at' => $tokenIssuedAt,
    'expires_at' => $tokenIssuedAt + 300, 'ids' => [], 'phase' => 'before_insert'];
$save = static function (array $updates) use (&$state, $journalDir, $journal): void {
    $state = array_replace_recursive($state, $updates);
    $temp = tempnam($journalDir, 'next-');
    if (!is_string($temp) || !chmod($temp, 0600)) {
        throw new RuntimeException('Private journal temp unavailable');
    }
    $json = json_encode($state, JSON_UNESCAPED_SLASHES);
    if (!is_string($json) || file_put_contents($temp, $json, LOCK_EX) !== strlen($json)
        || !rename($temp, $journal)) {
        throw new RuntimeException('Private journal atomic update failed');
    }
};
$save([]);
$ids = [];
$snapshots = [];
$result = ['target' => $target, 'date_window_days' => 28,
    'http' => 0, 'business_status' => null, 'source_day_seen' => false,
    'journal' => $journal, 'cleanup' => 'not_started'];
$tokenSaved = false;
$requestStarted = false;
$httpCompleted = false;
try {
    $ai->prepare('INSERT INTO ' . $tables['user']
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,0,0,0,?)')
        ->execute(['', $marker, $now]);
    $userRows = $rows('SELECT USER_ID FROM ' . $tables['user']
        . ' WHERE NICK_NAME=? AND STORE_ID=10001', [$marker]);
    if (count($userRows) !== 1) throw new RuntimeException('Synthetic user insert not unique');
    $ids['user'] = (int)$userRows[0]['user_id'];
    $save(['ids' => $ids, 'phase' => 'user_inserted']);
    $snapshots['user'] = $fingerprint('user', $ids['user']);
    if ($snapshots['user'] === null) throw new RuntimeException('Synthetic user snapshot missing');

    $ai->prepare('INSERT INTO ' . $tables['staff']
        . ' (NAME,USER_ID,STORE_ID,HEIGHT,WEIGHT,GENDER,BIRTHDAY,PHONE,IS_DELETE)'
        . " VALUES (?,?,10001,170,60,1,'1990-01-01','',0)")
        ->execute([$marker, $ids['user']]);
    $staffRows = $rows('SELECT ID FROM ' . $tables['staff']
        . ' WHERE USER_ID=? AND NAME=? AND STORE_ID=10001', [$ids['user'], $marker]);
    if (count($staffRows) !== 1) throw new RuntimeException('Synthetic staff insert not unique');
    $ids['staff'] = (int)$staffRows[0]['id'];
    $save(['ids' => $ids, 'phase' => 'staff_inserted']);
    $snapshots['staff'] = $fingerprint('staff', $ids['staff']);
    if ($snapshots['staff'] === null) throw new RuntimeException('Synthetic staff snapshot missing');

    $ai->prepare('INSERT INTO ' . $tables['goal']
        . ' (USER_ID,STORE_ID,START_DATE,INITIAL_WEIGHT,END_DATE,TARGET_WEIGHT,'
        . 'STATUS,ADJUST_SPEED,DELETE_FLAG,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,10001,?,?,0,?,1,1,0,?,?)')
        ->execute([$ids['user'], $dayEpoch, '60.00', '60.00', $now, $now]);
    $goalRows = $rows('SELECT ID FROM ' . $tables['goal']
        . ' WHERE USER_ID=? AND START_DATE=? AND STORE_ID=10001', [$ids['user'], $dayEpoch]);
    if (count($goalRows) !== 1) throw new RuntimeException('Synthetic goal insert not unique');
    $ids['goal'] = (int)$goalRows[0]['id'];
    $save(['ids' => $ids, 'phase' => 'goal_inserted']);
    $snapshots['goal'] = $fingerprint('goal', $ids['goal']);
    if ($snapshots['goal'] === null) throw new RuntimeException('Synthetic goal snapshot missing');

    $ai->prepare('INSERT INTO ' . $tables['suggestion']
        . ' (STAFF_ID,DATE,TYPE,K1,K2,K3,STORE_ID,IS_DELETE,CREATE_BY,CREATE_TIME,UPDATE_TIME)'
        . ' VALUES (?,?,1,?,\'\',\'\',10001,0,?,?,?)')
        ->execute([$ids['staff'], $day, $marker, $marker, $nowText, $nowText]);
    $suggestionRows = $rows('SELECT ID FROM ' . $tables['suggestion']
        . ' WHERE STAFF_ID=? AND DATE=? AND K1=? AND STORE_ID=10001',
        [$ids['staff'], $day, $marker]);
    if (count($suggestionRows) !== 1) throw new RuntimeException('Synthetic suggestion insert not unique');
    $ids['suggestion'] = (int)$suggestionRows[0]['id'];
    $save(['ids' => $ids, 'phase' => 'suggestion_inserted']);
    $snapshots['suggestion'] = $fingerprint('suggestion', $ids['suggestion']);
    if ($snapshots['suggestion'] === null) throw new RuntimeException('Synthetic suggestion snapshot missing');
    if ($count('SELECT COUNT(*) FROM ' . $tables['work']
        . ' WHERE STAFF_ID=? AND STORE_ID=10001', [$ids['staff']]) !== 0) {
        throw new RuntimeException('Unexpected workflow task before schedule read');
    }

    $cacheSaved = \think\facade\Cache::set($token, [
        'user' => ['user_id' => $ids['user']],
        'password_version' => 0, 'login_type' => 'thirdparty',
    ], 300);
    $tokenSaved = $cacheSaved === true;
    if (!$tokenSaved) throw new RuntimeException('Short isolated token unavailable');
    $save(['phase' => 'token_saved']);
    $context = stream_context_create(['http' => [
        'method' => 'GET', 'timeout' => 90, 'ignore_errors' => true,
        'header' => 'Access-Token: ' . $token . "\r\n"
            . "platform: MP-WEIXIN\r\nversion: 2.0.51\r\nenv-version: develop\r\n",
    ]]);
    $started = microtime(true);
    $requestStarted = true;
    $rawBody = file_get_contents('http://127.0.0.1:' . $port
        . '/api/health.Suggestion/schedule?storeId=10001', false, $context);
    $result['elapsed_ms'] = (int)round((microtime(true) - $started) * 1000);
    $result['http'] = isset($http_response_header[0])
        && preg_match('/HTTP\/\S+\s+(\d+)/', $http_response_header[0], $matches)
        ? (int)$matches[1] : 0;
    $httpCompleted = true;
    $body = is_string($rawBody) ? json_decode($rawBody, true) : null;
    $result['business_status'] = is_array($body) ? ($body['status'] ?? null) : null;
    $data = is_array($body) ? ($body['data'] ?? null) : null;
    $weeks = is_array($data) ? ($data['weeks'] ?? null) : null;
    if ($result['http'] !== 200 || $result['business_status'] !== 200
        || !is_array($weeks) || count($weeks) !== 4
        || !is_array($data['weight_status'] ?? null)
        || (int)($data['weight_status']['id'] ?? 0) !== $ids['goal']) {
        throw new RuntimeException('Health schedule HTTP contract failed');
    }
    $allDays = [];
    foreach ($weeks as $week) {
        if (!is_array($week['days'] ?? null) || count($week['days']) !== 7) {
            throw new RuntimeException('Health schedule week length mismatch');
        }
        foreach ($week['days'] as $entry) $allDays[] = $entry;
    }
    if (count($allDays) !== 28 || ($allDays[0]['date'] ?? null) !== $day
        || ($allDays[0]['suggest'] ?? null) !== $marker) {
        throw new RuntimeException('Stored first-day suggestion absent from 28-day output');
    }
    $result['source_day_seen'] = true;
    $result['response_week_count'] = 4;
    $result['response_day_count'] = 28;
    $result['first_day_type_map'] = array_map('gettype', array_intersect_key($allDays[0],
        array_fill_keys(['status', 'date', 'suggest', 'diet', 'sport', 'tips', 'weekday'], true)));
    if ($count('SELECT COUNT(*) FROM ' . $tables['work']
        . ' WHERE STAFF_ID=? AND STORE_ID=10001', [$ids['staff']]) !== 0) {
        throw new RuntimeException('Read-only schedule unexpectedly created workflow task');
    }
    $result['workflow_task_still_zero'] = true;
} catch (Throwable $error) {
    $result['error_class'] = get_class($error);
} finally {
    try {
        \think\facade\Cache::delete($token);
        $result['token_absent_first_process'] = !\think\facade\Cache::has($token);
    } catch (Throwable $error) {
        $result['token_absent_first_process'] = false;
    }
    $safeCleanup = (!$requestStarted || $httpCompleted)
        && $result['token_absent_first_process'];
    $cleanup = [];
    if ($safeCleanup) {
        try {
            $owners = $rows('SELECT USER_ID FROM ' . $tables['user']
                . ' WHERE NICK_NAME=? AND STORE_ID=10001', [$marker]);
            if (count($owners) > 1) throw new RuntimeException('Ambiguous synthetic user');
            if (count($owners) === 1) {
                $found = (int)$owners[0]['user_id'];
                if (isset($ids['user']) && $ids['user'] !== $found) {
                    throw new RuntimeException('Synthetic user ID changed');
                }
                $ids['user'] = $found;
            }
            $staffRows = $rows('SELECT ID,USER_ID FROM ' . $tables['staff']
                . ' WHERE NAME=? AND STORE_ID=10001', [$marker]);
            if (count($staffRows) > 1) throw new RuntimeException('Ambiguous synthetic staff');
            if (count($staffRows) === 1) {
                $found = (int)$staffRows[0]['id'];
                if (!isset($ids['user']) || (int)$staffRows[0]['user_id'] !== $ids['user']
                    || (isset($ids['staff']) && $ids['staff'] !== $found)) {
                    throw new RuntimeException('Synthetic staff ownership changed');
                }
                $ids['staff'] = $found;
            }
            $goalRows = isset($ids['user']) ? $rows('SELECT ID FROM ' . $tables['goal']
                . ' WHERE USER_ID=? AND START_DATE=? AND STORE_ID=10001', [$ids['user'], $dayEpoch]) : [];
            if (count($goalRows) > 1) throw new RuntimeException('Ambiguous synthetic goal');
            if (count($goalRows) === 1) {
                $found = (int)$goalRows[0]['id'];
                if (isset($ids['goal']) && $ids['goal'] !== $found) {
                    throw new RuntimeException('Synthetic goal ID changed');
                }
                $ids['goal'] = $found;
            }
            $suggestionRows = $rows('SELECT ID,STAFF_ID FROM ' . $tables['suggestion']
                . ' WHERE K1=? AND DATE=? AND STORE_ID=10001', [$marker, $day]);
            if (count($suggestionRows) > 1) throw new RuntimeException('Ambiguous synthetic suggestion');
            if (count($suggestionRows) === 1) {
                $found = (int)$suggestionRows[0]['id'];
                if (!isset($ids['staff']) || (int)$suggestionRows[0]['staff_id'] !== $ids['staff']
                    || (isset($ids['suggestion']) && $ids['suggestion'] !== $found)) {
                    throw new RuntimeException('Synthetic suggestion ownership changed');
                }
                $ids['suggestion'] = $found;
            }
            $owned = (!isset($ids['user']) || count($owners) === 1)
                && (!isset($ids['staff']) || count($staffRows) === 1)
                && (!isset($ids['goal']) || count($goalRows) === 1)
                && (!isset($ids['suggestion']) || count($suggestionRows) === 1)
                && (!isset($ids['staff']) || $count('SELECT COUNT(*) FROM ' . $tables['work']
                    . ' WHERE STAFF_ID=? AND STORE_ID=10001', [$ids['staff']]) === 0);
            foreach ($snapshots as $name => $beforeHash) {
                $owned = $owned && isset($ids[$name])
                    && $fingerprint($name, $ids[$name]) === $beforeHash;
            }
            $result['source_hash_unchanged'] = $owned;
            if (!$owned) throw new RuntimeException('Exact synthetic ownership changed');
            foreach (['suggestion' => 'ID', 'goal' => 'ID',
                'staff' => 'ID', 'user' => 'USER_ID'] as $name => $pk) {
                if (!isset($ids[$name])) continue;
                $id = $ids[$name];
                $ownerColumn = $name === 'suggestion' ? 'K1'
                    : ($name === 'goal' ? 'USER_ID' : ($name === 'staff' ? 'NAME' : 'NICK_NAME'));
                $ownerValue = $name === 'goal' ? $ids['user'] : $marker;
                $stmt = $ai->prepare('DELETE FROM ' . $tables[$name]
                    . ' WHERE ' . $pk . '=? AND ' . $ownerColumn . '=? AND STORE_ID=10001');
                $stmt->execute([$id, $ownerValue]);
                $cleanup[$name] = $stmt->rowCount();
                if ($cleanup[$name] !== 1) throw new RuntimeException('Exact row cleanup count mismatch');
            }
            $result['cleanup'] = array_sum($cleanup) === count($ids)
                ? 'first_process_exact_zero' : 'failed';
        } catch (Throwable $error) {
            $result['cleanup'] = 'deferred_ownership_or_delete_failure';
        }
    } else {
        $result['cleanup'] = 'deferred_inflight_or_partial_fixture';
    }
    $save(['phase' => $result['cleanup'], 'ids' => $ids]);
}
$result['status'] = !isset($result['error_class'])
    && $result['cleanup'] === 'first_process_exact_zero'
    ? 'pending_independent_token_and_rows_zero' : 'failed_or_deferred';
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
exit($result['status'] === 'pending_independent_token_and_rows_zero' ? 0 : 3);
