<?php

declare(strict_types=1);

// Bounded A/B/C verification for the three ai_app sport-item write routes.
$root = (string)getenv('POC_TARGET_ROOT');
$baseUrl = rtrim((string)getenv('POC_BASE_URL'), '/');
$dialect = (string)getenv('POC_DIALECT');
$mode = (string)($argv[1] ?? '--preflight');
if ($root === '' || $baseUrl === '' || !in_array($dialect, ['mysql', 'dm'], true)
    || !in_array($mode, ['--preflight', '--execute', '--invalid-date', '--audit'], true)) {
    throw new RuntimeException('Explicit isolated target and mode required');
}
if ($dialect === 'mysql') {
    $local = parse_ini_file((string)getenv('POC_MYSQL_LOCAL_ENV'), true, INI_SCANNER_RAW);
    if (!is_array($local) || !isset($local['DATABASE'])) {
        throw new RuntimeException('Code .env.local development AI database unavailable');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
        'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        putenv('POC_AI_' . $to . '=' . (string)$local['DATABASE'][$from]);
    }
} else {
    $pid = (int)getenv('POC_IMPORT_SERVER_PID');
    $command = $pid > 0 ? @file_get_contents('/proc/' . $pid . '/cmdline') : false;
    if (!is_string($command) || strpos($command, '127.0.0.1:18102') === false
        || strpos($command, $root . '/public') === false) {
        throw new RuntimeException('Verified isolated C HTTP process required');
    }
    $serverEnv = @file_get_contents('/proc/' . $pid . '/environ');
    if (!is_string($serverEnv)) throw new RuntimeException('C process environment unavailable');
    foreach (explode("\0", $serverEnv) as $entry) {
        if (strpos($entry, 'POC_AI_') === 0) putenv($entry);
    }
}
foreach (['POC_AI_HOST', 'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER',
    'POC_AI_PASSWORD'] as $part) {
    if ((string)getenv($part) === '') throw new RuntimeException('Selected AI database incomplete');
}
if ($dialect === 'dm' && ((string)getenv('POC_AI_SCHEMA') !== 'AIZHCT_APP'
    || (string)getenv('POC_AI_PORT') !== '5238')) {
    throw new RuntimeException('Unexpected C database identity');
}
$boundary = json_decode((string)@file_get_contents($baseUrl . '/__poc_boundary'), true);
if (empty($boundary['external_stubs'])) throw new RuntimeException('External stubs 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 = $dialect === 'dm'
    ? 'dm:host=' . getenv('POC_AI_HOST') . ';port=' . getenv('POC_AI_PORT')
        . ';schema=' . getenv('POC_AI_SCHEMA')
    : 'mysql:host=' . getenv('POC_AI_HOST') . ';port=' . getenv('POC_AI_PORT')
        . ';dbname=' . getenv('POC_AI_SCHEMA') . ';charset=utf8mb4';
$db = new PDO($dsn, getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$table = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? strtoupper($name) : $name;
};
$tables = [
    'user' => $table('yoshop_user'),
    'staff' => $table('yoshop_staff'),
    'item' => $table('yoshop_sport_item'),
    'record' => $table('yoshop_user_sport_item'),
    'energy' => $table('yoshop_user_sport_energy'),
];
$columns = [];
foreach ($tables as $kind => $name) {
    $columns[$kind] = $db->query("SELECT * FROM $name WHERE 1=0")->columnCount();
}
if ($mode === '--preflight') {
    echo json_encode(['mode' => 'preflight', 'dialect' => $dialect,
        'schema' => (string)getenv('POC_AI_SCHEMA'), 'columns' => $columns,
        'external_stubs' => true], JSON_UNESCAPED_UNICODE) . PHP_EOL;
    exit(0);
}
if ($mode === '--audit') {
    $marker = (string)($argv[2] ?? '');
    $ids = array_map('intval', array_slice($argv, 3, 5));
    if (!preg_match('/^CDXPARSPORT[0-9A-F]{8}$/', $marker)
        || count($ids) !== 5 || min(array_slice($ids, 0, 3)) <= 0
        || $ids[3] < 0 || $ids[4] < 0) {
        throw new RuntimeException('Exact synthetic marker and five IDs required for audit');
    }
    [$userId, $staffId, $itemId, $recordId, $energyId] = $ids;
    $counts = [];
    foreach ([
        'user' => ["SELECT COUNT(*) FROM {$tables['user']} WHERE USER_ID=? AND NICK_NAME=?",
            [$userId, $marker]],
        'staff' => ["SELECT COUNT(*) FROM {$tables['staff']} WHERE ID=? AND USER_ID=?",
            [$staffId, $userId]],
        'item' => ["SELECT COUNT(*) FROM {$tables['item']} WHERE ID=? AND NAME=?",
            [$itemId, '慢跑兼容验收' . $marker]],
        'record' => ["SELECT COUNT(*) FROM {$tables['record']} WHERE USER_ID=?",
            [$userId]],
        'energy' => ["SELECT COUNT(*) FROM {$tables['energy']} WHERE USER_ID=?",
            [$userId]],
    ] as $kind => [$sql, $args]) {
        $statement = $db->prepare($sql);
        $statement->execute($args);
        $counts[$kind] = (int)$statement->fetchColumn();
    }
    if (array_sum($counts) !== 0) throw new RuntimeException('Independent cleanup audit failed');
    echo json_encode(['mode' => 'audit', 'dialect' => $dialect,
        'marker' => $marker, 'remaining_rows' => $counts], JSON_UNESCAPED_UNICODE) . PHP_EOL;
    exit(0);
}

$read = static function (string $sql, array $args = []) use ($db): array {
    $statement = $db->prepare($sql);
    $statement->execute($args);
    $row = $statement->fetch(PDO::FETCH_ASSOC);
    return $row === false ? [] : array_change_key_case($row);
};
$count = static function (string $sql, array $args = []) use ($db): int {
    $statement = $db->prepare($sql);
    $statement->execute($args);
    return (int)$statement->fetchColumn();
};
$assert = static function (bool $ok, string $message): void {
    if (!$ok) throw new RuntimeException($message);
};
$transportUnknown = false;
$request = static function (string $action, string $method, string $token, array $payload)
    use ($baseUrl, &$transportUnknown): array {
    $headers = "platform: POC\r\n";
    if ($method === 'POST') $headers .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $headers .= 'Access-Token: ' . $token . "\r\n";
    $url = $baseUrl . '/api/sport.item/' . $action . '?storeId=10001';
    if ($method === 'GET') $url .= '&' . http_build_query($payload);
    $context = stream_context_create(['http' => [
        'method' => $method, 'header' => $headers,
        'content' => $method === 'POST' ? http_build_query(['form' => $payload]) : '',
        'timeout' => 35, 'ignore_errors' => true,
        'follow_location' => 0,
    ]]);
    $started = microtime(true);
    $raw = @file_get_contents($url, false, $context);
    $httpLine = $http_response_header[0] ?? '';
    $body = is_string($raw) ? json_decode($raw, true) : null;
    if (!is_array($body)) {
        $transportUnknown = true;
        throw new RuntimeException('Transport/body uncertain at sport.item/' . $action);
    }
    return ['http' => (int)(preg_match('/HTTP\/\S+\s+(\d+)/', $httpLine, $m) ? $m[1] : 0),
        'status' => (int)($body['status'] ?? -1),
        'message' => (string)($body['message'] ?? ''),
        'data' => $body['data'] ?? null,
        'elapsed_ms' => (int)round((microtime(true) - $started) * 1000)];
};
$shape = static function (array $response): array {
    return ['http' => $response['http'], 'status' => $response['status'],
        'data_type' => gettype($response['data']),
        'data_keys' => is_array($response['data']) ? array_keys($response['data']) : [],
        'message' => $response['message'], 'elapsed_ms' => $response['elapsed_ms']];
};
$marker = 'CDXPARSPORT' . strtoupper(bin2hex(random_bytes(4)));
$staffName = '兼容验收' . $marker;
$itemName = '慢跑兼容验收' . $marker;
$token = $marker . 'T' . strtoupper(bin2hex(random_bytes(10)));
$today = date('Y-m-d');
$now = time();
$userId = 0;
$recordId = 0;
$itemId = 0;
$staffId = 0;
$state = static function () use ($read, $count, $tables, &$userId, $today): array {
    $record = $read("SELECT ID,USER_ID,STORE_ID,SPORT_ITEM_ID,DATE,HEIGHT,WEIGHT,"
        . "ACTUAL_DURATION,ACTUAL_CONSUME FROM {$tables['record']} WHERE USER_ID=? AND DATE=?",
        [$userId, $today]);
    $energy = $read("SELECT ID,USER_ID,STORE_ID,DATE,TYPE,ENERGY"
        . " FROM {$tables['energy']} WHERE USER_ID=? AND DATE=? AND TYPE=4",
        [$userId, $today]);
    return ['record_count' => $count("SELECT COUNT(*) FROM {$tables['record']} WHERE USER_ID=?",
            [$userId]),
        'energy_count' => $count("SELECT COUNT(*) FROM {$tables['energy']} WHERE USER_ID=? AND TYPE=4",
            [$userId]), 'record' => $record, 'energy' => $energy];
};
try {
    $db->prepare("INSERT INTO {$tables['user']}"
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,?,?,0,?)')
        ->execute(['', $marker, '0.00', '0.00', $now]);
    $userId = (int)($read("SELECT USER_ID FROM {$tables['user']} WHERE NICK_NAME=?",
        [$marker])['user_id'] ?? 0);
    $assert($userId > 0, 'Synthetic user not created');
    $db->prepare("INSERT INTO {$tables['staff']}"
        . " (NAME,USER_ID,STORE_ID,HEIGHT,WEIGHT,GENDER,BIRTHDAY,PHONE,IS_DELETE)"
        . " VALUES (?,?,10001,170,60,1,?,'',0)")
        ->execute([$staffName, $userId, '1990-01-01']);
    $staffId = (int)($read("SELECT ID FROM {$tables['staff']} WHERE NAME=? AND USER_ID=?",
        [$staffName, $userId])['id'] ?? 0);
    $db->prepare("INSERT INTO {$tables['item']}"
        . ' (STORE_ID,NAME,INTENSITY,IS_DELETE,CREATE_TIME) VALUES (10001,?,1.25,0,?)')
        ->execute([$itemName, $now]);
    $itemId = (int)($read("SELECT ID FROM {$tables['item']} WHERE NAME=?",
        [$itemName])['id'] ?? 0);
    $assert($staffId > 0 && $itemId > 0 && $state()['record_count'] === 0,
        'Synthetic sport fixture incomplete');
    \think\facade\Cache::set($token, ['user' => ['user_id' => $userId],
        'password_version' => 0, 'login_type' => 'thirdparty'], 300);

    if ($mode === '--invalid-date') {
        $invalid = $request('record', 'POST', $token,
            ['sport_item_id' => $itemId, 'actual_duration' => 45,
                'date' => '2026-02-30']);
        $invalidRecord = $read("SELECT ID,USER_ID,STORE_ID,SPORT_ITEM_ID,DATE,"
            . "ACTUAL_DURATION,ACTUAL_CONSUME FROM {$tables['record']}"
            . " WHERE USER_ID=? ORDER BY ID", [$userId]);
        $invalidEnergy = $read("SELECT ID,USER_ID,STORE_ID,DATE,TYPE,ENERGY"
            . " FROM {$tables['energy']} WHERE USER_ID=? ORDER BY ID", [$userId]);
        $invalidRecordCount = $count("SELECT COUNT(*) FROM {$tables['record']} WHERE USER_ID=?",
            [$userId]);
        $invalidEnergyCount = $count("SELECT COUNT(*) FROM {$tables['energy']} WHERE USER_ID=?",
            [$userId]);
        $assert($invalid['http'] === 200 && $invalidRecordCount <= 1
            && $invalidEnergyCount <= 1,
            'Invalid-date probe uncertain HTTP/row cardinality');
        echo json_encode(['mode' => 'invalid-date', 'dialect' => $dialect,
            'marker' => $marker,
            'fixture_ids' => ['user' => $userId, 'staff' => $staffId,
                'item' => $itemId, 'record' => (int)($invalidRecord['id'] ?? 0),
                'energy' => (int)($invalidEnergy['id'] ?? 0)],
            'response' => $shape($invalid),
            'readback' => ['record_rows' => $invalidRecordCount,
                'energy_rows' => $invalidEnergyCount,
                'record_date' => (string)($invalidRecord['date'] ?? ''),
                'energy_date' => (string)($invalidEnergy['date'] ?? ''),
                'duration' => (int)($invalidRecord['actual_duration'] ?? 0)]],
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
    } else {
    $recordResponse = $request('record', 'POST', $token,
        ['sport_item_id' => $itemId, 'actual_duration' => 45, 'date' => $today]);
    $afterRecord = $state();
    $recordId = (int)($afterRecord['record']['id'] ?? 0);
    $assert($recordResponse['http'] === 200 && $recordResponse['status'] === 200
        && $afterRecord['record_count'] === 1 && $afterRecord['energy_count'] === 1
        && $recordId > 0
        && (int)($afterRecord['record']['store_id'] ?? 0) === 10001
        && (int)($afterRecord['record']['user_id'] ?? 0) === $userId
        && (int)($afterRecord['record']['sport_item_id'] ?? 0) === $itemId
        && (int)($afterRecord['record']['actual_duration'] ?? 0) === 45
        && (int)($afterRecord['energy']['type'] ?? 0) === 4
        && abs((float)($afterRecord['energy']['energy'] ?? -1)
            - (float)($afterRecord['record']['actual_consume'] ?? -2)) < 0.001,
        'record HTTP/original-table mismatch');
    $daily1 = $request('daily', 'GET', $token, ['date' => $today]);
    $dailyList1 = (array)($daily1['data']['list'] ?? []);
    $assert($daily1['http'] === 200 && $daily1['status'] === 200
        && count($dailyList1) === 1
        && (int)($dailyList1[0]['id'] ?? 0) === $recordId
        && (int)($dailyList1[0]['actual_duration'] ?? 0) === 45,
        'daily readback after record mismatch');

    $updateResponse = $request('update', 'POST', $token,
        ['user_sport_item_id' => $recordId, 'actual_duration' => 75]);
    $afterUpdate = $state();
    $assert($updateResponse['http'] === 200 && $updateResponse['status'] === 200
        && $afterUpdate['record_count'] === 1 && $afterUpdate['energy_count'] === 1
        && (int)($afterUpdate['record']['id'] ?? 0) === $recordId
        && (int)($afterUpdate['record']['actual_duration'] ?? 0) === 75
        && abs((float)($afterUpdate['energy']['energy'] ?? -1)
            - (float)($afterUpdate['record']['actual_consume'] ?? -2)) < 0.001,
        'update HTTP/original-table mismatch');
    $daily2 = $request('daily', 'GET', $token, ['date' => $today]);
    $dailyList2 = (array)($daily2['data']['list'] ?? []);
    $assert($daily2['http'] === 200 && $daily2['status'] === 200
        && count($dailyList2) === 1
        && (int)($dailyList2[0]['id'] ?? 0) === $recordId
        && (int)($dailyList2[0]['actual_duration'] ?? 0) === 75,
        'daily readback after update mismatch');

    $deleteResponse = $request('delete', 'POST', $token,
        ['user_sport_item_id' => $recordId]);
    $afterDelete = $state();
    $assert($deleteResponse['http'] === 200 && $deleteResponse['status'] === 200
        && $afterDelete['record_count'] === 0
        && $afterDelete['energy_count'] === 1
        && abs((float)($afterDelete['energy']['energy'] ?? -1)) < 0.001,
        'delete HTTP/original-table mismatch');

    echo json_encode(['mode' => 'execute', 'dialect' => $dialect,
        'status' => 'passed', 'marker' => $marker,
        'fixture_ids' => ['user' => $userId, 'staff' => $staffId,
            'item' => $itemId, 'record' => $recordId,
            'energy' => (int)$afterRecord['energy']['id']],
        'response' => ['record' => $shape($recordResponse),
            'daily_after_record' => $shape($daily1), 'update' => $shape($updateResponse),
            'daily_after_update' => $shape($daily2), 'delete' => $shape($deleteResponse)],
        'readback' => [
            'record_duration' => (int)$afterRecord['record']['actual_duration'],
            'updated_duration' => (int)$afterUpdate['record']['actual_duration'],
            'record_energy' => (float)$afterRecord['energy']['energy'],
            'updated_energy' => (float)$afterUpdate['energy']['energy'],
            'deleted_remaining_energy' => (float)$afterDelete['energy']['energy'],
            'record_rows_after_delete' => $afterDelete['record_count']],
        'daily_type_contract' => [
            'total_duration' => gettype($daily2['data']['total_duration'] ?? null),
            'total_consume' => gettype($daily2['data']['total_consume'] ?? null),
            'item' => array_map('gettype', $dailyList2[0])]],
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
    }
} finally {
    if ($transportUnknown) {
        fwrite(STDERR, 'Transport unknown; fixture retained for manual bounded recovery: '
            . $marker . PHP_EOL);
    } else {
        \think\facade\Cache::delete($token);
        $db->prepare("DELETE FROM {$tables['energy']} WHERE USER_ID=? AND TYPE=4")
            ->execute([$userId]);
        $db->prepare("DELETE FROM {$tables['record']} WHERE USER_ID=?")
            ->execute([$userId]);
        $db->prepare("DELETE FROM {$tables['item']} WHERE NAME=?")
            ->execute([$itemName]);
        $db->prepare("DELETE FROM {$tables['staff']} WHERE NAME=? AND USER_ID=?")
            ->execute([$staffName, $userId]);
        $db->prepare("DELETE FROM {$tables['user']} WHERE NICK_NAME=? AND USER_ID=?")
            ->execute([$marker, $userId]);
        $left = $count("SELECT COUNT(*) FROM {$tables['energy']} WHERE USER_ID=?", [$userId])
            + $count("SELECT COUNT(*) FROM {$tables['record']} WHERE USER_ID=?", [$userId])
            + $count("SELECT COUNT(*) FROM {$tables['item']} WHERE NAME=?", [$itemName])
            + $count("SELECT COUNT(*) FROM {$tables['staff']} WHERE NAME=?", [$staffName])
            + $count("SELECT COUNT(*) FROM {$tables['user']} WHERE NICK_NAME=?", [$marker]);
        if ($left !== 0) throw new RuntimeException('Synthetic sport cleanup left rows=' . $left);
        fwrite(STDERR, 'Synthetic sport cleanup rows=0' . PHP_EOL);
    }
}
