<?php

declare(strict_types=1);

$dialect = (string)getenv('POC_DIALECT');
$root = (string)getenv('POC_APP_ROOT');
$base = rtrim((string)getenv('POC_BASE_URL'), '/');
if (!in_array($dialect, ['mysql', 'dm'], true) || strpos($root, '/tmp/zhct-') !== 0
    || $base === '' || !is_file($root . '/application/base/Security.php')) {
    throw new RuntimeException('Isolated Store directory runtime missing');
}
$boundaryContext = stream_context_create(['http' => ['timeout' => 5]]);
$boundaryRaw = @file_get_contents($base . '/__poc_boundary', false, $boundaryContext);
$boundary = is_string($boundaryRaw) ? json_decode($boundaryRaw, true) : null;
if (!is_array($boundary) || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated'])) {
    throw new RuntimeException('Directory HTTP isolation boundary absent');
}

$env = [];
foreach (['POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER', 'POC_PASSWORD'] as $key) {
    $env[$key] = (string)getenv($key);
}
$pid = (string)getenv('POC_RUNTIME_PID');
if ($dialect === 'dm' && $env['POC_HOST'] === '' && ctype_digit($pid)) {
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $entry) {
        $parts = explode('=', $entry, 2);
        if (count($parts) === 2 && array_key_exists($parts[0], $env)) $env[$parts[0]] = $parts[1];
    }
}
if ($dialect === 'mysql' && $env['POC_HOST'] === '') {
    $local = $root . '/application/config/local/database.php';
    $file = is_file($local) ? $local : $root . '/application/database.php';
    $config = (static function (string $path): array { return require $path; })($file);
    $env = ['POC_HOST' => (string)$config['hostname'],
        'POC_PORT' => (string)($config['hostport'] ?? 3306),
        'POC_SCHEMA' => (string)$config['database'],
        'POC_USER' => (string)$config['username'],
        'POC_PASSWORD' => (string)$config['password']];
}
if ($env['POC_HOST'] === '' || $env['POC_SCHEMA'] === '' || $env['POC_USER'] === '') {
    throw new RuntimeException('Store POC DB configuration missing');
}
$dsn = $dialect === 'dm'
    ? "dm:host={$env['POC_HOST']};port={$env['POC_PORT']};schema={$env['POC_SCHEMA']}"
    : "mysql:host={$env['POC_HOST']};port={$env['POC_PORT']};dbname={$env['POC_SCHEMA']};charset=utf8mb4";
$db = new PDO($dsn, $env['POC_USER'], $env['POC_PASSWORD'],
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
$t = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YDY_' . strtoupper($name) : 'ydy_' . $name;
};
$one = static function (string $sql, array $params = []) use ($db): array {
    $query = $db->prepare($sql);
    $query->execute($params);
    return $query->fetch(PDO::FETCH_ASSOC) ?: [];
};
$limit = $dialect === 'dm' ? 'FETCH FIRST 1 ROWS ONLY' : 'LIMIT 1';
$department = $one('SELECT d.UUID,d.CODE,d.NAME,d.STATUS FROM ' . $t('department') . ' d'
    . ' WHERE d.IS_SHOW=1 AND d.STATUS=1'
    . ' AND EXISTS (SELECT 1 FROM ' . $t('staff_department') . ' sd'
    . ' JOIN ' . $t('staff') . ' s ON s.UUID=sd.STAFF_UUID'
    . ' WHERE sd.DEPARTMENT_UUID=d.UUID AND s.IS_SHOW=1)'
    . ' AND NOT EXISTS (SELECT 1 FROM ' . $t('department') . ' child'
    . ' WHERE child.PUUID=d.UUID AND child.IS_SHOW=1)'
    . ' ORDER BY d.UUID ' . $limit);
if (!$department) throw new RuntimeException('No visible leaf department with staff');
$staff = $one('SELECT s.ID,s.UUID,s.NAME,s.GRADE FROM ' . $t('staff') . ' s'
    . ' JOIN ' . $t('staff_department') . ' sd ON sd.STAFF_UUID=s.UUID'
    . ' WHERE sd.DEPARTMENT_UUID=? AND s.IS_SHOW=1 ORDER BY s.ID DESC ' . $limit,
    [(string)$department['uuid']]);
if (!$staff) throw new RuntimeException('Selected department has no visible staff');
$recipe = $one('SELECT rec.RESTAURANT_ID,rec.MENU_DATE,rec.MEAL_TIMES'
    . ' FROM ' . $t('recipes') . ' rec'
    . ' JOIN ' . $t('dishes') . ' di ON di.UUID=rec.DISHES_UUID'
    . ' JOIN ' . $t('restaurants') . ' r ON r.ID=rec.RESTAURANT_ID'
    . ' WHERE di.IS_DEL=1 AND di.STATUS=1 AND di.CATEGORY BETWEEN 1 AND 6'
    . ' AND r.IS_DELETE=2 ORDER BY rec.ID DESC ' . $limit);
if (!$recipe) throw new RuntimeException('No active recipe/restaurant read fixture');
$recipeCount = $one('SELECT COUNT(*) AS N FROM ' . $t('recipes') . ' rec'
    . ' JOIN ' . $t('dishes') . ' di ON di.UUID=rec.DISHES_UUID'
    . ' WHERE rec.RESTAURANT_ID=? AND rec.MENU_DATE=? AND rec.MEAL_TIMES=?'
    . ' AND di.IS_DEL=1 AND di.STATUS=1 AND di.CATEGORY BETWEEN 1 AND 6',
    [(int)$recipe['restaurant_id'], substr((string)$recipe['menu_date'], 0, 10),
        (int)$recipe['meal_times']]);
if ((int)($recipeCount['n'] ?? 0) <= 0) throw new RuntimeException('Restaurant fixture empty');

require_once $root . '/application/base/Security.php';
$marker = 'CDXPARSTDIR' . strtoupper(bin2hex(random_bytes(6)));
$token = \app\base\Security::encrypt(implode("\t", ['0', $marker, $marker, '0', '', '', $marker]));
$adminId = 0;
$results = [];
$post = static function (string $route, array $form, bool $auth = true) use ($base, $token): array {
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\n"
            . ($auth ? 'token: ' . $token . "\r\n" : ''),
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 20, 'ignore_errors' => true,
    ]]);
    $body = file_get_contents($base . $route, false, $context);
    $headers = $http_response_header ?? [];
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $match);
    $status = isset($match[1]) ? (int)$match[1] : 0;
    $json = is_string($body) ? json_decode($body, true) : null;
    return ['http_status' => $status, 'json' => is_array($json) ? $json : null,
        'body_is_json' => is_array($json)];
};
$shape = static function ($value) use (&$shape): string {
    if (!is_array($value)) return gettype($value);
    $keys = array_keys($value);
    if ($keys === range(0, count($keys) - 1)) {
        return '[' . implode(',', array_map($shape, $value)) . ']';
    }
    sort($keys, SORT_STRING);
    $parts = [];
    foreach ($keys as $key) $parts[] = $key . ':' . $shape($value[$key]);
    return '{' . implode(',', $parts) . '}';
};
$record = static function (array $response, bool $pass, array $extras = []) use ($shape): array {
    $json = $response['json'];
    return ['status' => $pass ? 'tested-pass' : 'tested-fail',
        'http_status' => $response['http_status'],
        'json' => $response['body_is_json'],
        'shape_sha256' => is_array($json) ? hash('sha256', $shape($json)) : null] + $extras;
};
try {
    $insertAdmin = $db->prepare('INSERT INTO ' . $t('user')
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $insertAdmin->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
    $admin = $one('SELECT ID FROM ' . $t('user') . ' WHERE UUID=? AND ACCOUNT=?', [$marker, $marker]);
    $adminId = (int)($admin['id'] ?? 0);
    if ($adminId <= 0) throw new RuntimeException('Synthetic directory operator insert failed');

    $unauth = $post('/p/department/lists', ['page' => 1, 'pagesize' => 1], false);
    $results['department_lists_unauth'] = $record($unauth,
        $unauth['http_status'] === 200 && (int)($unauth['json']['code'] ?? -1) === 100);

    $deptForm = ['page' => 1, 'pagesize' => 1, 'search_k' => 'uuid',
        'search_v' => (string)$department['uuid']];
    $deptList = $post('/p/department/lists', $deptForm);
    $deptRows = (array)($deptList['json']['data']['rows'] ?? []);
    $results['department_lists'] = $record($deptList,
        $deptList['http_status'] === 200 && (int)($deptList['json']['code'] ?? -1) === 0
            && count($deptRows) === 1
            && (string)($deptRows[0]['uuid'] ?? '') === (string)$department['uuid']
            && (string)($deptRows[0]['name'] ?? '') === (string)$department['name']
            && (int)($deptList['json']['data']['total'] ?? -1) === 1,
        ['result_rows' => count($deptRows), 'source_match' => true]);
    $page2 = $post('/p/department/lists', array_replace($deptForm, ['page' => 2]));
    $results['department_page2'] = $record($page2,
        $page2['http_status'] === 200 && (int)($page2['json']['code'] ?? -1) === 0
            && count((array)($page2['json']['data']['rows'] ?? [])) === 0
            && (int)($page2['json']['data']['total'] ?? -1) === 1,
        ['result_rows' => 0]);
    $deptInfo = $post('/p/department/info', ['uuid' => (string)$department['uuid']]);
    $info = (array)($deptInfo['json']['data'] ?? []);
    $results['department_info'] = $record($deptInfo,
        $deptInfo['http_status'] === 200 && (int)($deptInfo['json']['code'] ?? -1) === 0
            && (string)($info['uuid'] ?? '') === (string)$department['uuid']
            && (string)($info['name'] ?? '') === (string)$department['name']
            && (int)($info['status'] ?? -1) === (int)$department['status'],
        ['source_match' => true]);

    $staffForm = ['department_uuid' => (string)$department['uuid'], 'page' => 1];
    $staffList = $post('/p/staff/lists', $staffForm);
    $staffRows = (array)($staffList['json']['data']['rows'] ?? []);
    $staffUuids = array_map(static function ($item): string {
        return (string)($item['uuid'] ?? '');
    }, $staffRows);
    $results['staff_lists'] = $record($staffList,
        $staffList['http_status'] === 200 && (int)($staffList['json']['code'] ?? -1) === 0
            && count($staffRows) >= 1 && count($staffRows) <= 15
            && in_array((string)$staff['uuid'], $staffUuids, true)
            && (int)($staffList['json']['data']['total'] ?? 0) >= count($staffRows),
        ['result_rows' => count($staffRows), 'source_member' => in_array((string)$staff['uuid'], $staffUuids, true)]);
    $sportShow = $post('/p/staff/sportShow', ['department_uuid' => (string)$department['uuid']]);
    $sportRows = (array)($sportShow['json']['data']['rows'] ?? []);
    $sportUuids = array_map(static function ($item): string {
        return (string)($item['uuid'] ?? '');
    }, $sportRows);
    $results['staff_sport_show'] = $record($sportShow,
        $sportShow['http_status'] === 200 && (int)($sportShow['json']['code'] ?? -1) === 0
            && in_array((string)$staff['uuid'], $sportUuids, true)
            && (int)($sportShow['json']['data']['total'] ?? 0) >= 1,
        ['result_rows' => count($sportRows), 'source_member' => in_array((string)$staff['uuid'], $sportUuids, true)]);

    $restaurantForm = ['restaurant_id' => (int)$recipe['restaurant_id'],
        'menu_date' => substr((string)$recipe['menu_date'], 0, 10),
        'meal_times' => (int)$recipe['meal_times']];
    $greens = $post('/p/restaurant/greens_kind', $restaurantForm);
    $map = (array)($greens['json']['data']['map'] ?? []);
    $observed = array_sum(array_map('intval', $map));
    $expected = (int)$recipeCount['n'];
    $results['restaurant_greens_kind'] = $record($greens,
        $greens['http_status'] === 200 && (int)($greens['json']['code'] ?? -1) === 0
            && $expected > 0 && $observed === $expected,
        ['category_rows' => $observed, 'source_rows' => $expected]);

    // Sixth read-only observation. Its legacy raw SQL may be dialect-sensitive.
    $cook = $post('/p/restaurant/cook_method', $restaurantForm);
    $cookMap = (array)($cook['json']['data']['map'] ?? []);
    $cookTable = (array)($cook['json']['data']['table'] ?? []);
    $sumCook = array_sum(array_map('intval', $cookMap));
    $cookPass = $cook['http_status'] === 200 && (int)($cook['json']['code'] ?? -1) === 0
        && count($cookMap) === 20 && $sumCook >= 0
        && (int)($cookTable['recommend_num'] ?? -1)
            + (int)($cookTable['no_recommend_num'] ?? -1) === $sumCook;
    $results['restaurant_cook_method'] = $record($cook, $cookPass,
        ['cook_categories' => count($cookMap), 'sum_matches_subtotals' => $cookPass]);
} finally {
    if ($adminId > 0) {
        $delete = $db->prepare('DELETE FROM ' . $t('user')
            . ' WHERE ID=? AND UUID=? AND ACCOUNT=?');
        $delete->execute([$adminId, $marker, $marker]);
        $check = $db->prepare('SELECT COUNT(*) FROM ' . $t('user') . ' WHERE ID=?');
        $check->execute([$adminId]);
        if ((int)$check->fetchColumn() !== 0) {
            throw new RuntimeException('Synthetic directory operator not cleaned');
        }
    }
}
$results['cleanup'] = ['status' => 'tested-pass', 'operator_rows' => 0];
echo json_encode(['dialect' => $dialect, 'module' => 'store-directory-reads',
    'results' => $results], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
foreach ($results as $result) {
    if (($result['status'] ?? '') === 'tested-fail') exit(1);
}
