<?php
declare(strict_types=1);

// B/C only. This creates one synthetic AI user and cart row, calls two GETs,
// and never submits an order. Runtime credentials come only from the isolated
// test process environment. A private journal remains until independent zero
// verification; an uncertain HTTP transport intentionally leaves the fixture.
$root = (string)getenv('POC_TARGET_ROOT');
$url = rtrim((string)getenv('POC_BASE_URL'), '/');
$dialect = (string)getenv('POC_DIALECT');
$journal = (string)getenv('POC_PRIVATE_JOURNAL');
$allowWrite = getenv('POC_ALLOW_CART_DEEP_WRITE') === '1';
if (!in_array($dialect, ['mysql', 'dm'], true) || $root === '' || $url === ''
    || $journal === '' || strpos($url, 'http://127.0.0.1:') !== 0
    || is_file($journal)) {
    throw new RuntimeException('Isolated runtime, unique journal and loopback URL required');
}
umask(0077);
if ($dialect === 'mysql') {
    $localPath = (string)getenv('POC_MYSQL_LOCAL_ENV');
    $local = parse_ini_file($localPath, true, INI_SCANNER_RAW);
    if (!is_array($local) || !isset($local['DATABASE'], $local['DATABASE_ZHCT'])) {
        throw new RuntimeException('Development .env.local is required');
    }
    foreach (['DATABASE' => 'AI_', 'DATABASE_ZHCT' => ''] as $section => $prefix) {
        foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
            'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $source => $target) {
            putenv('POC_' . $prefix . $target . '=' . (string)$local[$section][$source]);
        }
    }
}
foreach (['HOST', 'PORT', 'SCHEMA', 'USER', 'PASSWORD', 'AI_HOST', 'AI_PORT',
    'AI_SCHEMA', 'AI_USER', 'AI_PASSWORD'] as $part) {
    if (getenv('POC_' . $part) === false) {
        throw new RuntimeException('Database connection environment incomplete');
    }
}
if ($dialect === 'dm' && ((string)getenv('POC_PORT') !== '5237'
    || (string)getenv('POC_AI_PORT') !== '5238'
    || strtoupper((string)getenv('POC_SCHEMA')) !== 'ZHCT_APP'
    || strtoupper((string)getenv('POC_AI_SCHEMA')) !== 'AIZHCT_APP')) {
    throw new RuntimeException('Nonproduction DM business-schema identity mismatch');
}
$boundary = json_decode((string)file_get_contents($url . '/__poc_boundary'), true);
if (!is_array($boundary) || empty($boundary['external_stubs'])) {
    throw new RuntimeException('External substitute boundary is not active');
}
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();

$dsn = static function (string $prefix) use ($dialect): string {
    $host = (string)getenv('POC_' . $prefix . 'HOST');
    $port = (string)getenv('POC_' . $prefix . 'PORT');
    $schema = (string)getenv('POC_' . $prefix . 'SCHEMA');
    return $dialect === 'dm' ? "dm:host=$host;port=$port;schema=$schema"
        : "mysql:host=$host;port=$port;dbname=$schema;charset=utf8mb4";
};
$ai = new PDO($dsn('AI_'), getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$store = new PDO($dsn(''), getenv('POC_USER'), getenv('POC_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
if ($dialect === 'mysql') {
    $version = (string)$ai->query('SELECT VERSION()')->fetchColumn();
    if (strpos($version, '5.6.') !== 0
        || strcasecmp((string)$ai->query('SELECT DATABASE()')->fetchColumn(),
            (string)getenv('POC_AI_SCHEMA')) !== 0
        || strcasecmp((string)$store->query('SELECT DATABASE()')->fetchColumn(),
            (string)getenv('POC_SCHEMA')) !== 0) {
        throw new RuntimeException('Development MySQL 5.6 identity mismatch');
    }
}
$table = static function (string $prefix, string $name) use ($dialect): string {
    $value = $prefix . $name;
    return $dialect === 'dm' ? strtoupper($value) : $value;
};
$aUser = $table('yoshop_', 'user');
$aCart = $table('yoshop_', 'meal_cart');
$aDish = $table('yoshop_', 'dishes');
$sRestaurant = $table('ydy_', 'restaurants');
$sDish = $table('ydy_', 'dishes');
$limit = $dialect === 'dm' ? 'FETCH FIRST 50 ROWS ONLY' : 'LIMIT 50';

// Existing natural source rows are read-only; avoid visible staff/order/recipe writes.
$restaurantId = 0;
foreach ($store->query("SELECT ID,BOOK_RULES FROM $sRestaurant WHERE STALL_TYPE=2"
    . ' AND IS_DELETE=2 AND STATUS=1 ORDER BY ID', PDO::FETCH_ASSOC) as $row) {
    $row = array_change_key_case($row);
    $rules = json_decode((string)$row['book_rules'], true);
    if ((int)($rules['rule'] ?? 0) !== 3) {
        $restaurantId = (int)$row['id'];
        break;
    }
}
$aiDish = null;
$storeDish = null;
$candidates = $store->query("SELECT ID,AI_DISHES_ID,PRICE,UUID,ORDER_NUM FROM $sDish"
    . " WHERE IS_DELETE=0 AND AI_DISHES_ID>0 ORDER BY AI_DISHES_ID $limit",
    PDO::FETCH_ASSOC);
$lookup = $ai->prepare("SELECT ID,WEIGHT,ENERGY,CARBOHYDRATE,PROTEIN,FAT FROM $aDish"
    . ' WHERE ID=? AND IS_DELETE=0');
foreach ($candidates as $row) {
    $row = array_change_key_case($row);
    $lookup->execute([(int)$row['ai_dishes_id']]);
    $found = $lookup->fetch(PDO::FETCH_ASSOC);
    if (is_array($found)) {
        $storeDish = $row;
        $aiDish = array_change_key_case($found);
        break;
    }
}
if ($restaurantId <= 0 || $aiDish === null || $storeDish === null) {
    throw new RuntimeException('Read-only restaurant / mapped dish prerequisite missing');
}
$nutrition = ['weight', 'energy', 'carbohydrate', 'protein', 'fat'];
foreach ($nutrition as $name) {
    if (!is_numeric($aiDish[$name] ?? null)) {
        throw new RuntimeException('Nutrition source is not numeric');
    }
}
$aiDishSourceHash = hash('sha256', json_encode($aiDish));
$storeDishSourceHash = hash('sha256', json_encode($storeDish));
$preflight = [
    'dialect' => $dialect,
    'ai_schema' => (string)getenv('POC_AI_SCHEMA'),
    'store_schema' => (string)getenv('POC_SCHEMA'),
    'restaurant_id' => $restaurantId,
    'dish_id' => (int)$aiDish['id'],
    'loaded_service_sha' => hash_file('sha256',
        (new ReflectionClass(\app\api\service\dishes\Dishes::class))->getFileName()),
    'loaded_cart_sha' => hash_file('sha256',
        (new ReflectionClass(\app\api\service\MealCart::class))->getFileName()),
    'loaded_checkout_sha' => hash_file('sha256',
        (new ReflectionClass(\app\api\controller\MealCheckout::class))->getFileName()),
    'ai_source_types' => array_map(static function (string $key) use ($aiDish): string {
        return get_debug_type($aiDish[$key]);
    }, $nutrition),
];
$ownedUsers = $ai->query("SELECT COUNT(*) FROM $aUser WHERE NICK_NAME LIKE 'CDXMCDEEP%'")
    ->fetchColumn();
$preflight['prior_owned_user_count'] = (int)$ownedUsers;
if ((int)$ownedUsers !== 0) {
    throw new RuntimeException('Owned-marker prefix has residual users');
}
if ($preflight['loaded_service_sha'] !== '88aa6cb2cadf4afa428bced8b57544ac1a4a2b8fbe32230232cce67c8c46d005'
    || $preflight['loaded_cart_sha'] !== 'd48663b5bb73fcc4f1561b8b44bb0a071f27b00dbcf96df51be8bd953a14ef33'
    || $preflight['loaded_checkout_sha'] !== '50835e8522b18c43eca711ee648c49592e30db2d140a7d10923da66221fd6d10') {
    throw new RuntimeException('Actual loaded compatibility source SHA drifted');
}
if (!$allowWrite) {
    echo json_encode(['stage' => 'preflight-only', 'result' => $preflight], JSON_UNESCAPED_UNICODE) . PHP_EOL;
    exit(0);
}
$marker = 'CDXMCDEEP' . strtoupper(bin2hex(random_bytes(5)));
$token = $marker . 'TOKEN' . strtoupper(bin2hex(random_bytes(12)));
$userId = 0;
$cartId = 0;
$httpUncertain = false;
$journalData = [
    'dialect' => $dialect, 'marker' => $marker, 'token' => $token,
    'user_id' => 0, 'cart_id' => 0, 'stage' => 'started',
];
$saveJournal = static function () use ($journal, &$journalData): void {
    if (file_put_contents($journal, json_encode($journalData, JSON_UNESCAPED_UNICODE), LOCK_EX) === false) {
        throw new RuntimeException('Private journal write failed');
    }
    chmod($journal, 0600);
};
$saveJournal();
$request = static function (string $path) use ($url, $token, &$httpUncertain): array {
    $httpUncertain = true;
    $headers = "platform: POC\r\nAccess-Token: $token\r\n";
    $opts = ['method' => 'GET', 'header' => $headers, 'timeout' => 20,
        'ignore_errors' => true];
    $raw = file_get_contents($url . $path, false, stream_context_create(['http' => $opts]));
    $json = is_string($raw) ? json_decode($raw, true) : null;
    if (!is_array($json)) {
        throw new RuntimeException('GET transport or JSON unknown');
    }
    $httpUncertain = false;
    return $json;
};
$zero = static function (PDO $pdo, string $sql, array $params): int {
    $q = $pdo->prepare($sql);
    $q->execute($params);
    return (int)$q->fetchColumn();
};
try {
    $insert = $ai->prepare("INSERT INTO $aUser"
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,?,?,0,?)');
    $insert->execute(['', $marker, '0.00', '0.00', time()]);
    $userId = (int)$ai->lastInsertId();
    $journalData['user_id'] = $userId;
    $journalData['stage'] = 'user-created';
    $saveJournal();
    if ($userId <= 0) throw new RuntimeException('Synthetic user ID missing');
    $insertCart = $ai->prepare("INSERT INTO $aCart"
        . ' (DISHES_ID,DISHES_NUM,RESTAURANT_ID,MEAL_DATE,MEAL_TIMES,USER_ID,'
        . ' STORE_ID,IS_DELETE,CREATE_TIME,UPDATE_TIME) VALUES (?,?,?,?,?,?,?,?,?,?)');
    $insertCart->execute([(int)$aiDish['id'], 2, $restaurantId, '2099-01-01', 1,
        $userId, 10001, 0, time(), time()]);
    $cartId = (int)$ai->lastInsertId();
    $journalData['cart_id'] = $cartId;
    $journalData['stage'] = 'cart-created';
    $saveJournal();
    if ($cartId <= 0 || $zero($ai, "SELECT COUNT(*) FROM $aCart WHERE ID=? AND USER_ID=?",
        [$cartId, $userId]) !== 1) {
        throw new RuntimeException('Synthetic cart readback missing');
    }
    \think\facade\Cache::set($token, [
        'user' => ['user_id' => $userId], 'password_version' => 0,
        'login_type' => 'thirdparty',
    ], 300);
    $journalData['stage'] = 'token-created';
    $saveJournal();
    $query = '?storeId=10001&restaurant_id=' . $restaurantId
        . '&meal_date=2099-01-01&meal_times=1';
    $list = $request('/api/mealCart/list' . $query);
    $order = $request('/api/mealCheckout/order' . $query);
    $listItem = $list['data']['list'][0] ?? null;
    $orderItem = $order['data']['detail'][0] ?? null;
    if ((int)($list['status'] ?? 0) !== 200
        || (int)($order['status'] ?? 0) !== 200
        || !is_array($listItem) || !is_array($orderItem)
        || (int)($listItem['cart_id'] ?? 0) !== $cartId
        || (int)($orderItem['cart_id'] ?? 0) !== $cartId
        || (int)($listItem['quantity'] ?? 0) !== 2
        || (int)($orderItem['quantity'] ?? 0) !== 2) {
        throw new RuntimeException('Nonempty list or checkout preview readback mismatch: '
            . json_encode(['list_status' => $list['status'] ?? null,
                'order_status' => $order['status'] ?? null,
                'order_message' => $order['message'] ?? null], JSON_UNESCAPED_UNICODE));
    }
    $types = [];
    foreach (['list' => $listItem, 'checkout_detail' => $orderItem] as $route => $item) {
        $types[$route] = [];
        foreach ($nutrition as $field) {
            if (!array_key_exists($field, $item) || !is_numeric($item[$field])
                || sprintf('%.2f', (float)$item[$field]) !== sprintf('%.2f', (float)$aiDish[$field])) {
                throw new RuntimeException("$route.$field source-value mismatch");
            }
            if (getenv('POC_EXPECT_FIXED_STRING') === '1'
                && (!is_string($item[$field])
                    || !preg_match('/^-?[0-9]+\.[0-9]{2}$/', $item[$field]))) {
                throw new RuntimeException("$route.$field fixed-scale JSON contract mismatch");
            }
            $types[$route][$field] = get_debug_type($item[$field]);
        }
        if (!is_numeric($item['price'] ?? null)
            || sprintf('%.2f', (float)$item['price']) !== sprintf('%.2f', (float)$storeDish['price'])) {
            throw new RuntimeException("$route.price source-value mismatch");
        }
        $types[$route]['price'] = get_debug_type($item['price']);
        $types[$route]['quantity'] = get_debug_type($item['quantity']);
        $types[$route]['cart_id'] = get_debug_type($item['cart_id']);
    }
    $lookup->execute([(int)$aiDish['id']]);
    $aiDishAfter = $lookup->fetch(PDO::FETCH_ASSOC);
    $storeAfterQuery = $store->prepare("SELECT ID,AI_DISHES_ID,PRICE,UUID,ORDER_NUM"
        . " FROM $sDish WHERE ID=?");
    $storeAfterQuery->execute([(int)$storeDish['id']]);
    $storeDishAfter = $storeAfterQuery->fetch(PDO::FETCH_ASSOC);
    if (!is_array($aiDishAfter) || !is_array($storeDishAfter)
        || hash('sha256', json_encode(array_change_key_case($aiDishAfter))) !== $aiDishSourceHash
        || hash('sha256', json_encode(array_change_key_case($storeDishAfter))) !== $storeDishSourceHash) {
        throw new RuntimeException('Natural source dish changed across read-only HTTP');
    }
    $journalData['stage'] = 'http-readback-complete';
    $saveJournal();
    echo json_encode(['stage' => 'http-verified', 'preflight' => $preflight,
        'response_types' => $types,
        'natural_ai_dish_hash_unchanged' => true,
        'natural_store_dish_hash_unchanged' => true,
        'list_keys' => array_keys($listItem),
        'checkout_keys' => array_keys($orderItem)], JSON_UNESCAPED_UNICODE) . PHP_EOL;
} finally {
    if ($httpUncertain) {
        $journalData['stage'] = 'transport-unknown-no-cleanup';
        $saveJournal();
    } else {
        \think\facade\Cache::delete($token);
        if ($cartId > 0) {
            $ai->prepare("DELETE FROM $aCart WHERE ID=? AND USER_ID=?")
                ->execute([$cartId, $userId]);
        }
        if ($userId > 0) {
            $ai->prepare("DELETE FROM $aUser WHERE USER_ID=? AND NICK_NAME=?")
                ->execute([$userId, $marker]);
        }
        $journalData['stage'] = 'cleaned-awaiting-independent-zero';
        $saveJournal();
    }
}
