<?php

declare(strict_types=1);

// REVIEW DRAFT ONLY. No network/DB work unless this exact file SHA is approved
// separately and both explicit gates are supplied. Never run on production.
if ((string)getenv('CDXREAD4_EXECUTE') !== 'ROOT_APPROVED_EXACT_SHA'
    || ($argv[1] ?? '') !== '--execute-read4') {
    fwrite(STDERR, "Review draft; execution disabled\n");
    exit(2);
}

set_exception_handler(static function (Throwable $error): void {
    fwrite(STDERR, 'READ4_GATE_FAILED ' . get_class($error)
        . ' code=' . (string)$error->getCode() . PHP_EOL);
    exit(2);
});

$lane = (string)getenv('CDXREAD4_LANE');
$root = (string)getenv('POC_APP_ROOT');
$base = rtrim((string)getenv('POC_BASE_URL'), '/');
$allowed = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 'mysql'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 'mysql'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 'dm'],
];
if (!isset($allowed[$lane])
    || $root !== $allowed[$lane][0]
    || $base !== $allowed[$lane][1]
    || (string)getenv('POC_DIALECT') !== $allowed[$lane][2]
    || is_link($root)) {
    throw new RuntimeException('Approved isolated Store lane required');
}

$pid = (string)getenv('POC_RUNTIME_PID');
if (!ctype_digit($pid) || !is_file('/proc/' . $pid . '/cmdline')) {
    throw new RuntimeException('Runtime PID gate failed');
}
$cmdline = (string)file_get_contents('/proc/' . $pid . '/cmdline');
$port = (string)parse_url($base, PHP_URL_PORT);
if (strpos($cmdline, '127.0.0.1:' . $port) === false
    || strpos($cmdline, $root . '/public') === false) {
    throw new RuntimeException('Runtime port/docroot mismatch');
}

$sources = [
    'application/route.php',
    'application/base/AdminController.php',
    'application/p/controller/City.php',
    'application/p/controller/Article.php',
    'application/p/controller/BirthdayDish.php',
    'application/p/logic/City.php',
    'application/p/logic/Article.php',
    'application/p/logic/BirthdayDish.php',
];
$sourceHashes = [];
foreach ($sources as $source) {
    $path = $root . '/' . $source;
    if (!is_file($path) || is_link($path)) {
        throw new RuntimeException('Source file gate failed');
    }
    $sourceHashes[] = hash_file('sha256', $path);
}
$sourceDigest = hash('sha256', implode('', $sourceHashes));
if ((string)getenv('CDXREAD4_SOURCE_DIGEST') !== $sourceDigest) {
    throw new RuntimeException('Reviewed source digest mismatch');
}

$boundaryBody = @file_get_contents($base . '/__poc_boundary');
$boundary = is_string($boundaryBody) ? json_decode($boundaryBody, true) : null;
if (!is_array($boundary)
    || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated'])
    || !empty($boundary['real_queue'])) {
    throw new RuntimeException('External stub/Redis/queue boundary failed');
}

$host = (string)getenv('POC_HOST');
$dbPort = (string)getenv('POC_PORT');
$schema = (string)getenv('POC_SCHEMA');
$dbUser = (string)getenv('POC_USER');
$dbPassword = (string)getenv('POC_PASSWORD');
if ($host === '' || $dbPort === '' || $schema === ''
    || $dbUser === '' || $dbPassword === '') {
    throw new RuntimeException('DB context missing');
}
if ($lane === 'C') {
    if ($host !== '127.0.0.1' || $dbPort !== '5237'
        || strtoupper($schema) !== 'ZHCT_APP'
        || strtoupper($dbUser) !== 'ZHCT_APP') {
        throw new RuntimeException('Nonproduction DM target mismatch');
    }
    $dsn = 'dm:host=' . $host . ';port=' . $dbPort . ';schema=' . $schema;
} else {
    $configPath = $root . '/application/config/local/database.php';
    if (!is_file($configPath)) {
        throw new RuntimeException('Code local MySQL config missing');
    }
    $config = require $configPath;
    if (!is_array($config)
        || (string)($config['hostname'] ?? '') !== $host
        || (string)($config['hostport'] ?? '3306') !== $dbPort
        || (string)($config['database'] ?? '') !== $schema
        || (string)($config['username'] ?? '') !== $dbUser
        || (string)($config['password'] ?? '') !== $dbPassword) {
        throw new RuntimeException('Runtime DB differs from code local MySQL');
    }
    $dsn = 'mysql:host=' . $host . ';port=' . $dbPort
        . ';dbname=' . $schema . ';charset=utf8mb4';
}
$db = new PDO($dsn, $dbUser, $dbPassword, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
]);
$table = static function (string $name) use ($lane): string {
    return $lane === 'C' ? strtoupper('ydy_' . $name) : 'ydy_' . $name;
};
$query = static function (string $sql, array $params = []) use ($db): array {
    $statement = $db->prepare($sql);
    $statement->execute($params);
    return array_map(static function (array $row): array {
        return array_change_key_case($row, CASE_LOWER);
    }, $statement->fetchAll(PDO::FETCH_ASSOC));
};
$scalar = static function (string $sql, array $params = []) use ($query): int {
    $rows = $query($sql, $params);
    return (int)($rows[0]['n'] ?? -1);
};

// Read-only fixture gates. The actor must have been created by a separately
// approved permission-fixture run; this script never creates or deletes rows.
$actorUuid = (string)getenv('CDXREAD4_ACTOR_UUID');
$marker = (string)getenv('CDXREAD4_ACTOR_MARKER');
$token = (string)getenv('CDXREAD4_TOKEN');
$cityMenuUuid = (string)getenv('CDXREAD4_CITY_MENU_UUID');
$birthdayMenuUuid = 'A1344000-0000-0000-0000-000000000001';
if (!preg_match('/^[A-Fa-f0-9-]{36}$/', $actorUuid)
    || !preg_match('/^CDX[A-Z0-9_]{8,48}$/', $marker)
    || $token === ''
    || !preg_match('/^[A-Fa-f0-9-]{36}$/', $cityMenuUuid)) {
    throw new RuntimeException('Synthetic actor/menu identity missing');
}
require_once $root . '/application/base/Security.php';
$decoded = \app\base\Security::decrypt($token);
$parts = is_string($decoded) ? explode("\t", $decoded) : [];
if (count($parts) < 8
    || strtoupper((string)($parts[1] ?? '')) !== strtoupper($actorUuid)
    || (int)($parts[3] ?? -1) !== 1) {
    throw new RuntimeException('Token is not this type-1 synthetic actor');
}
$users = $query('SELECT UUID,ACCOUNT,TYPE FROM ' . $table('user') . ' WHERE UUID=?',
    [$actorUuid]);
if (count($users) !== 1 || (string)$users[0]['account'] !== $marker
    || (int)$users[0]['type'] !== 1) {
    throw new RuntimeException('Synthetic actor row mismatch');
}
$links = $query('SELECT ROLE_UUID FROM ' . $table('user_role')
    . ' WHERE USER_UUID=?', [$actorUuid]);
if (count($links) !== 1) {
    throw new RuntimeException('Actor role cardinality mismatch');
}
$roleUuid = (string)$links[0]['role_uuid'];
$roles = $query('SELECT UUID,NAME FROM ' . $table('role')
    . ' WHERE UUID=?', [$roleUuid]);
if (count($roles) !== 1 || (string)$roles[0]['name'] !== $marker) {
    throw new RuntimeException('Synthetic role mismatch');
}
$menuLinks = $query('SELECT MENU_UUID FROM ' . $table('role_menu')
    . ' WHERE ROLE_UUID=?', [$roleUuid]);
$actualMenuUuids = array_map(static function (array $row): string {
    return strtoupper((string)$row['menu_uuid']);
}, $menuLinks);
sort($actualMenuUuids);
$expectedMenuUuids = [strtoupper($cityMenuUuid), $birthdayMenuUuid];
sort($expectedMenuUuids);
if ($actualMenuUuids !== $expectedMenuUuids) {
    throw new RuntimeException('Exact read-only menu links absent');
}
foreach ([
    [$cityMenuUuid, 'p/city/lists'],
    [$birthdayMenuUuid, 'p/birthdaydish/dishoptions'],
] as $expected) {
    $menus = $query('SELECT UUID,FILTER FROM ' . $table('menu')
        . ' WHERE UUID=?', [$expected[0]]);
    if (count($menus) !== 1) {
        throw new RuntimeException('Exact menu row missing');
    }
    $filters = array_map('trim', explode('|',
        strtolower((string)$menus[0]['filter'])));
    if (!in_array($expected[1], $filters, true)) {
        throw new RuntimeException('Exact menu filter missing');
    }
}
foreach ([1, 3] as $scopeType) {
    $scope = $query('SELECT DATA_UUID FROM ' . $table('user_data')
        . ' WHERE USER_UUID=? AND TYPE=?', [$actorUuid, $scopeType]);
    if (count($scope) !== 1 || empty($scope[0]['data_uuid'])) {
        throw new RuntimeException('Department/restaurant scope missing');
    }
    $source = $scopeType === 1 ? 'department' : 'restaurants';
    $where = $scopeType === 1
        ? 'STATUS=1 AND IS_SHOW=1'
        : 'STATUS=1 AND IS_DELETE=2';
    if ($scalar('SELECT COUNT(*) N FROM ' . $table($source)
        . ' WHERE UUID=? AND ' . $where,
        [(string)$scope[0]['data_uuid']]) !== 1) {
        throw new RuntimeException('Inactive permission scope target');
    }
}

$businessTables = ['city', 'article_cate', 'article', 'dishes'];
$before = [];
foreach ($businessTables as $name) {
    $before[$name] = $scalar('SELECT COUNT(*) N FROM ' . $table($name));
    if ($before[$name] < 1) {
        throw new RuntimeException('Nonempty source gate failed');
    }
}
$post = static function (string $path, array $form, string $authToken = '') use ($base): array {
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($authToken !== '') {
        $headers .= 'token: ' . $authToken . "\r\n";
    }
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => $headers,
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 15,
        'ignore_errors' => true,
    ]]);
    $body = @file_get_contents($base . $path, false, $context);
    $responseHeaders = $http_response_header ?? [];
    if (!is_string($body)
        || strpos((string)($responseHeaders[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('Isolated read HTTP transport failed');
    }
    $json = json_decode($body, true);
    if (!is_array($json)) {
        throw new RuntimeException('Read HTTP JSON contract failed');
    }
    return $json;
};
$cases = [
    ['/p/article/getCate', [], 'anonymous'],
    ['/p/article/lists', ['page' => 1], 'anonymous'],
    ['/p/birthdayDish/dishOptions', ['page' => 1, 'limit' => 20], 'type1'],
    ['/p/city/lists', [], 'type1'],
];
$nonempty = static function (string $path, $data): bool {
    if (!is_array($data)) {
        return false;
    }
    if ($path === '/p/article/lists'
        || $path === '/p/birthdayDish/dishOptions') {
        return !empty($data['rows']) && is_array($data['rows']);
    }
    return count($data) > 0;
};
$results = [];
foreach ($cases as $case) {
    $response = $post($case[0], $case[1],
        $case[2] === 'type1' ? $token : '');
    $results[] = [
        'path' => $case[0],
        'auth' => $case[2],
        'code' => $response['code'] ?? null,
        'data_type' => gettype($response['data'] ?? null),
        'data_nonempty' => $nonempty($case[0], $response['data'] ?? null),
        'response_sha256' => hash('sha256', json_encode(
            $response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
    ];
    if (!array_key_exists('code', $response)
        || (int)$response['code'] !== 0
        || !$nonempty($case[0], $response['data'] ?? null)) {
        throw new RuntimeException('Business success/nonempty gate failed');
    }
}
foreach ($businessTables as $name) {
    $after = $scalar('SELECT COUNT(*) N FROM ' . $table($name));
    if ($before[$name] !== $after) {
        throw new RuntimeException('Business source row count changed');
    }
}
echo json_encode([
    'lane' => $lane,
    'fixture' => 'read-only-external-type1',
    'business_rows_unchanged' => true,
    'results' => $results,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
