<?php

declare(strict_types=1);

$root = (string)getenv('POC_TARGET_ROOT');
$baseUrl = rtrim((string)getenv('POC_BASE_URL'), '/');
$dialect = (string)getenv('POC_DIALECT');
if ($root === '' || $baseUrl === '' || !in_array($dialect, ['mysql', 'dm'], true)) {
    throw new RuntimeException('Isolated AI runtime, HTTP URL and dialect are required');
}
if ($dialect === 'mysql') {
    $config = parse_ini_file((string)getenv('POC_MYSQL_LOCAL_ENV'), true, INI_SCANNER_RAW);
    if (!is_array($config) || !isset($config['DATABASE'])) {
        throw new RuntimeException('Selected local MySQL config unavailable');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
        'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        putenv('POC_AI_' . $to . '=' . (string)$config['DATABASE'][$from]);
    }
}
foreach (['POC_AI_HOST', 'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER', 'POC_AI_PASSWORD'] as $key) {
    if (getenv($key) === false) throw new RuntimeException('AI database config incomplete');
}
$boundary = json_decode((string)file_get_contents($baseUrl . '/__poc_boundary'), true);
if (empty($boundary['external_stubs'])) {
    throw new RuntimeException('AI HTTP external-call boundary unavailable');
}
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();
set_exception_handler(static function (Throwable $error): void {
    fwrite(STDERR, 'ADDRESS_BOUNDARY_FAIL ' . get_class($error) . ': '
        . mb_substr($error->getMessage(), 0, 280) . PHP_EOL);
    exit(1);
});
$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';
$pdo = new PDO($dsn, getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$userTable = $dialect === 'dm' ? 'YOSHOP_USER' : 'yoshop_user';
$addressTable = $dialect === 'dm' ? 'YOSHOP_USER_ADDRESS' : 'yoshop_user_address';
$baseMarker = 'CDXPARAI' . strtoupper(bin2hex(random_bytes(4)));
$ownerMarker = $baseMarker . 'O';
$intruderMarker = $baseMarker . 'X';
$ownerToken = $baseMarker . 'OT' . strtoupper(bin2hex(random_bytes(10)));
$intruderToken = $baseMarker . 'XT' . strtoupper(bin2hex(random_bytes(10)));
$ownerId = 0;
$intruderId = 0;
$firstId = 0;
$secondId = 0;
$call = static function (string $method, string $action, ?string $token,
    ?string $addressId = null, array $form = []) use ($baseUrl): array {
    $query = ['storeId' => '10001'];
    if ($addressId !== null) $query['addressId'] = $addressId;
    $headers = "platform: POC\r\nContent-Type: application/x-www-form-urlencoded\r\n";
    if ($token !== null) $headers .= 'Access-Token: ' . $token . "\r\n";
    $options = ['method' => $method, 'header' => $headers,
        'ignore_errors' => true, 'timeout' => 20];
    if ($method === 'POST') $options['content'] = http_build_query(['form' => $form]);
    $body = file_get_contents($baseUrl . '/api/address/' . $action . '?'
        . http_build_query($query), false, stream_context_create(['http' => $options]));
    $statusLine = $http_response_header[0] ?? '';
    $json = is_string($body) ? json_decode($body, true) : null;
    if (!is_array($json)) {
        throw new RuntimeException('Non-JSON address response: ' . $action . ' ' . $statusLine);
    }
    unset($json['trace_id']);
    return ['http' => (int)(preg_match('/HTTP\/\S+\s+(\d+)/', $statusLine, $m)
        ? $m[1] : 0), 'body' => $json];
};
$read = static function (string $sql, array $args) use ($pdo): array {
    $q = $pdo->prepare($sql);
    $q->execute($args);
    $row = $q->fetch(PDO::FETCH_ASSOC);
    return $row === false ? [] : array_change_key_case($row);
};
$list = static function (int $userId) use ($pdo, $addressTable): array {
    $q = $pdo->prepare('SELECT ADDRESS_ID,NAME,DETAIL,USER_ID,IS_DELETE FROM '
        . $addressTable . ' WHERE USER_ID=? ORDER BY ADDRESS_ID');
    $q->execute([$userId]);
    return array_map('array_change_key_case', $q->fetchAll(PDO::FETCH_ASSOC));
};
$pointer = static function (int $userId) use ($read, $userTable): int {
    $row = $read("SELECT ADDRESS_ID FROM $userTable WHERE USER_ID=?", [$userId]);
    return (int)($row['address_id'] ?? -1);
};
$assert = static function (bool $condition, string $message): void {
    if (!$condition) throw new RuntimeException($message);
};
$form = static function (string $name): array {
    return ['name' => $name, 'phone' => '13900000000', 'detail' => '合成地址 12-3',
        'region' => [['value' => 1], ['value' => 2], ['value' => 3]]];
};
$status = static function (array $response): int {
    return (int)($response['body']['status'] ?? -1);
};
try {
    $insert = $pdo->prepare('INSERT INTO ' . $userTable
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,?,?,0,?)');
    $insert->execute(['', $ownerMarker, '0.00', '0.00', time()]);
    $ownerId = (int)$pdo->lastInsertId();
    $insert->execute(['', $intruderMarker, '0.00', '0.00', time()]);
    $intruderId = (int)$pdo->lastInsertId();
    $assert(min($ownerId, $intruderId) > 0 && $ownerId !== $intruderId
        && $list($ownerId) === [] && $list($intruderId) === [],
        'Two-user synthetic precondition failed');
    foreach ([[$ownerToken, $ownerId], [$intruderToken, $intruderId]] as $pair) {
        \think\facade\Cache::set($pair[0], ['user' => ['user_id' => $pair[1]],
            'password_version' => 0, 'login_type' => 'thirdparty'], 300);
    }
    foreach (['A', 'B'] as $suffix) {
        $added = $call('POST', 'add', $ownerToken, null,
            $form($ownerMarker . $suffix));
        $assert($status($added) === 200, 'Owner address creation failed');
    }
    $rows = $list($ownerId);
    $assert(count($rows) === 2 && $pointer($ownerId) === (int)$rows[0]['address_id'],
        'Owner address/default setup mismatch');
    $firstId = (int)$rows[0]['address_id'];
    $secondId = (int)$rows[1]['address_id'];
    $before = $rows;

    $cross = [
        'list' => $call('GET', 'list', $intruderToken),
        'detail' => $call('GET', 'detail', $intruderToken, (string)$firstId),
        'edit' => $call('POST', 'edit', $intruderToken, (string)$firstId,
            $form($intruderMarker . 'EDIT')),
        'setDefault' => $call('POST', 'setDefault', $intruderToken, (string)$firstId),
        'remove' => $call('POST', 'remove', $intruderToken, (string)$firstId),
    ];
    $assert($status($cross['list']) === 200
        && ($cross['list']['body']['data']['list'] ?? null) === []
        && $status($cross['detail']) !== 200
        && $status($cross['edit']) !== 200
        && $status($cross['setDefault']) !== 200
        && $status($cross['remove']) !== 200
        && $list($ownerId) === $before && $list($intruderId) === []
        && $pointer($ownerId) === $firstId && $pointer($intruderId) === 0,
        'Cross-user address isolation or original row readback failed');

    $invalid = [];
    foreach (['0', '-1', '999999999', 'not-an-id'] as $id) {
        $response = $call('GET', 'detail', $ownerToken, $id);
        $invalid[$id] = ['http' => $response['http'], 'status' => $status($response),
            'body_keys' => array_keys($response['body'])];
        $assert($status($response) !== 200 && $list($ownerId) === $before,
            'Invalid address ID accepted or changed rows');
    }
    $assert($pointer($ownerId) === $firstId, 'Invalid ID changed default pointer');

    $activeOverlongName = $ownerMarker . 'EDITED_DELETED';
    $activeOverlong = $call('POST', 'edit', $ownerToken, (string)$firstId,
        $form($activeOverlongName));
    $activeOverlongRow = $read('SELECT NAME FROM ' . $addressTable
        . ' WHERE ADDRESS_ID=?', [$firstId]);
    $restoreFirst = $call('POST', 'edit', $ownerToken, (string)$firstId,
        $form($ownerMarker . 'A'));
    $assert($status($restoreFirst) === 200 && $list($ownerId) === $before,
        'Active overlength boundary did not restore original synthetic row');

    $removed = $call('POST', 'remove', $ownerToken, (string)$secondId);
    $assert($status($removed) === 200 && $pointer($ownerId) === $firstId
        && (int)$list($ownerId)[1]['is_delete'] === 1,
        'Owner soft-delete setup failed');
    $afterDeleteList = $call('GET', 'list', $ownerToken);
    $assert($status($afterDeleteList) === 200
        && count((array)($afterDeleteList['body']['data']['list'] ?? [])) === 1,
        'Soft-deleted address remained in normal list');
    $deletedDetail = $call('GET', 'detail', $ownerToken, (string)$secondId);
    $deletedDefault = $call('POST', 'setDefault', $ownerToken, (string)$secondId);
    $pointerAfterDeletedDefault = $pointer($ownerId);
    $validDeletedName = $ownerMarker . 'ED';
    $deletedEditValid = $call('POST', 'edit', $ownerToken, (string)$secondId,
        $form($validDeletedName));
    $validEditedRow = $read('SELECT ADDRESS_ID,NAME,IS_DELETE FROM ' . $addressTable
        . ' WHERE ADDRESS_ID=?', [$secondId]);
    $overlongName = $ownerMarker . 'EDITED_DELETED';
    $deletedEditOverlong = $call('POST', 'edit', $ownerToken, (string)$secondId,
        $form($overlongName));
    $deletedRow = $read('SELECT ADDRESS_ID,NAME,IS_DELETE FROM ' . $addressTable
        . ' WHERE ADDRESS_ID=?', [$secondId]);
    $deletedRemoveAgain = $call('POST', 'remove', $ownerToken, (string)$secondId);
    $pointerAfterRepeatRemove = $pointer($ownerId);
    $assert((int)$deletedRow['is_delete'] === 1,
        'Deleted-address edit unexpectedly reactivated row');

    echo json_encode([
        'dialect' => $dialect, 'status' => 'tested', 'marker' => $baseMarker,
        'cross_user' => [
            'list_status' => $status($cross['list']),
            'detail_status' => $status($cross['detail']),
            'edit_status' => $status($cross['edit']),
            'set_default_status' => $status($cross['setDefault']),
            'remove_status' => $status($cross['remove']),
            'db_unchanged' => true,
        ],
        'invalid_ids' => $invalid,
        'active_overlong_name' => [
            'input_length' => strlen($activeOverlongName),
            'edit_status' => $status($activeOverlong),
            'edit_message' => mb_substr(str_replace($root, '[runtime]',
                (string)($activeOverlong['body']['message'] ?? '')), 0, 220),
            'stored_length' => strlen((string)$activeOverlongRow['name']),
            'exactly_persisted' => $activeOverlongRow['name'] === $activeOverlongName,
            'restore_status' => $status($restoreFirst),
        ],
        'deleted' => [
            'list_active_count' => 1,
            'detail_status' => $status($deletedDetail),
            'set_default_status' => $status($deletedDefault),
            'pointer_after_set_default' => $pointerAfterDeletedDefault === $secondId
                ? 'deleted_address' : 'other',
            'valid_edit_status' => $status($deletedEditValid),
            'valid_edit_persisted' => $validEditedRow['name'] === $validDeletedName,
            'overlong_name_length' => strlen($overlongName),
            'overlong_edit_status' => $status($deletedEditOverlong),
            'overlong_edit_message' => mb_substr(str_replace($root, '[runtime]',
                (string)($deletedEditOverlong['body']['message'] ?? '')), 0, 220),
            'overlong_stored_name_length' => strlen((string)$deletedRow['name']),
            'overlong_exactly_persisted' => $deletedRow['name'] === $overlongName,
            'repeat_remove_status' => $status($deletedRemoveAgain),
            'pointer_after_repeat_remove' => $pointerAfterRepeatRemove,
        ],
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
} finally {
    \think\facade\Cache::delete($ownerToken);
    \think\facade\Cache::delete($intruderToken);
    foreach ([[$ownerId, $ownerMarker], [$intruderId, $intruderMarker]] as $pair) {
        if ($pair[0] <= 0) continue;
        $pdo->prepare("DELETE FROM $addressTable WHERE USER_ID=?")->execute([$pair[0]]);
        $pdo->prepare("DELETE FROM $userTable WHERE USER_ID=? AND NICK_NAME=?")
            ->execute([$pair[0], $pair[1]]);
    }
    foreach ([[$ownerId, $ownerMarker], [$intruderId, $intruderMarker]] as $pair) {
        if ($pair[0] <= 0) continue;
        $userRemaining = $read("SELECT USER_ID FROM $userTable WHERE USER_ID=?", [$pair[0]]);
        $assert($list($pair[0]) === [] && $userRemaining === [],
            'Synthetic address/user cleanup left rows for ' . $pair[1]);
    }
    fwrite(STDERR, 'Synthetic address boundary cleanup: marker=' . $baseMarker
        . ' users=0 addresses=0' . PHP_EOL);
}
