<?php

declare(strict_types=1);

// Isolated synthetic-order A/B/C probe. Run with POC_PREFLIGHT_ONLY=1 before allowing writes.
$root = (string)getenv('POC_TARGET_ROOT');
$baseUrl = rtrim((string)getenv('POC_BASE_URL'), '/');
$dialect = (string)getenv('POC_DIALECT');
$label = (string)getenv('POC_LABEL');
$orderId = (int)getenv('POC_ORDER_ID');
$orderMarker = (string)getenv('POC_ORDER_MARKER');
$preflightOnly = getenv('POC_PREFLIGHT_ONLY') === '1';
if (!in_array($label, ['A', 'B', 'C'], true)
    || !in_array($dialect, ['mysql', 'dm'], true)
    || !preg_match('#^http://127\.0\.0\.1:[0-9]{2,5}$#D', $baseUrl)
    || !preg_match('/^CDXPAY[A-F0-9]{8}$/D', $orderMarker)
    || $orderId <= 0 || !is_file($root . '/vendor/autoload.php')) {
    throw new RuntimeException('Invalid isolated evaluation fixture');
}
if ($dialect === 'mysql') {
    $localFile = (string)getenv('POC_MYSQL_LOCAL_ENV');
    if (basename($localFile) !== '.env.local') throw new RuntimeException('Only ai_api .env.local is allowed');
    $local = parse_ini_file($localFile, true, INI_SCANNER_RAW);
    if (!is_array($local) || !isset($local['DATABASE'], $local['DATABASE_ZHCT'])) {
        throw new RuntimeException('Development MySQL configuration unavailable');
    }
    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('Approved database environment incomplete');
}
$boundary = json_decode((string)file_get_contents($baseUrl . '/__poc_boundary'), true);
if (!is_array($boundary) || empty($boundary['external_stubs'])) {
    throw new RuntimeException('Isolated external boundary unavailable');
}
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";
};
$store = new PDO($dsn(''), getenv('POC_USER'), getenv('POC_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$ai = new PDO($dsn('AI_'), getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$t = static function (string $name, bool $isAi = false) use ($dialect): string {
    $raw = ($isAi ? 'yoshop_' : 'ydy_') . $name;
    return $dialect === 'dm' ? strtoupper($raw) : $raw;
};
$sOrder = $t('meal_order');
$sUser = $t('user');
$sMenu = $t('initial_menu');
$sEval = $t('order_evaluate');
$sEvalDetail = $t('order_evaluate_detail');
$aUser = $t('user', true);
$commentColumn = $dialect === 'dm' ? '"COMMENT"' : '`COMMENT`';
$read = static function (PDO $pdo, string $sql, array $params = []): array {
    $statement = $pdo->prepare($sql);
    $statement->execute($params);
    return array_change_key_case($statement->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
};
$count = static function (PDO $pdo, string $sql, array $params = []): int {
    $statement = $pdo->prepare($sql);
    $statement->execute($params);
    return (int)$statement->fetchColumn();
};
$order = $read($store, 'SELECT ID,MSGID,ORDER_NO,USER_ID,ORDER_STATUS,EVALUATE_STATUS,EVALUATE_ID,UPDATE_TIME'
    . " FROM $sOrder WHERE ID=?", [$orderId]);
$user = $read($store, "SELECT ID,UUID,ACCOUNT,AI_USER_ID FROM $sUser WHERE ID=?", [(int)($order['user_id'] ?? 0)]);
$aiUser = $read($ai, "SELECT USER_ID,STORE_ID,IS_DELETE FROM $aUser WHERE USER_ID=?",
    [(int)($user['ai_user_id'] ?? 0)]);
$menu = $read($store, "SELECT ID,DISHES_UUID FROM $sMenu WHERE MEAL_ORDER_ID=? ORDER BY ID", [$orderId]);
$priorEvalRows = $count($store, "SELECT COUNT(*) FROM $sEval WHERE MEAL_ORDER_ID=?", [$orderId]);
$priorDetailRows = $count($store, "SELECT COUNT(*) FROM $sEvalDetail WHERE MEAL_ORDER_ID=?", [$orderId]);
if (!$order || (string)$order['msgid'] !== $orderMarker || (int)$order['order_status'] !== 30
    || (int)$order['evaluate_status'] !== 1 || (int)$order['evaluate_id'] !== 0
    || !$user || (string)$user['account'] !== $orderMarker
    || strpos((string)$user['uuid'], 'CDXUSER-') !== 0
    || (int)$user['id'] !== (int)$order['user_id']
    || !$aiUser || (int)$aiUser['store_id'] !== 10001 || (int)$aiUser['is_delete'] !== 0
    || !$menu || $priorEvalRows !== 0 || $priorDetailRows !== 0) {
    throw new RuntimeException('Synthetic order preflight failed; no evaluation was written');
}
$preflight = [
    'label' => $label, 'dialect' => $dialect, 'order_id' => $orderId,
    'synthetic_linked_identity' => true, 'completed_unevaluated' => true,
    'menu_rows' => $count($store, "SELECT COUNT(*) FROM $sMenu WHERE MEAL_ORDER_ID=?", [$orderId]),
    'existing_evaluation_rows' => $priorEvalRows,
    'existing_evaluation_detail_rows' => $priorDetailRows,
    'external_stubs' => true,
];
if ($preflightOnly) {
    echo json_encode(['phase' => 'preflight', 'result' => $preflight],
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

$reviewMarker = 'CDXPAREVAL' . strtoupper(bin2hex(random_bytes(4)));
$token = $reviewMarker . 'TOKEN' . strtoupper(bin2hex(random_bytes(12)));
$base = '?storeId=10001';
$request = static function (string $method, string $path, ?string $auth = null, array $body = []) use ($baseUrl): array {
    $headers = "platform: POC\r\nContent-Type: application/json\r\n";
    if ($auth !== null) $headers .= 'Access-Token: ' . $auth . "\r\n";
    $options = ['method' => $method, 'header' => $headers, 'ignore_errors' => true, 'timeout' => 30];
    if ($method !== 'GET') $options['content'] = json_encode($body, JSON_UNESCAPED_UNICODE);
    $raw = file_get_contents($baseUrl . $path, false, stream_context_create(['http' => $options]));
    $statusLine = $http_response_header[0] ?? '';
    $json = is_string($raw) ? json_decode($raw, true) : null;
    if (!is_array($json)) throw new RuntimeException('Non-JSON HTTP response at ' . $path);
    unset($json['trace_id']);
    return ['http' => (int)(preg_match('/HTTP\/\S+\s+(\d+)/', $statusLine, $m) ? $m[1] : 0),
        'body' => $json];
};
$ok = static function (array $result, string $step): array {
    $body = $result['body'];
    if ($result['http'] !== 200 || (int)($body['status'] ?? -1) !== 200) {
        throw new RuntimeException($step . ' did not return HTTP/business 200: http='
            . $result['http'] . ', status=' . ($body['status'] ?? 'missing')
            . ', message=' . mb_substr((string)($body['message'] ?? ''), 0, 120));
    }
    return $body;
};
$shape = static function ($value, string $path = '$') use (&$shape): array {
    $result = [$path => is_array($value) ? ($value === [] ? 'empty-container'
        : (array_keys($value) === range(0, count($value) - 1) ? 'array' : 'object')) : gettype($value)];
    if (is_array($value)) {
        foreach ($value as $key => $child) {
            $childPath = is_int($key) ? $path . '[]' : $path . '.' . $key;
            $result = array_merge($result, $shape($child, $childPath));
        }
    }
    ksort($result);
    return $result;
};
$beforePointer = [(int)$order['evaluate_status'], (int)$order['evaluate_id'], (string)$order['update_time']];
$tokenCreated = false;
$runError = null;
$cleanupError = null;
$result = ['phase' => 'execution', 'preflight' => $preflight, 'review_marker' => $reviewMarker,
    'checks' => [], 'response_shapes' => []];
try {
    \think\facade\Cache::set($token, ['user' => ['user_id' => (int)$user['ai_user_id']],
        'password_version' => 0, 'login_type' => 'thirdparty'], 600);
    $tokenCreated = true;
    $unauth = $request('GET', '/api/mealOrder/evaluate' . $base . '&orderId=' . $orderId);
    $result['checks']['unauth_get_rejected'] = (int)($unauth['body']['status'] ?? 200) !== 200;
    if (!$result['checks']['unauth_get_rejected']) throw new RuntimeException('Unauthenticated evaluation GET accepted');

    $before = $ok($request('GET', '/api/mealOrder/evaluate' . $base . '&orderId=' . $orderId, $token),
        'evaluate before');
    $beforeData = $before['data'] ?? [];
    $result['checks']['before_unevaluated'] = (int)($beforeData['orderInfo']['evaluate_status'] ?? 0) === 1
        && count((array)($beforeData['detail'] ?? [])) >= 1;
    if (!$result['checks']['before_unevaluated']) throw new RuntimeException('Before-evaluate state mismatch');
    $dishUuid = (string)($beforeData['detail'][0]['dishes_uuid'] ?? '');
    if ($dishUuid === '' || $dishUuid !== (string)$menu['dishes_uuid']) {
        throw new RuntimeException('Evaluation dish is not the synthetic order dish');
    }
    $result['response_shapes']['before'] = $shape($before);

    $form = ['orderId' => $orderId, 'environment_score' => 5, 'service_score' => 4,
        'comment' => $reviewMarker,
        'detail' => [['dishes_uuid' => $dishUuid, 'dishes_score' => 5,
            'dishes_taste' => [], 'comment' => $reviewMarker]]];
    $submit = $ok($request('POST', '/api/mealOrder/evaluateSubmit' . $base, $token,
        ['form' => $form]), 'evaluate submit');
    $evaluationId = (int)($submit['data']['evaluate_id'] ?? 0);
    $result['checks']['submit_id_positive'] = $evaluationId > 0;
    if (!$result['checks']['submit_id_positive']) throw new RuntimeException('Submit evaluation ID missing');
    $result['response_shapes']['submit'] = $shape($submit);

    $writtenOrder = $read($store, "SELECT EVALUATE_STATUS,EVALUATE_ID FROM $sOrder WHERE ID=?", [$orderId]);
    $writtenEval = $read($store, "SELECT ID,MEAL_ORDER_ID,USER_ID,ENVIRONMENT_SCORE,SERVICE_SCORE,$commentColumn"
        . " FROM $sEval WHERE ID=?", [$evaluationId]);
    $writtenDetail = $read($store, "SELECT ID,EVALUATE_ID,MEAL_ORDER_ID,DISHES_UUID,DISHES_SCORE,DISHES_TASTE,$commentColumn"
        . " FROM $sEvalDetail WHERE EVALUATE_ID=?", [$evaluationId]);
    $result['checks']['three_table_readback'] = (int)($writtenOrder['evaluate_status'] ?? 0) === 2
        && (int)($writtenOrder['evaluate_id'] ?? 0) === $evaluationId
        && (int)($writtenEval['meal_order_id'] ?? 0) === $orderId
        && (int)($writtenEval['user_id'] ?? 0) === (int)$user['id']
        && (string)($writtenEval['comment'] ?? '') === $reviewMarker
        && (int)($writtenEval['environment_score'] ?? -1) === 5
        && (int)($writtenEval['service_score'] ?? -1) === 4
        && (int)($writtenDetail['evaluate_id'] ?? 0) === $evaluationId
        && (int)($writtenDetail['meal_order_id'] ?? 0) === $orderId
        && (string)($writtenDetail['dishes_uuid'] ?? '') === $dishUuid
        && (int)($writtenDetail['dishes_score'] ?? -1) === 5
        && (string)($writtenDetail['dishes_taste'] ?? '') === '[]'
        && (string)($writtenDetail['comment'] ?? '') === $reviewMarker;
    if (!$result['checks']['three_table_readback']) throw new RuntimeException('Three-table evaluation readback mismatch');

    $after = $ok($request('GET', '/api/mealOrder/evaluate' . $base . '&orderId=' . $orderId, $token),
        'evaluate after');
    $afterData = $after['data'] ?? [];
    $result['checks']['after_get_reflects_write'] = (int)($afterData['orderInfo']['evaluate_status'] ?? 0) === 2
        && (string)($afterData['overall']['comment'] ?? '') === $reviewMarker
        && (string)($afterData['detail'][0]['comment'] ?? '') === $reviewMarker;
    if (!$result['checks']['after_get_reflects_write']) throw new RuntimeException('After-evaluate GET mismatch');
    $result['response_shapes']['after'] = $shape($after);

    $repeat = $request('POST', '/api/mealOrder/evaluateSubmit' . $base, $token, ['form' => $form]);
    $result['checks']['duplicate_rejected'] = (int)($repeat['body']['status'] ?? 200) !== 200
        && $count($store, "SELECT COUNT(*) FROM $sEval WHERE MEAL_ORDER_ID=?", [$orderId]) === 1
        && $count($store, "SELECT COUNT(*) FROM $sEvalDetail WHERE MEAL_ORDER_ID=?", [$orderId]) === 1
        && (int)($read($store, "SELECT EVALUATE_ID FROM $sOrder WHERE ID=?", [$orderId])['evaluate_id'] ?? 0)
            === $evaluationId;
    if (!$result['checks']['duplicate_rejected']) throw new RuntimeException('Duplicate evaluation was accepted or wrote rows');
    $result['response_shapes']['duplicate'] = $shape($repeat['body']);
} catch (Throwable $error) {
    $runError = get_class($error) . ': ' . mb_substr($error->getMessage(), 0, 160);
} finally {
    try {
        $owned = $read($store, "SELECT ID FROM $sEval WHERE MEAL_ORDER_ID=? AND $commentColumn=?",
            [$orderId, $reviewMarker]);
        $ownedId = (int)($owned['id'] ?? 0);
        $ownedRows = $count($store, "SELECT COUNT(*) FROM $sEval WHERE MEAL_ORDER_ID=? AND $commentColumn=?",
            [$orderId, $reviewMarker]);
        $current = $read($store, "SELECT EVALUATE_STATUS,EVALUATE_ID FROM $sOrder WHERE ID=?", [$orderId]);
        if ($ownedRows > 1) throw new RuntimeException('Multiple marker-owned evaluation rows; preserving for review');
        if ($ownedId > 0) {
            $detailTotal = $count($store, "SELECT COUNT(*) FROM $sEvalDetail WHERE EVALUATE_ID=?", [$ownedId]);
            $detailOwned = $count($store, "SELECT COUNT(*) FROM $sEvalDetail WHERE EVALUATE_ID=? AND $commentColumn=?",
                [$ownedId, $reviewMarker]);
            if ($detailTotal !== $detailOwned) throw new RuntimeException('Foreign detail under marker evaluation; preserving');
            $pointerOwned = (int)($current['evaluate_id'] ?? -1) === $ownedId
                && (int)($current['evaluate_status'] ?? -1) === 2;
            $pointerOriginal = (int)($current['evaluate_id'] ?? -1) === $beforePointer[1]
                && (int)($current['evaluate_status'] ?? -1) === $beforePointer[0];
            if (!$pointerOwned && !$pointerOriginal) {
                throw new RuntimeException('Order pointer changed unexpectedly; preserving marker evaluation');
            }
            $store->beginTransaction();
            try {
                if ($pointerOwned) {
                    $restore = $store->prepare("UPDATE $sOrder SET EVALUATE_STATUS=?,EVALUATE_ID=?,UPDATE_TIME=?"
                        . ' WHERE ID=? AND EVALUATE_STATUS=2 AND EVALUATE_ID=?');
                    $restore->execute([$beforePointer[0], $beforePointer[1], $beforePointer[2], $orderId, $ownedId]);
                    if ($restore->rowCount() !== 1) throw new RuntimeException('Pointer restore guard failed');
                }
                $deleteDetails = $store->prepare("DELETE FROM $sEvalDetail WHERE EVALUATE_ID=? AND $commentColumn=?");
                $deleteDetails->execute([$ownedId, $reviewMarker]);
                if ($deleteDetails->rowCount() !== $detailOwned) throw new RuntimeException('Detail cleanup count mismatch');
                $deleteEval = $store->prepare("DELETE FROM $sEval WHERE ID=? AND MEAL_ORDER_ID=? AND $commentColumn=?");
                $deleteEval->execute([$ownedId, $orderId, $reviewMarker]);
                if ($deleteEval->rowCount() !== 1) throw new RuntimeException('Evaluation cleanup count mismatch');
                $store->commit();
            } catch (Throwable $error) {
                if ($store->inTransaction()) $store->rollBack();
                throw $error;
            }
        }
        $restored = $read($store, "SELECT EVALUATE_STATUS,EVALUATE_ID,UPDATE_TIME FROM $sOrder WHERE ID=?", [$orderId]);
        $result['cleanup'] = [
            'order_status_pointer_restored' => (int)($restored['evaluate_status'] ?? -1) === $beforePointer[0]
                && (int)($restored['evaluate_id'] ?? -1) === $beforePointer[1],
            'order_update_time_restored' => (string)($restored['update_time'] ?? '') === $beforePointer[2],
            'marker_evaluation_rows_zero' => $count($store,
                "SELECT COUNT(*) FROM $sEval WHERE MEAL_ORDER_ID=? AND $commentColumn=?", [$orderId, $reviewMarker]) === 0,
            'marker_detail_rows_zero' => $count($store,
                "SELECT COUNT(*) FROM $sEvalDetail WHERE MEAL_ORDER_ID=? AND $commentColumn=?", [$orderId, $reviewMarker]) === 0,
        ];
        if (in_array(false, $result['cleanup'], true)) throw new RuntimeException('Cleanup readback incomplete');
    } catch (Throwable $error) {
        $cleanupError = get_class($error) . ': ' . mb_substr($error->getMessage(), 0, 160);
    }
    if ($tokenCreated) {
        try {
            \think\facade\Cache::delete($token);
            $result['cleanup']['cache_token_deleted'] = !\think\facade\Cache::has($token);
        } catch (Throwable $error) {
            $cleanupError = ($cleanupError ? $cleanupError . '; ' : '')
                . 'token cleanup ' . get_class($error);
        }
    }
}
$result['status'] = $runError === null && $cleanupError === null ? 'passed' : 'failed';
if ($runError !== null) $result['failure'] = $runError;
if ($cleanupError !== null) $result['cleanup_failure'] = $cleanupError;
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
if ($result['status'] !== 'passed') exit(1);
