<?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('POC_TARGET_ROOT, POC_BASE_URL and POC_DIALECT are 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('Selected local MySQL configuration is unavailable');
    }
    $db = $local['DATABASE'];
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
        'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $source => $target) {
        putenv('POC_AI_' . $target . '=' . (string)$db[$source]);
    }
}
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('Missing database environment');
}

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_HTTP_FAIL ' . get_class($error) . ': '
        . mb_substr($error->getMessage(), 0, 300) . 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';
$marker = 'CDXPARAI' . strtoupper(bin2hex(random_bytes(4)));
$token = $marker . 'TOKEN' . strtoupper(bin2hex(random_bytes(12)));
$userId = 0;
$addressIds = [];
$http = static function (string $method, string $path, ?string $auth = null,
    array $form = [], array $query = []) use ($baseUrl): array {
    $query = ['storeId' => '10001'] + $query;
    $url = $baseUrl . '/api/address/' . $path . '?' . http_build_query($query);
    $headers = "platform: POC\r\nContent-Type: application/x-www-form-urlencoded\r\n";
    if ($auth !== null) $headers .= 'Access-Token: ' . $auth . "\r\n";
    $options = ['method' => $method, 'header' => $headers,
        'content' => $method === 'POST' ? http_build_query(['form' => $form]) : '',
        'ignore_errors' => true, 'timeout' => 20];
    $body = file_get_contents($url, 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 HTTP result: ' . $statusLine);
    unset($json['trace_id']);
    return ['http' => (int)(preg_match('/HTTP\/\S+\s+(\d+)/', $statusLine, $m) ? $m[1] : 0),
        'body' => $json];
};
$requireStatus = static function (array $result, int $status, string $case): array {
    if ($result['http'] !== 200 || (int)($result['body']['status'] ?? -1) !== $status) {
        throw new RuntimeException($case . ' unexpected HTTP/business status: '
            . json_encode($result, JSON_UNESCAPED_UNICODE));
    }
    return $result['body'];
};
$row = static function (int $id) use ($pdo, $addressTable): array {
    $q = $pdo->prepare('SELECT ADDRESS_ID,NAME,PHONE,PROVINCE_ID,CITY_ID,REGION_ID,DETAIL,'
        . 'USER_ID,STORE_ID,IS_DELETE,CREATE_TIME,UPDATE_TIME FROM ' . $addressTable
        . ' WHERE ADDRESS_ID=?');
    $q->execute([$id]);
    return array_change_key_case((array)$q->fetch(PDO::FETCH_ASSOC));
};
$list = static function () use ($pdo, $addressTable, &$userId): array {
    $q = $pdo->prepare('SELECT ADDRESS_ID,NAME,PHONE,PROVINCE_ID,CITY_ID,REGION_ID,DETAIL,'
        . 'USER_ID,STORE_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));
};
$default = static function () use ($pdo, $userTable, &$userId): int {
    $q = $pdo->prepare('SELECT ADDRESS_ID FROM ' . $userTable . ' WHERE USER_ID=?');
    $q->execute([$userId]);
    return (int)$q->fetchColumn();
};
$assert = static function (bool $ok, string $message): void {
    if (!$ok) throw new RuntimeException($message);
};
$form = static function (string $name, string $detail): array {
    return ['name' => $name, 'phone' => '13900000000', 'detail' => $detail,
        'region' => [['value' => 1], ['value' => 2], ['value' => 3]]];
};
$evidence = [];
try {
    $insert = $pdo->prepare('INSERT INTO ' . $userTable
        . ' (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();
    $assert($userId > 0 && $list() === [] && $default() === 0, 'Synthetic user precondition failed');
    \think\facade\Cache::set($token, ['user' => ['user_id' => $userId],
        'password_version' => 0, 'login_type' => 'thirdparty'], 300);

    $unauth = $requireStatus($http('GET', 'list'), 401, 'unauthenticated list');
    $empty = $requireStatus($http('GET', 'list', $token), 200, 'empty list');
    $assert(($empty['data']['list'] ?? null) === [], 'New user list is not empty');
    $missing = $requireStatus($http('POST', 'add', $token,
        ['name' => $marker, 'phone' => '13900000000', 'detail' => 'missing region']), 500,
        'missing region');
    $assert($list() === [], 'Invalid address was persisted');

    $added = $requireStatus($http('POST', 'add', $token,
        $form($marker . 'A', '合成地址 A 12-3')), 200, 'add first');
    $rows = $list();
    $assert(count($rows) === 1, 'First address not persisted exactly once');
    $first = (int)$rows[0]['address_id'];
    $addressIds[] = $first;
    $assert((int)$rows[0]['user_id'] === $userId && (int)$rows[0]['store_id'] === 10001
        && (int)$rows[0]['is_delete'] === 0 && (int)$rows[0]['province_id'] === 1
        && (int)$rows[0]['city_id'] === 2 && (int)$rows[0]['region_id'] === 3
        && $rows[0]['name'] === $marker . 'A' && $rows[0]['detail'] === '合成地址 A 12-3'
        && $default() === $first, 'First address or automatic default mismatch');
    $nonempty = $requireStatus($http('GET', 'list', $token), 200, 'nonempty list');
    $defaultId = $requireStatus($http('GET', 'defaultId', $token), 200, 'default id');
    $detail = $requireStatus($http('GET', 'detail', $token, [], ['addressId' => $first]),
        200, 'detail');
    $assert(count($nonempty['data']['list'] ?? []) === 1
        && (int)($detail['data']['detail']['address_id'] ?? 0) === $first
        && (int)($defaultId['data']['defaultId'] ?? 0) === $first,
        'List/detail does not return persisted address');

    $edited = $requireStatus($http('POST', 'edit', $token,
        $form($marker . 'E', '合成地址 E 45-6'), ['addressId' => $first]), 200, 'edit');
    $firstRow = $row($first);
    $assert($firstRow['name'] === $marker . 'E'
        && $firstRow['detail'] === '合成地址 E 45-6'
        && (int)$firstRow['is_delete'] === 0, 'Edited values not persisted');

    $requireStatus($http('POST', 'add', $token,
        $form($marker . 'B', '合成地址 B 78-9')), 200, 'add second');
    $rows = $list();
    $assert(count($rows) === 2, 'Second address not persisted');
    $second = (int)$rows[1]['address_id'];
    $addressIds[] = $second;
    $assert($default() === $first, 'Second address unexpectedly changed default');
    $setDefault = $requireStatus($http('POST', 'setDefault', $token, [],
        ['addressId' => $second]), 200, 'set default');
    $assert($default() === $second, 'Default address update not persisted');
    $removed = $requireStatus($http('POST', 'remove', $token, [],
        ['addressId' => $second]), 200, 'remove default');
    $assert((int)$row($second)['is_delete'] === 1 && $default() === 0,
        'Remove default did not soft-delete and clear pointer');
    $noDefault = $requireStatus($http('GET', 'defaultId', $token), 200,
        'default id after remove');
    $assert((int)($noDefault['data']['defaultId'] ?? -1) === 0,
        'Default ID response disagrees with database');
    $afterRemove = $requireStatus($http('GET', 'list', $token), 200, 'list after remove');
    $assert(count($afterRemove['data']['list'] ?? []) === 1
        && (int)$afterRemove['data']['list'][0]['address_id'] === $first,
        'Removed address still listed');

    $evidence = [
        'dialect' => $dialect, 'status' => 'passed', 'route_count' => 7,
        'scenarios' => ['unauth_list', 'empty_list', 'missing_region_no_write',
            'add_first_auto_default', 'nonempty_list_detail', 'default_id', 'edit',
            'add_second', 'set_default', 'remove_default', 'default_id_cleared',
            'list_excludes_removed'],
        'responses' => [
            'unauth' => $unauth, 'empty' => $empty, 'missing_region' => $missing,
            'add' => $added, 'edit' => $edited, 'set_default' => $setDefault,
            'remove' => $removed,
            'default_id_type' => gettype($defaultId['data']['defaultId']),
            'no_default_id_type' => gettype($noDefault['data']['defaultId']),
            'nonempty_list_keys' => array_keys($nonempty['data']['list'][0]),
            'detail_keys' => array_keys($detail['data']['detail']),
            'nonempty_list_types' => array_map('gettype', $nonempty['data']['list'][0]),
            'detail_types' => array_map('gettype', $detail['data']['detail']),
            'nonempty_list_region' => $nonempty['data']['list'][0]['region'],
            'detail_region' => $detail['data']['detail']['region'],
        ],
        'database' => ['rows_after_add' => 1, 'rows_after_second_add' => 2,
            'rows_after_remove' => 2, 'active_rows_after_remove' => 1,
            'first_address_id_type' => gettype($nonempty['data']['list'][0]['address_id'])],
    ];
} finally {
    \think\facade\Cache::delete($token);
    if ($userId > 0) {
        $pdo->prepare('DELETE FROM ' . $addressTable . ' WHERE USER_ID=?')->execute([$userId]);
        $pdo->prepare('DELETE FROM ' . $userTable . ' WHERE USER_ID=? AND NICK_NAME=?')
            ->execute([$userId, $marker]);
        $userCheck = $pdo->prepare('SELECT COUNT(*) FROM ' . $userTable . ' WHERE USER_ID=?');
        $userCheck->execute([$userId]);
        $assert($list() === [] && (int)$userCheck->fetchColumn() === 0,
            'Synthetic user/address cleanup left rows');
    }
}
$evidence['cleanup_rows'] = 0;
echo json_encode($evidence, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
