<?php

declare(strict_types=1);

// Approved local-development MySQL Store only. All business rows are new CDXPAR fixtures.
$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')
    || !is_file($root . '/application/p/controller/Archive.php')
    || !preg_match('~^http://127\.0\.0\.1:[0-9]+$~', $base)) {
    throw new RuntimeException('Approved isolated Store MySQL runtime missing');
}
foreach (['p/controller/Station.php' => 'bce73e23130e9a2114d9788854b68bef2227eb118d58560afbee1aa88a0808e5',
    'p/logic/Station.php' => '9e28dcc5d35c3a8354859e8e82252bf3dcf07b8d8c4a1ce413cb7f10e5a3f74b',
    'p/controller/Archive.php' => 'b28e7773f29afff17980692ff8def8c7b0f20fb485fe6a9d8788698ecbec7fde',
    'p/logic/Archive.php' => 'cfcec4c0be80264e4ca4b108d22bcf750ddc2d8fb9acfeec9721ccc0acfb8175',
    'model/StationArchive.php' => '230ba81c12f310d5dfd390e0ca8f50797108133d6ac23ff17dee8a9040f6acc0'] as $file => $expected) {
    if (!hash_equals($expected, hash_file('sha256', $root . '/application/' . $file))) {
        throw new RuntimeException('Station/archive runtime source 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 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 {
    $statement = $db->prepare($sql);
    $statement->execute($args);
    return $statement->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);
    $headersOut = $http_response_header ?? [];
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($headersOut[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, ?string $message = null): bool {
    return $response['http'] === 200 && is_array($response['json'])
        && (int)($response['json']['code'] ?? -1) === $businessCode
        && ($message === null || (string)($response['json']['message'] ?? '') === $message);
};

$required = [
    'ydy_station' => ['id', 'uuid', 'name', 'status', 'create_by'],
    'ydy_archive' => ['id', 'uuid', 'name', 'order_no', 'status', 'link', 'is_show', 'create_by'],
    'ydy_station_archive' => ['id', 'station_uuid', 'archive_uuid', 'create_by'],
    'ydy_staff' => ['uuid', 'station_uuid'],
    'ydy_user' => ['id', 'uuid', 'account', 'name', 'type'],
];
$metadata = [];
foreach ($required as $name => $fields) {
    $columns = $rows('SELECT COLUMN_NAME,IS_NULLABLE AS NULLABLE,COLUMN_DEFAULT AS DATA_DEFAULT'
        . ' FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?', [$name]);
    $assert(count(array_diff($fields, array_column($columns, 'column_name'))) === 0,
        'Required MySQL relation table columns absent: ' . $name);
    $metadata[$name] = [
        'columns' => count($columns),
        'indexes' => $count('SELECT COUNT(DISTINCT INDEX_NAME) N FROM information_schema.STATISTICS'
            . ' WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?', [$name]),
        'foreign_keys' => $count('SELECT COUNT(DISTINCT CONSTRAINT_NAME) N'
            . ' FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE()'
            . ' AND TABLE_NAME=? AND REFERENCED_TABLE_NAME IS NOT NULL', [$name]),
        'triggers' => $count('SELECT COUNT(*) N FROM information_schema.TRIGGERS'
            . ' WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?', [$name]),
    ];
}
$archiveDefaults = [];
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_archive', 'order_no', 'status', 'is_show', 'link']) as $column) {
    $archiveDefaults[(string)$column['column_name']] = [
        'nullable' => (string)$column['nullable'],
        'has_default' => $column['data_default'] !== null,
    ];
}
$assert(($archiveDefaults['link']['nullable'] ?? '') === 'NO', 'Archive link field unexpectedly nullable');
if ((string)getenv('POC_PREFLIGHT_ONLY') === '1') {
    echo json_encode(['suite' => 'station-archive-mysql-' . $variant, 'mode' => 'read-only-preflight',
        'isolated' => true, 'tables' => $metadata, 'archive_defaults' => $archiveDefaults,
        'station_marker_rows' => $count('SELECT COUNT(*) N FROM ' . $table('station')
            . ' WHERE NAME LIKE ?', ['CDXPARSTAT%']),
        'archive_marker_rows' => $count('SELECT COUNT(*) N FROM ' . $table('archive')
            . ' WHERE NAME LIKE ?', ['CDXPARARCH%'])], JSON_UNESCAPED_SLASHES) . PHP_EOL;
    exit(0);
}

require_once $root . '/application/base/Security.php';
$suffix = strtoupper(bin2hex(random_bytes(4)));
$operator = 'CDXPARSA' . $suffix;
$stationName = 'CDXPARSTAT' . $suffix . 'A';
$archiveName = 'CDXPARARCH' . $suffix . 'A';
$operatorId = 0;
$stationId = 0;
$archiveId = 0;
$stationUuid = '';
$archiveUuid = '';
$results = [];
$cleanup = [];
$failure = '';
try {
    $assert(strlen($stationName) <= 20 && strlen($archiveName) <= 20,
        'Synthetic names exceed validator limits');
    $assert($count('SELECT COUNT(*) N FROM ' . $table('user')
        . ' WHERE UUID=? OR ACCOUNT=?', [$operator, $operator]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station')
            . ' WHERE NAME=?', [$stationName]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('archive')
            . ' WHERE NAME=?', [$archiveName]) === 0,
        'Synthetic marker already exists');
    $results['marker_before_zero'] = true;

    $unauthShow = $post('/p/station/show', []);
    $unauthLink = $post('/p/station/addArchive', ['uuid' => 'INVALID', 'archive_uuid' => 'INVALID']);
    $assert($pass($unauthShow, 100) && $pass($unauthLink, 100),
        'Station show/addArchive unauthenticated gate failed');
    $results['unauth_station_routes_rejected'] = true;

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

    $addArchiveType = $post('/p/archive/add', ['name' => $archiveName,
        'order_no' => '9999', 'link' => '/codex-test/' . $archiveName,
        'demo' => $archiveName], $token);
    $assert($pass($addArchiveType, 0, '添加成功'), 'Synthetic archive add HTTP contract failed');
    $archiveRows = $rows('SELECT ID,UUID,NAME,STATUS,LINK,IS_SHOW,CREATE_BY'
        . ' FROM ' . $table('archive') . ' WHERE NAME=? AND CREATE_BY=?',
        [$archiveName, $operator]);
    $assert(count($archiveRows) === 1, 'Synthetic archive row missing/ambiguous');
    $archiveId = (int)$archiveRows[0]['id'];
    $archiveUuid = trim((string)$archiveRows[0]['uuid']);
    $assert($archiveId > 0 && $archiveUuid !== ''
        && (int)$archiveRows[0]['status'] === 1
        && (int)$archiveRows[0]['is_show'] === 1
        && (string)$archiveRows[0]['link'] === '/codex-test/' . $archiveName,
        'Synthetic archive values mismatch');
    $results['archive_add_source_readback'] = true;

    $addStation = $post('/p/station/add', ['name' => $stationName,
        'demo' => $stationName], $token);
    $assert($pass($addStation, 0, '添加成功'), 'Synthetic station add HTTP contract failed');
    $stationRows = $rows('SELECT ID,UUID,NAME,STATUS,CREATE_BY FROM ' . $table('station')
        . ' WHERE NAME=? AND CREATE_BY=?', [$stationName, $operator]);
    $assert(count($stationRows) === 1, 'Synthetic station row missing/ambiguous');
    $stationId = (int)$stationRows[0]['id'];
    $stationUuid = trim((string)$stationRows[0]['uuid']);
    $assert($stationId > 0 && $stationUuid !== '' && (int)$stationRows[0]['status'] === 1,
        'Synthetic station values mismatch');
    $assert($count('SELECT COUNT(*) N FROM ' . $table('staff')
        . ' WHERE STATION_UUID=?', [$stationUuid]) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
            . ' WHERE STATION_UUID=? OR ARCHIVE_UUID=?', [$stationUuid, $archiveUuid]) === 0,
        'Synthetic station/archive already referenced');
    $results['station_add_zero_links_and_staff'] = true;

    $show = $post('/p/station/show', [], $token);
    $shown = $show['json']['data'] ?? null;
    $assert($pass($show, 0, '查询成功') && is_array($shown),
        'Station show HTTP contract failed');
    $seen = 0;
    foreach ($shown as $item) {
        if (is_array($item) && trim((string)($item['uuid'] ?? '')) === $stationUuid
            && (string)($item['name'] ?? '') === $stationName) ++$seen;
    }
    $assert($seen === 1, 'Synthetic station absent/duplicate in show');
    $results['station_show_contains_only_own_asserted_row'] = true;

    $link = $post('/p/station/addArchive', ['uuid' => $stationUuid,
        'archive_uuid' => $archiveUuid], $token);
    $linkRows = $rows('SELECT ID,STATION_UUID,ARCHIVE_UUID,CREATE_BY FROM '
        . $table('station_archive') . ' WHERE STATION_UUID=? OR ARCHIVE_UUID=?',
        [$stationUuid, $archiveUuid]);
    $assert($pass($link, 0, '添加成功') && count($linkRows) === 1
        && trim((string)$linkRows[0]['station_uuid']) === $stationUuid
        && trim((string)$linkRows[0]['archive_uuid']) === $archiveUuid
        && (string)$linkRows[0]['create_by'] === $operator,
        'Station addArchive HTTP code/message or exact relation failed');
    $results['station_add_archive_controller_contract_and_one_relation'] = true;

    $assert($count('SELECT COUNT(*) N FROM ' . $table('staff')
        . ' WHERE STATION_UUID=?', [$stationUuid]) === 0,
        'Synthetic station unexpectedly acquired staff');
    $deleteArchive = $post('/p/archive/delete', ['uuid' => $archiveUuid], $token);
    $afterDelete = $one('SELECT IS_SHOW FROM ' . $table('archive')
        . ' WHERE ID=? AND UUID=?', [$archiveId, $archiveUuid]);
    $assert($pass($deleteArchive, 0, '删除成功')
        && (int)($afterDelete['is_show'] ?? -1) === 0
        && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
            . ' WHERE STATION_UUID=? OR ARCHIVE_UUID=?', [$stationUuid, $archiveUuid]) === 0,
        'Archive delete did not soft-delete and unlink only own relation');
    $results['archive_delete_soft_and_relation_zero'] = true;
} catch (Throwable $e) {
    $failure = get_class($e) . ':' . hash('sha256', $e->getMessage());
} finally {
    try {
        $stationOwned = $rows('SELECT ID,UUID,NAME,CREATE_BY FROM ' . $table('station')
            . ' WHERE NAME=? AND CREATE_BY=?', [$stationName, $operator]);
        $archiveOwned = $rows('SELECT ID,UUID,NAME,CREATE_BY FROM ' . $table('archive')
            . ' WHERE NAME=? AND CREATE_BY=?', [$archiveName, $operator]);
        if (count($stationOwned) > 1 || count($archiveOwned) > 1) {
            throw new RuntimeException('Ambiguous synthetic relation cleanup');
        }
        if (count($stationOwned) === 1) {
            $item = $stationOwned[0];
            if ($stationId > 0 && ((int)$item['id'] !== $stationId
                || trim((string)$item['uuid']) !== $stationUuid)) {
                throw new RuntimeException('Synthetic station identity changed');
            }
            $stationUuid = trim((string)$item['uuid']);
            $stationId = (int)$item['id'];
        }
        if (count($archiveOwned) === 1) {
            $item = $archiveOwned[0];
            if ($archiveId > 0 && ((int)$item['id'] !== $archiveId
                || trim((string)$item['uuid']) !== $archiveUuid)) {
                throw new RuntimeException('Synthetic archive identity changed');
            }
            $archiveUuid = trim((string)$item['uuid']);
            $archiveId = (int)$item['id'];
        }
        if ($stationUuid !== '' && $count('SELECT COUNT(*) N FROM ' . $table('staff')
            . ' WHERE STATION_UUID=?', [$stationUuid]) !== 0) {
            throw new RuntimeException('Station acquired staff; refusing cleanup');
        }
        if ($stationUuid !== '' && $archiveUuid !== '') {
            $links = $rows('SELECT ID,STATION_UUID,ARCHIVE_UUID FROM ' . $table('station_archive')
                . ' WHERE STATION_UUID=? OR ARCHIVE_UUID=?', [$stationUuid, $archiveUuid]);
            foreach ($links as $item) {
                if (trim((string)$item['station_uuid']) !== $stationUuid
                    || trim((string)$item['archive_uuid']) !== $archiveUuid) {
                    throw new RuntimeException('Unexpected cross-fixture relation; refusing cleanup');
                }
            }
            foreach ($links as $item) {
                $statement = $db->prepare('DELETE FROM ' . $table('station_archive')
                    . ' WHERE ID=? AND STATION_UUID=? AND ARCHIVE_UUID=?');
                $statement->execute([(int)$item['id'], $stationUuid, $archiveUuid]);
            }
        }
        if (count($stationOwned) === 1) {
            $statement = $db->prepare('DELETE FROM ' . $table('station')
                . ' WHERE ID=? AND UUID=? AND NAME=? AND CREATE_BY=?');
            $statement->execute([$stationId, $stationUuid, $stationName, $operator]);
        }
        if (count($archiveOwned) === 1) {
            $statement = $db->prepare('DELETE FROM ' . $table('archive')
                . ' WHERE ID=? AND UUID=? AND NAME=? AND CREATE_BY=?');
            $statement->execute([$archiveId, $archiveUuid, $archiveName, $operator]);
        }
        if ($operatorId > 0) {
            $statement = $db->prepare('DELETE FROM ' . $table('user')
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=?');
            $statement->execute([$operatorId, $operator, $operator]);
        }
        $cleanup = [
            'operator_zero' => $count('SELECT COUNT(*) N FROM ' . $table('user')
                . ' WHERE UUID=? OR ACCOUNT=?', [$operator, $operator]) === 0,
            'station_zero' => $count('SELECT COUNT(*) N FROM ' . $table('station')
                . ' WHERE NAME=?', [$stationName]) === 0,
            'archive_zero' => $count('SELECT COUNT(*) N FROM ' . $table('archive')
                . ' WHERE NAME=?', [$archiveName]) === 0,
            'relation_zero' => ($stationUuid === '' && $archiveUuid === '')
                || ($stationUuid !== '' && $archiveUuid !== ''
                    && $count('SELECT COUNT(*) N FROM ' . $table('station_archive')
                        . ' WHERE STATION_UUID=? OR ARCHIVE_UUID=?', [$stationUuid, $archiveUuid]) === 0),
            'staff_zero' => $stationUuid === '' || $count('SELECT COUNT(*) N FROM ' . $table('staff')
                . ' 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['archive_zero'] ?? false) && ($cleanup['relation_zero'] ?? false)
    && ($cleanup['staff_zero'] ?? false);
echo json_encode(['suite' => 'station-archive-mysql-' . $variant, 'dialect' => 'mysql', 'isolated' => true,
    'marker_suffix' => $suffix, 'results' => $results, 'failure' => $failure,
    'cleanup' => $cleanup], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
if ($failure !== '' || !$clean) exit(1);
