<?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 address HTTP target required');
}
if ($dialect === 'mysql') {
    $config = parse_ini_file((string)getenv('POC_MYSQL_LOCAL_ENV'), true, INI_SCANNER_RAW);
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
        'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        putenv('POC_AI_' . $to . '=' . (string)$config['DATABASE'][$from]);
    }
}
$boundary = json_decode((string)file_get_contents($baseUrl . '/__poc_boundary'), true);
if (empty($boundary['external_stubs'])) throw new RuntimeException('External 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_LIMIT_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]);
$u = $dialect === 'dm' ? 'YOSHOP_USER' : 'yoshop_user';
$a = $dialect === 'dm' ? 'YOSHOP_USER_ADDRESS' : 'yoshop_user_address';
$marker = 'CDXPARAI' . strtoupper(bin2hex(random_bytes(4)));
$token = $marker . 'T' . strtoupper(bin2hex(random_bytes(10)));
$userId = 0;
$addressId = 0;
$request = static function (string $action, ?string $token,
    array $form, int $addressId = 0) use ($baseUrl, $root): array {
    $query = ['storeId' => 10001];
    if ($addressId > 0) $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";
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => $headers,
        'content' => http_build_query(['form' => $form]),
        'timeout' => 20, 'ignore_errors' => true,
    ]]);
    $raw = file_get_contents($baseUrl . '/api/address/' . $action . '?'
        . http_build_query($query), false, $context);
    $statusLine = $http_response_header[0] ?? '';
    $decoded = is_string($raw) ? json_decode($raw, true) : null;
    if (!is_array($decoded)) throw new RuntimeException('Non-JSON address ' . $action . ' result');
    unset($decoded['trace_id']);
    return ['http' => (int)(preg_match('/HTTP\/\S+\s+(\d+)/', $statusLine, $m)
        ? $m[1] : 0), 'status' => (int)($decoded['status'] ?? -1),
        'body_keys' => array_keys($decoded),
        'data_type' => gettype($decoded['data'] ?? null),
        'message' => mb_substr(str_replace($root, '[runtime]',
            (string)($decoded['message'] ?? '')), 0, 180)];
};
$read = static function (int $id) use ($pdo, $a): array {
    $q = $pdo->prepare("SELECT NAME,PHONE,DETAIL FROM $a WHERE ADDRESS_ID=?");
    $q->execute([$id]);
    $value = $q->fetch(PDO::FETCH_ASSOC);
    return $value === false ? [] : array_change_key_case($value);
};
$count = static function (int $id) use ($pdo, $a): int {
    $q = $pdo->prepare("SELECT COUNT(*) FROM $a WHERE USER_ID=?");
    $q->execute([$id]);
    return (int)$q->fetchColumn();
};
$form = static function (array $override = []) use ($marker): array {
    return array_replace(['name' => $marker, 'phone' => '13900000000',
        'detail' => '合成地址 12-3',
        'region' => [['value' => 1], ['value' => 2], ['value' => 3]]], $override);
};
$cases = [];
try {
    $insert = $pdo->prepare("INSERT INTO $u"
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,?,?,0,?)');
    $insert->execute(['', $marker, '0.00', '0.00', time()]);
    $userId = (int)$pdo->lastInsertId();
    if ($userId <= 0) throw new RuntimeException('Synthetic AI user was not inserted');
    \think\facade\Cache::set($token, ['user' => ['user_id' => $userId],
        'password_version' => 0, 'login_type' => 'thirdparty'], 300);
    $initial = $request('add', $token, $form());
    if ($initial['status'] !== 200 || $count($userId) !== 1) {
        throw new RuntimeException('Baseline address creation/readback failed');
    }
    $q = $pdo->prepare("SELECT ADDRESS_ID FROM $a WHERE USER_ID=?");
    $q->execute([$userId]);
    $addressId = (int)$q->fetchColumn();
    $unauthBefore = $count($userId);
    $unauth = $request('add', null, $form());
    $unauthAfter = $count($userId);
    $emptyBefore = $count($userId);
    $empty = $request('add', $token,
        $form(['name' => '', 'phone' => '', 'detail' => '']));
    $emptyAfter = $count($userId);
    $emptyRowQuery = $pdo->prepare("SELECT NAME,PHONE,DETAIL FROM $a"
        . ' WHERE USER_ID=? AND ADDRESS_ID<>? ORDER BY ADDRESS_ID');
    $emptyRowQuery->execute([$userId, $addressId]);
    $emptyRow = $emptyRowQuery->fetch(PDO::FETCH_ASSOC);
    if ($unauth['status'] !== 401 || $unauthAfter !== $unauthBefore
        || $empty['status'] !== 200 || $emptyAfter !== $emptyBefore + 1
        || !is_array($emptyRow) || (string)$emptyRow['NAME'] !== ''
        || (string)$emptyRow['PHONE'] !== '' || (string)$emptyRow['DETAIL'] !== '') {
        throw new RuntimeException('Unauthenticated or empty-string baseline changed');
    }
    $fields = [
        'name' => ['max' => 30, 'char' => '测', 'baseline' => $marker],
        'phone' => ['max' => 20, 'char' => '1', 'baseline' => '13900000000'],
        'detail' => ['max' => 255, 'char' => '址', 'baseline' => '合成地址 12-3'],
    ];
    foreach ($fields as $field => $spec) {
        $valid = str_repeat($spec['char'], $spec['max']);
        $invalid = str_repeat($spec['char'], $spec['max'] + 1);
        $validEdit = $request('edit', $token, $form([$field => $valid]), $addressId);
        $validRow = $read($addressId);
        if ($validEdit['status'] !== 200 || $validRow[$field] !== $valid
            || mb_strlen($validRow[$field], 'UTF-8') !== $spec['max']) {
            throw new RuntimeException($field . ' max-length edit/readback failed');
        }
        $invalidEdit = $request('edit', $token, $form([$field => $invalid]), $addressId);
        $afterEdit = $read($addressId);
        $rowsBeforeAdd = $count($userId);
        $invalidAdd = $request('add', $token, $form([$field => $invalid]));
        $rowsAfterAdd = $count($userId);
        $restored = $request('edit', $token,
            $form([$field => $spec['baseline']]), $addressId);
        if ($restored['status'] !== 200
            || $read($addressId)[$field] !== $spec['baseline']) {
            throw new RuntimeException($field . ' boundary fixture restore failed');
        }
        $cases[$field] = [
            'max_characters' => $spec['max'], 'valid_edit_status' => $validEdit['status'],
            'valid_persisted_chars' => mb_strlen($validRow[$field], 'UTF-8'),
            'overlong_characters' => mb_strlen($invalid, 'UTF-8'),
            'overlong_edit_status' => $invalidEdit['status'],
            'overlong_edit_message' => $invalidEdit['message'],
            'overlong_edit_http' => $invalidEdit['http'],
            'overlong_edit_body_keys' => $invalidEdit['body_keys'],
            'overlong_edit_data_type' => $invalidEdit['data_type'],
            'after_overlong_edit_chars' => mb_strlen((string)$afterEdit[$field], 'UTF-8'),
            'after_overlong_edit_equals_valid' => $afterEdit[$field] === $valid,
            'overlong_add_status' => $invalidAdd['status'],
            'overlong_add_message' => $invalidAdd['message'],
            'overlong_add_http' => $invalidAdd['http'],
            'overlong_add_body_keys' => $invalidAdd['body_keys'],
            'overlong_add_data_type' => $invalidAdd['data_type'],
            'overlong_add_rows_delta' => $rowsAfterAdd - $rowsBeforeAdd,
            'restored' => true,
        ];
    }
    echo json_encode(['dialect' => $dialect, 'status' => 'tested',
        'marker' => $marker,
        'unauth_add' => ['status' => $unauth['status'],
            'rows_delta' => $unauthAfter - $unauthBefore],
        'empty_string_add' => ['status' => $empty['status'],
            'rows_delta' => $emptyAfter - $emptyBefore, 'db_fields_empty' => true],
        'field_boundaries' => $cases],
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
} finally {
    \think\facade\Cache::delete($token);
    if ($userId > 0) {
        $pdo->prepare("DELETE FROM $a WHERE USER_ID=?")->execute([$userId]);
        $pdo->prepare("DELETE FROM $u WHERE USER_ID=? AND NICK_NAME=?")
            ->execute([$userId, $marker]);
        $q = $pdo->prepare("SELECT COUNT(*) FROM $u WHERE USER_ID=?");
        $q->execute([$userId]);
        if ($count($userId) !== 0 || (int)$q->fetchColumn() !== 0) {
            throw new RuntimeException('Synthetic address length fixture cleanup left rows');
        }
    }
    fwrite(STDERR, 'Address length synthetic cleanup: marker=' . $marker
        . ' users=0 addresses=0' . PHP_EOL);
}
