<?php

declare(strict_types=1);

// Only an approved local-development MySQL Store HTTP runtime. No credentials in output.
$root = (string)getenv('POC_APP_ROOT');
$base = rtrim((string)getenv('POC_BASE_URL'), '/');
$variant = (string)getenv('POC_VARIANT');
if ((string)getenv('POC_DIALECT') !== 'mysql'
    || !in_array($variant, ['A', 'B'], true)
    || strpos($root, '/tmp/zhct-http-store-') !== 0
    || !is_file($root . '/application/base/Security.php')
    || !is_file($root . '/application/p/controller/Station.php')
    || !preg_match('~^http://127\.0\.0\.1:[0-9]+$~', $base)) {
    throw new RuntimeException('Approved isolated MySQL Store runtime missing');
}
foreach (['p/controller/Station.php' => 'bce73e23130e9a2114d9788854b68bef2227eb118d58560afbee1aa88a0808e5',
    'p/logic/Station.php' => '9e28dcc5d35c3a8354859e8e82252bf3dcf07b8d8c4a1ce413cb7f10e5a3f74b',
    'model/Station.php' => 'ae4f7c6f437f4ba9770d1aa6de259d21c9365d2c34d9c45142439c9036fb7901'] as $file => $expected) {
    if (!hash_equals($expected, hash_file('sha256', $root . '/application/' . $file))) {
        throw new RuntimeException('Station runtime source hash differs');
    }
}
$boundaryRaw = @file_get_contents($base . '/__poc_boundary', false,
    stream_context_create(['http' => ['timeout' => 5]]));
$boundary = is_string($boundaryRaw) ? json_decode($boundaryRaw, true) : null;
if (!is_array($boundary) || empty($boundary['external_stubs']) || empty($boundary['redis_isolated'])) {
    throw new RuntimeException('Isolated HTTP side-effect boundary absent');
}

$localPath = '/workspace/zhct/zhctproject/store/application/config/local/database.php';
if (!is_file($localPath) || !is_file($root . '/application/database.php')) {
    throw new RuntimeException('Store local development config boundary absent');
}
$local = require $localPath;
$runtime = require $root . '/application/database.php';
foreach (['hostname', 'database', 'username', 'password'] as $key) {
    if (($runtime[$key] ?? null) !== ($local[$key] ?? null)) {
        throw new RuntimeException('Runtime differs from Store local development config');
    }
}
if ((int)($runtime['hostport'] ?? 3306) !== (int)($local['hostport'] ?? 3306)) {
    throw new RuntimeException('Runtime port differs from Store local development config');
}
$charset = (string)($runtime['charset'] ?? 'utf8');
if (!in_array($charset, ['utf8', 'utf8mb4'], true)) {
    throw new RuntimeException('Unexpected Store MySQL charset');
}
$db = new PDO('mysql:host=' . $runtime['hostname']
    . ';port=' . (int)($runtime['hostport'] ?? 3306)
    . ';dbname=' . $runtime['database'] . ';charset=' . $charset,
    $runtime['username'], $runtime['password'],
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER,
        PDO::ATTR_TIMEOUT => 5]);
unset($local, $runtime);
$table = static function (string $name): string { return 'ydy_' . $name; };
$rows = static function (string $sql, array $args = []) use ($db): array {
    $query = $db->prepare($sql);
    $query->execute($args);
    return $query->fetchAll(PDO::FETCH_ASSOC);
};
$one = static function (string $sql, array $args = []) use ($rows): array {
    return $rows($sql, $args)[0] ?? [];
};
$count = static function (string $sql, array $args = []) use ($one): int {
    return (int)($one($sql, $args)['n'] ?? -1);
};
$assert = static function (bool $ok, string $message): void {
    if (!$ok) throw new RuntimeException($message);
};
$post = static function (string $route, array $form, string $token = '') use ($base): array {
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($token !== '') $headers .= 'token: ' . $token . "\r\n";
    $context = stream_context_create(['http' => [
        'method' => 'POST', 'header' => $headers,
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 20, 'ignore_errors' => true,
    ]]);
    $body = @file_get_contents($base . $route, false, $context);
    $responseHeaders = $http_response_header ?? [];
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($responseHeaders[0] ?? ''), $match);
    $json = is_string($body) ? json_decode($body, true) : null;
    return ['http' => isset($match[1]) ? (int)$match[1] : 0,
        'json' => is_array($json) ? $json : null];
};
$pass = static function (array $response, int $businessCode = 0): bool {
    return $response['http'] === 200 && is_array($response['json'])
        && (int)($response['json']['code'] ?? -1) === $businessCode;
};

$required = [
    'ydy_station' => ['id', 'uuid', 'name', 'status', 'quantity', 'demo', 'create_by', 'create_time'],
    'ydy_station_archive' => ['id', 'station_uuid', 'archive_uuid'],
    'ydy_archive' => ['uuid', 'name'],
    'ydy_staff' => ['uuid', 'station_uuid', 'is_show'],
    'ydy_user' => ['id', 'uuid', 'account', 'pwd', 'salt', 'name', 'type',
        'create_time', 'create_by', 'is_delete'],
];
$metadata = [];
foreach ($required as $name => $columns) {
    $actual = $rows('SELECT COLUMN_NAME,DATA_TYPE,IS_NULLABLE AS NULLABLE,'
        . ' COLUMN_DEFAULT AS DATA_DEFAULT FROM information_schema.COLUMNS'
        . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?', [$name]);
    $names = array_column($actual, 'column_name');
    $assert(count(array_diff($columns, $names)) === 0, 'Required MySQL station columns absent: ' . $name);
    $metadata[$name] = ['required_columns_present' => true,
        'column_count' => count($actual),
        'index_count' => $count('SELECT COUNT(DISTINCT INDEX_NAME) N'
            . ' FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?', [$name])];
}
$stationMeta = [];
foreach ($rows('SELECT COLUMN_NAME,IS_NULLABLE AS NULLABLE,COLUMN_DEFAULT AS DATA_DEFAULT'
    . ' FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE()'
    . ' AND TABLE_NAME=? AND COLUMN_NAME IN (?,?,?,?)',
    ['ydy_station', 'status', 'quantity', 'name', 'create_by']) as $item) {
    $stationMeta[(string)$item['column_name']] = [
        'nullable' => (string)$item['nullable'],
        'has_default' => $item['data_default'] !== null,
    ];
}
$assert(($stationMeta['status']['has_default'] ?? false)
    && ($stationMeta['quantity']['has_default'] ?? false),
    'Station status/quantity defaults absent');
if ((string)getenv('POC_PREFLIGHT_ONLY') === '1') {
    echo json_encode(['suite' => 'store-station-mysql-' . $variant, 'mode' => 'read-only-preflight',
        'isolated' => true, 'tables' => $metadata, 'station_defaults' => $stationMeta,
        'existing_marker_station_rows' => $count('SELECT COUNT(*) N FROM ' . $table('station')
            . ' WHERE NAME LIKE ?', ['CDXPARSTAT%']),
        'existing_marker_operator_rows' => $count('SELECT COUNT(*) N FROM ' . $table('user')
            . ' WHERE ACCOUNT LIKE ?', ['CDXPARSTAT%'])], JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

require_once $root . '/application/base/Security.php';
$marker = 'CDXPARSTAT' . strtoupper(bin2hex(random_bytes(4)));
$nameA = $marker . 'A';
$nameB = $marker . 'B';
$operatorId = 0;
$stationId = 0;
$stationUuid = '';
$results = [];
$cleanup = [];
$failure = '';
try {
    $assert($count('SELECT COUNT(*) N FROM ' . $table('station') . ' WHERE NAME IN (?,?)',
        [$nameA, $nameB]) === 0, 'Synthetic station marker already present');
    $assert($count('SELECT COUNT(*) N FROM ' . $table('user')
        . ' WHERE UUID=? OR ACCOUNT=?', [$marker, $marker]) === 0,
        'Synthetic operator marker already present');
    $results['marker_before_zero'] = true;

    $unauth = $post('/p/station/add', ['name' => $nameA]);
    $assert($pass($unauth, 100)
        && $count('SELECT COUNT(*) N FROM ' . $table('station') . ' WHERE NAME=?', [$nameA]) === 0,
        'Unauthenticated station add not rejected without write');
    $results['unauth_add_rejected_zero_write'] = true;

    $insert = $db->prepare('INSERT INTO ' . $table('user')
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $insert->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
    $operatorId = (int)($one('SELECT ID FROM ' . $table('user')
        . ' WHERE UUID=? AND ACCOUNT=?', [$marker, $marker])['id'] ?? 0);
    $assert($operatorId > 0, 'Synthetic operator row missing');
    $token = \app\base\Security::encrypt(implode("\t",
        ['0', $marker, $marker, '0', '', '', $marker]));

    $invalid = $post('/p/station/add', ['name' => ''], $token);
    $assert($pass($invalid, 1)
        && $count('SELECT COUNT(*) N FROM ' . $table('station') . ' WHERE NAME=?', [$nameA]) === 0,
        'Missing station name not rejected without write');
    $results['missing_name_rejected_zero_write'] = true;

    $add = $post('/p/station/add', ['name' => $nameA, 'demo' => $marker], $token);
    $assert($pass($add), 'Station add HTTP/business response failed');
    $owned = $rows('SELECT ID,UUID,NAME,STATUS,QUANTITY,DEMO,CREATE_BY FROM ' . $table('station')
        . ' WHERE NAME=? AND CREATE_BY=?', [$nameA, $marker]);
    $assert(count($owned) === 1, 'Station add source row not unique');
    $station = $owned[0];
    $stationId = (int)$station['id'];
    $stationUuid = trim((string)$station['uuid']);
    $assert($stationId > 0 && $stationUuid !== '' && (int)$station['status'] === 1
        && (int)$station['quantity'] === 0 && (string)$station['demo'] === $marker,
        'Station add source values differ');
    $assert($count('SELECT COUNT(*) N FROM ' . $table('staff')
        . ' WHERE STATION_UUID=?', [$stationUuid]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
            . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
        'New station unexpectedly referenced');
    $results['add_source_readback_and_zero_links'] = true;

    $info = $post('/p/station/info', ['uuid' => $stationUuid], $token);
    $infoData = $info['json']['data'] ?? null;
    $assert($pass($info) && is_array($infoData)
        && trim((string)($infoData['uuid'] ?? '')) === $stationUuid
        && (string)($infoData['name'] ?? '') === $nameA
        && (int)($infoData['status'] ?? -1) === 1,
        'Station info response diverged from source');
    $results['info_contract_source_match'] = true;

    $list = $post('/p/station/lists', ['name' => $nameA, 'page' => 1], $token);
    $data = $list['json']['data'] ?? null;
    $listRows = is_array($data) ? ($data['rows'] ?? null) : null;
    $assert($pass($list) && is_array($listRows) && count($listRows) === 1
        && (int)($data['total'] ?? -1) === 1
        && trim((string)($listRows[0]['uuid'] ?? '')) === $stationUuid
        && (string)($listRows[0]['name'] ?? '') === $nameA
        && (int)($listRows[0]['quantity'] ?? -1) === 0
        && (int)($listRows[0]['is_quote'] ?? -1) === 0,
        'Station list response diverged from source/zero staff');
    $results['lists_filtered_source_match_zero_staff'] = true;

    $update = $post('/p/station/update', ['uuid' => $stationUuid,
        'name' => $nameB, 'demo' => $marker . 'B'], $token);
    $updated = $one('SELECT ID,UUID,NAME,STATUS,DEMO FROM ' . $table('station')
        . ' WHERE ID=? AND UUID=?', [$stationId, $stationUuid]);
    $assert($pass($update) && (string)($updated['name'] ?? '') === $nameB
        && (string)($updated['demo'] ?? '') === $marker . 'B'
        && (int)($updated['status'] ?? -1) === 1,
        'Station update response/source mismatch');
    $results['update_own_name_source_readback'] = true;

    $assert($count('SELECT COUNT(*) N FROM ' . $table('staff')
        . ' WHERE STATION_UUID=?', [$stationUuid]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
            . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
        'Station unexpectedly linked before status toggle');
    $off = $post('/p/station/able', ['uuid' => $stationUuid], $token);
    $offRow = $one('SELECT STATUS FROM ' . $table('station')
        . ' WHERE ID=? AND UUID=?', [$stationId, $stationUuid]);
    $assert($pass($off) && (int)($offRow['status'] ?? -1) === 0,
        'Station disable response/source mismatch');
    $results['able_disable_own_status'] = true;
    $on = $post('/p/station/able', ['uuid' => $stationUuid], $token);
    $onRow = $one('SELECT STATUS FROM ' . $table('station')
        . ' WHERE ID=? AND UUID=?', [$stationId, $stationUuid]);
    $assert($pass($on) && (int)($onRow['status'] ?? -1) === 1,
        'Station enable response/source mismatch');
    $results['able_enable_own_status'] = true;

    $assert($count('SELECT COUNT(*) N FROM ' . $table('staff')
        . ' WHERE STATION_UUID=?', [$stationUuid]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
            . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
        'Station linked before delete; refusing delete');
    $delete = $post('/p/station/delete', ['uuid' => $stationUuid], $token);
    $assert($pass($delete)
        && $count('SELECT COUNT(*) N FROM ' . $table('station')
            . ' WHERE ID=? AND UUID=?', [$stationId, $stationUuid]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
            . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
        'Station delete response/source mismatch');
    $results['delete_own_station_and_link_zero'] = true;
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    try {
        $owned = $rows('SELECT ID,UUID,NAME,CREATE_BY FROM ' . $table('station')
            . ' WHERE NAME IN (?,?) AND CREATE_BY=?', [$nameA, $nameB, $marker]);
        if (count($owned) > 1) throw new RuntimeException('Ambiguous station cleanup');
        if (count($owned) === 1) {
            $item = $owned[0];
            $id = (int)$item['id'];
            $uuid = trim((string)$item['uuid']);
            if ($stationId > 0 && ($id !== $stationId || $uuid !== $stationUuid)) {
                throw new RuntimeException('Station identity changed before cleanup');
            }
            if ($count('SELECT COUNT(*) N FROM ' . $table('staff') . ' WHERE STATION_UUID=?', [$uuid]) !== 0
                || $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
                    . ' WHERE STATION_UUID=?', [$uuid]) !== 0) {
                throw new RuntimeException('Station referenced; refusing exact cleanup');
            }
            $statement = $db->prepare('DELETE FROM ' . $table('station')
                . ' WHERE ID=? AND UUID=? AND NAME=? AND CREATE_BY=?');
            $statement->execute([$id, $uuid, (string)$item['name'], $marker]);
        }
        if ($operatorId > 0) {
            $statement = $db->prepare('DELETE FROM ' . $table('user')
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=?');
            $statement->execute([$operatorId, $marker, $marker]);
        }
        $cleanup = [
            'operator_zero' => $count('SELECT COUNT(*) N FROM ' . $table('user')
                . ' WHERE UUID=? OR ACCOUNT=?', [$marker, $marker]) === 0,
            'station_zero' => $count('SELECT COUNT(*) N FROM ' . $table('station')
                . ' WHERE NAME IN (?,?)', [$nameA, $nameB]) === 0,
            'staff_link_zero' => $stationUuid === '' || $count('SELECT COUNT(*) N FROM ' . $table('staff')
                . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
            'archive_link_zero' => $stationUuid === '' || $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
                . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
        ];
    } catch (Throwable $e) {
        $cleanup['error_class'] = get_class($e);
        $cleanup['error_sha256'] = hash('sha256', $e->getMessage());
    }
}
$clean = ($cleanup['operator_zero'] ?? false) && ($cleanup['station_zero'] ?? false)
    && ($cleanup['staff_link_zero'] ?? false) && ($cleanup['archive_link_zero'] ?? false);
echo json_encode(['suite' => 'store-station-mysql-' . $variant, 'dialect' => 'mysql', 'isolated' => true,
    'marker' => $marker, 'results' => $results, 'failure' => $failure, 'cleanup' => $cleanup],
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
if ($failure !== '' || !$clean) exit(1);
