<?php
// One-shot development-database integration test. Run only inside zhctapp_v2_0.
declare(strict_types=1);

$storeRoot = '/workspace/zhct/zhctproject/store';
define('TP_ENV', 'local');
define('BUSINESS', 'zhct_zhangjiagang');
define('APP_PATH', $storeRoot . '/application/');
require $storeRoot . '/thinkphp/base.php';
require $storeRoot . '/thinkphp/helper.php';
require $storeRoot . '/application/common.php';
$localDb = require $storeRoot . '/application/config/local/database.php';
$db = array_merge([
    'type' => 'mysql', 'hostname' => '', 'database' => '', 'username' => '',
    'password' => '', 'hostport' => 3306, 'charset' => 'utf8', 'prefix' => 'ydy_',
    'debug' => false, 'auto_timestamp' => false,
], $localDb);
if ($db['hostname'] !== '101.200.165.56' || $db['database'] !== 'zhct') {
    throw new RuntimeException('Unexpected database target; test aborted.');
}
\think\Config::set('database', $db);
\think\Config::set('zhangjiagang_face_department_uuid', '4272E971-FDE5-A6FB-7349-D2B004034D42');
\think\Config::set('baidu_face_db_group_id', 'zhct_dev');
\think\Db::clear();
$pdo = new PDO(
    'mysql:host=' . $db['hostname'] . ';port=' . ($db['hostport'] ?: 3306)
        . ';dbname=' . $db['database'] . ';charset=utf8mb4',
    $db['username'], $db['password'],
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);

function row(PDO $pdo, string $sql, array $params): array
{
    $statement = $pdo->prepare($sql);
    $statement->execute($params);
    return $statement->fetch() ?: [];
}

function execute(PDO $pdo, string $sql, array $params): void
{
    $statement = $pdo->prepare($sql);
    $statement->execute($params);
}

function ensure(bool $condition, string $message): void
{
    if (!$condition) { throw new RuntimeException($message); }
}

function api(string $path, array $body): array
{
    $curl = curl_init('http://127.0.0.1' . $path);
    curl_setopt_array($curl, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($body),
        CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
    $raw = curl_exec($curl);
    $http = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    $error = curl_error($curl);
    curl_close($curl);
    ensure($raw !== false && $http === 200, 'HTTP request failed: ' . $http . ' ' . $error);
    $json = json_decode($raw, true);
    ensure(is_array($json) && !empty($json['trace_id']), 'Missing JSON or trace_id.');
    return $json;
}

function importOnePhoto(bool $dryRun = false): array
{
    $command = new \app\cron\command\ZhangjiagangFaceImport();
    $output = new \think\console\Output('buffer');
    $files = glob($GLOBALS['sourceDir'] . '*');
    ensure(count($files) === 1 && $files[0] === $GLOBALS['source'], 'face_int has unrelated photos; command not run.');
    try {
        $args = $dryRun ? ['--dry-run', '--limit=1'] : ['--limit=1'];
        $status = $command->run(new \think\console\Input($args), $output);
        return [$status, $output->fetch()];
    } catch (Throwable $e) {
        return [1, $e->getMessage() . ' ' . $output->fetch()];
    }
}

$mode = row($pdo, 'SELECT config_value FROM ydy_config WHERE config_key=? AND status=1 LIMIT 1', ['face_learning_mode']);
ensure(($mode['config_value'] ?? null) === '2', 'face_learning_mode is not 2.');
$department = row($pdo, 'SELECT uuid,is_show FROM ydy_department WHERE uuid=? LIMIT 1', [\think\Config::get('zhangjiagang_face_department_uuid')]);
ensure(($department['is_show'] ?? null) == 1, 'Configured development department missing.');

$suffix = (string)random_int(100000000000, 999999999999);
$staffNumber = '991010' . $suffix;
$deviceCode = 'CODEXFACE' . substr($suffix, -12);
$sourceDir = $storeRoot . '/public/static/upload/face_int/';
$readyDir = $storeRoot . '/public/static/upload/face/';
foreach ([$sourceDir, $readyDir] as $dir) {
    if (!is_dir($dir)) { mkdir($dir, 0755, true); }
    ensure(!is_link($dir), 'Photo directory must not be a symlink.');
}
$source = $sourceDir . $staffNumber . '_1.png';
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg==');
$photoPaths = [];
$staffId = null;
$equipmentId = null;
$report = [];

try {
    ensure(!row($pdo, 'SELECT id FROM ydy_staff WHERE personnel_umber=? LIMIT 1', [$staffNumber]), 'Generated staff number already exists.');
    execute($pdo, 'INSERT INTO ydy_equipment(code,name,type,status,mate_flag,face_time,create_time,create_by,update_time,update_by) VALUES(?,?,?,?,?,?,?,?,?,?)',
        [$deviceCode, 'Codex人脸全流程测试', 2, 2, 1, '2001-01-01 00:00:00', date('Y-m-d H:i:s'), 'codex_test', date('Y-m-d H:i:s'), 'codex_test']);
    $equipmentId = (int)$pdo->lastInsertId();

    file_put_contents($source, $png);
    [$status, $log] = importOnePhoto(true);
    ensure($status === 0 && is_file($source) && strpos($log, '[预检]') !== false,
        'Dry-run import failed or modified source: ' . $log);
    [$status, $log] = importOnePhoto();
    ensure($status === 0, 'Initial import failed: ' . $log);
    ensure(!is_file($source), 'Imported source was not removed.');
    $staff = row($pdo, 'SELECT id,uuid,name,personnel_umber,identity_category_id FROM ydy_staff WHERE personnel_umber=? LIMIT 1', [$staffNumber]);
    ensure($staff && $staff['name'] === $staffNumber && (int)$staff['identity_category_id'] === 1, 'Imported staff mismatch.');
    $staffId = (int)$staff['id'];
    $faceDb = row($pdo, 'SELECT id,head_img_url,feature,baidu_face_token,hardware_img_uuid,group_id FROM ydy_staff_face WHERE staff_uuid=? AND is_del=0 LIMIT 1', [$staff['uuid']]);
    ensure($faceDb && $faceDb['feature'] === '' && $faceDb['baidu_face_token'] === '', 'Imported face must be unlearned.');
    $photoPaths[] = $readyDir . basename($faceDb['head_img_url']);
    ensure(is_file($photoPaths[0]), 'Imported photo missing from ready directory.');
    $departmentLink = row($pdo, 'SELECT staff_uuid FROM ydy_staff_department WHERE staff_uuid=? AND department_uuid=? LIMIT 1',
        [$staff['uuid'], $department['uuid']]);
    ensure((bool)$departmentLink, 'Staff-department association missing.');
    $report['import'] = ['command_dry_run' => true, 'command_execute' => true,
        'staff_created' => true, 'department_linked' => true, 'face_unlearned' => true,
        'source_removed' => true, 'ready_photo_exists' => true];

    $first = api('/p/api/facedata', ['code' => $deviceCode, 'type' => 1, 'staff_uuid' => $staff['uuid']]);
    ensure($first['code'] === 0 && $first['face_learning_mode'] === 2 && count($first['data']['face_data']) === 1,
        'First full sync mismatch.');
    $face = $first['data']['face_data'][0];
    ensure((int)$face['id'] === $staffId && $face['feature'] === '' && $face['face_token'] === '', 'First sync face fields mismatch.');
    ensure(strpos($face['head_img_url'], $faceDb['head_img_url']) !== false,
        'Full-sync photo URL does not contain the stored relative path.');
    $report['first_sync'] = ['code' => 0, 'mode' => 2, 'trace_id' => $first['trace_id'],
        'photo_url_matches_db' => true, 'url_reachability_skipped' => true];

    $learn = ['code' => $deviceCode, 'staff_id' => $staffId, 'head_img_url' => $face['head_img_url'],
        'feature' => 'CODEX_TEST_FEATURE_A', 'baidu_face_token' => 'CODEX_TEST_TOKEN_A'];
    $saved = api('/p/api/facedataLearnResult', $learn);
    ensure($saved['code'] === 0, 'Initial learning callback failed: ' . $saved['message']);
    $report['first_callback'] = ['code' => $saved['code'], 'trace_id' => $saved['trace_id']];

    file_put_contents($source, $png);
    [$status, $log] = importOnePhoto();
    ensure($status === 0, 'Repeat import failed: ' . $log);
    $staffAfter = row($pdo, 'SELECT id,uuid FROM ydy_staff WHERE personnel_umber=? LIMIT 1', [$staffNumber]);
    ensure((int)$staffAfter['id'] === $staffId, 'Repeat import created a new staff member.');
    $updatedFace = row($pdo, 'SELECT id,head_img_url,feature,baidu_face_token,hardware_img_uuid FROM ydy_staff_face WHERE staff_uuid=? AND is_del=0 LIMIT 1', [$staff['uuid']]);
    ensure($updatedFace['head_img_url'] !== $faceDb['head_img_url'] && $updatedFace['feature'] === ''
        && $updatedFace['baidu_face_token'] === '', 'Repeat import did not rotate photo and clear learning result.');
    $photoPaths[] = $readyDir . basename($updatedFace['head_img_url']);
    ensure(is_file($photoPaths[1]), 'Second photo missing from ready directory.');
    $oldHardware = row($pdo, 'SELECT is_del FROM ydy_hardware_img WHERE uuid=? AND staff_uuid=? LIMIT 1',
        [$faceDb['hardware_img_uuid'], $staff['uuid']]);
    ensure((int)$oldHardware['is_del'] === 1, 'Old hardware image was not disabled.');
    $report['repeat_import'] = ['same_staff_id' => true, 'photo_url_rotated' => true,
        'feature_and_token_cleared' => true, 'old_hardware_disabled' => true];

    $stale = api('/p/api/facedataLearnResult', $learn);
    ensure($stale['code'] === 1 && $stale['message'] === '人脸照片已更新，请重新拉取', 'Old-photo callback should fail.');
    $report['stale_callback'] = ['code' => 1, 'message' => $stale['message'], 'trace_id' => $stale['trace_id']];
    $second = api('/p/api/facedata', ['code' => $deviceCode, 'type' => 1, 'staff_uuid' => $staff['uuid']]);
    ensure($second['code'] === 0 && $second['data']['face_data'][0]['face_token'] === '', 'Second full sync should require learning.');
    $learn['head_img_url'] = $second['data']['face_data'][0]['head_img_url'];
    $learn['feature'] = 'CODEX_TEST_FEATURE_B';
    $learn['baidu_face_token'] = 'CODEX_TEST_TOKEN_B';
    $savedAgain = api('/p/api/facedataLearnResult', $learn);
    ensure($savedAgain['code'] === 0, 'New-photo callback failed: ' . $savedAgain['message']);
    $same = api('/p/api/facedataLearnResult', $learn);
    ensure($same['code'] === 0, 'Same-result retry failed.');
    $staffFaceRead = row($pdo, 'SELECT feature,baidu_face_token FROM ydy_staff_face WHERE id=?', [$updatedFace['id']]);
    $hardwareRead = row($pdo, 'SELECT feature,baidu_face_token,face_token FROM ydy_hardware_img WHERE uuid=? AND staff_uuid=?',
        [$updatedFace['hardware_img_uuid'], $staff['uuid']]);
    ensure($staffFaceRead['feature'] === $learn['feature'] && $staffFaceRead['baidu_face_token'] === $learn['baidu_face_token'],
        'staff_face readback mismatch.');
    ensure($hardwareRead['feature'] === $learn['feature'] && $hardwareRead['baidu_face_token'] === $learn['baidu_face_token']
        && $hardwareRead['face_token'] === $learn['baidu_face_token'], 'hardware_img readback mismatch.');
    $last = api('/p/api/facedata', ['code' => $deviceCode, 'type' => 1, 'staff_uuid' => $staff['uuid']]);
    ensure($last['code'] === 0 && $last['data']['face_data'][0]['face_token'] === $learn['baidu_face_token'],
        'Final full sync token mismatch.');
    $report['final'] = ['callback_code' => $savedAgain['code'], 'callback_trace_id' => $savedAgain['trace_id'],
        'retry_code' => $same['code'], 'staff_face_readback' => true, 'hardware_img_readback' => true,
        'final_full_sync_token_present' => true, 'final_trace_id' => $last['trace_id']];
} finally {
    $found = row($pdo, 'SELECT id,uuid FROM ydy_staff WHERE personnel_umber=? LIMIT 1', [$staffNumber]);
    if ($found) {
        execute($pdo, 'DELETE FROM ydy_staff_department WHERE staff_uuid=?', [$found['uuid']]);
        execute($pdo, 'DELETE FROM ydy_staff_face WHERE staff_uuid=?', [$found['uuid']]);
        execute($pdo, 'DELETE FROM ydy_hardware_img WHERE staff_uuid=?', [$found['uuid']]);
        execute($pdo, 'DELETE FROM ydy_staff WHERE id=?', [$found['id']]);
    }
    if ($equipmentId !== null) {
        execute($pdo, 'DELETE FROM ydy_equipment WHERE id=?', [$equipmentId]);
    }
    if (is_file($source)) { unlink($source); }
    foreach ($photoPaths as $path) {
        if (strpos($path, $readyDir) === 0 && is_file($path)) { unlink($path); }
    }
    $report['fixture_cleaned'] = true;
    echo json_encode($report, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
}
