<?php

declare(strict_types=1);

$dialect = getenv('POC_DIALECT');
$baseUrl = rtrim((string)getenv('POC_BASE_URL'), '/');
if (!$baseUrl || !in_array($dialect, ['mysql', 'dm'], true)) exit(2);
$connect = static function (bool $ai) use ($dialect): PDO {
    $prefix = $ai ? 'POC_AI_' : 'POC_';
    $host = getenv($prefix . 'HOST');
    $port = getenv($prefix . 'PORT');
    $schema = getenv($prefix . 'SCHEMA');
    $dsn = $dialect === 'dm'
        ? "dm:host=$host;port=$port;schema=$schema"
        : "mysql:host=$host;port=$port;dbname=$schema;charset=utf8mb4";
    return new PDO($dsn, getenv($prefix . 'USER'), getenv($prefix . 'PASSWORD'),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
};
$store = $connect(false);
$ai = $connect(true);
$s = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YDY_' . strtoupper($name) : 'ydy_' . strtolower($name);
};
$a = static function (string $name) use ($dialect): string {
    return $dialect === 'dm' ? 'YOSHOP_' . strtoupper($name) : 'yoshop_' . strtolower($name);
};
foreach ([$s('measurement_session'), $s('measurement_session_raw'), $s('health_record'),
    $a('user'), $a('staff'), $a('user_weight'), $a('user_score_log'), $a('user_score')] as $table) {
    (stripos($table, 'YOSHOP_') === 0 ? $ai : $store)
        ->query('SELECT COUNT(*) FROM ' . $table . ' WHERE 1=0');
}
$marker = 'CDXMOCA' . strtoupper(bin2hex(random_bytes(5)));
$staffUuid = 'CDXMOCA-' . strtoupper(bin2hex(random_bytes(12)));
$mobile = '139' . (string)random_int(10000000, 99999999);
$now = date('Y-m-d H:i:s');
$aiUserId = 0;
$aiStaffId = 0;
$storeStaffId = 0;
$sessionId = 0;
$addedScoreMetrics = [];
try {
    $createAiUser = $ai->prepare('INSERT INTO ' . $a('user')
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,?,?,0,?)');
    $createAiUser->execute(['', $marker, '0.00', '0.00', time()]);
    $aiUserId = (int)$ai->lastInsertId();
    if ($aiUserId <= 0) throw new RuntimeException('Mocatch test AI user insert failed');
    $createAiStaff = $ai->prepare('INSERT INTO ' . $a('staff')
        . ' (NAME,USER_ID,STORE_ID,HEIGHT,WEIGHT,GENDER,BIRTHDAY,PHONE,IS_DELETE)'
        . " VALUES (?,?,10001,174,68,1,?, '',0)");
    $createAiStaff->execute([$marker, $aiUserId, '1990-01-01']);
    $aiStaffId = (int)$ai->lastInsertId();
    if ($aiStaffId <= 0) throw new RuntimeException('Mocatch test AI staff insert failed');
    $createStoreStaff = $store->prepare('INSERT INTO ' . $s('staff')
        . ' (UUID,STATION_UUID,NAME,MOBILE,AI_USER_ID,BIRTHDAY,SPORT_UUID,SPORT_EVENT_UUID,'
        . ' IN_TIME,CREATE_TIME,CREATE_BY,PERSONNEL_UMBER,PERMANENT_ADDRESS,COACH_NAME,SPORT_YEAR,IS_SHOW)'
        . ' VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1)');
    $createStoreStaff->execute([$staffUuid, 'CDXMOCA-STATION', $marker, $mobile, $aiUserId,
        '1990-01-01', 'CDXMOCA-SPORT', '[]', date('Y-m-d'), $now,
        'CODEX', $marker, 'CODEX TEST', 'CODEX TEST', '0']);
    $storeStaffId = (int)$store->lastInsertId();
    if ($storeStaffId <= 0) throw new RuntimeException('Mocatch test store staff insert failed');

    $payload = [
        'NameID' => $mobile, 'TestTime' => $now, 'equipmentNo' => $marker,
        'Weight' => '70.5', 'Height' => '175', 'BMI' => '23.02',
        'PBF' => '18.5', 'HealthScore' => '86',
    ];
    $post = static function (string $route, array $form) use ($baseUrl): array {
        $context = stream_context_create(['http' => [
            'method' => 'POST',
            'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
            'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
            'timeout' => 20, 'ignore_errors' => true,
        ]]);
        $body = file_get_contents($baseUrl . $route, false, $context);
        $status = (string)($http_response_header[0] ?? '');
        if (!is_string($body)) throw new RuntimeException('Mocatch HTTP body unavailable');
        $contentType = '';
        foreach ($http_response_header as $header) {
            if (stripos($header, 'Content-Type:') === 0) {
                $contentType = trim(substr($header, strlen('Content-Type:')));
            }
        }
        return ['status' => $status, 'content_type' => $contentType, 'body' => $body];
    };
    $first = $post('/api/mocatch/body', $payload);
    $findSession = $store->prepare('SELECT SESSION_ID,AI_USER_ID,IS_VALID FROM '
        . $s('measurement_session') . " WHERE VENDOR='mocatch' AND DEVICE_MODEL=?");
    $findSession->execute([$marker]);
    $sessions = array_map('array_change_key_case', $findSession->fetchAll(PDO::FETCH_ASSOC));
    if (count($sessions) !== 1 || (int)$sessions[0]['ai_user_id'] !== $aiUserId
        || (int)$sessions[0]['is_valid'] !== 1
        || strpos($first['status'], '200') === false) {
        throw new RuntimeException('Mocatch HTTP session readback mismatch');
    }
    $sessionId = (int)$sessions[0]['session_id'];
    if ($first['body'] !== (string)(30000000 + $sessionId)
        || $first['content_type'] !== 'text/plain; charset=utf-8') {
        throw new RuntimeException('Mocatch body response contract mismatch');
    }
    $weightMetric = $store->prepare('SELECT VALUE FROM ' . $s('health_record')
        . " WHERE SESSION_ID=? AND AI_USER_ID=? AND METRIC_CODE='weight'");
    $weightMetric->execute([$sessionId, $aiUserId]);
    $rawCount = $store->prepare('SELECT COUNT(*) FROM ' . $s('measurement_session_raw')
        . ' WHERE SESSION_ID=?');
    $rawCount->execute([$sessionId]);
    $aiWeight = $ai->prepare('SELECT WEIGHT FROM ' . $a('user_weight')
        . ' WHERE USER_ID=? AND STORE_ID=10001');
    $aiWeight->execute([$aiUserId]);
    $aiStaffWeight = $ai->prepare('SELECT WEIGHT FROM ' . $a('staff') . ' WHERE ID=?');
    $aiStaffWeight->execute([$aiStaffId]);
    if (abs((float)$weightMetric->fetchColumn() - 70.5) > 0.001
        || (int)$rawCount->fetchColumn() !== 1
        || abs((float)$aiWeight->fetchColumn() - 70.5) > 0.001
        || abs((float)$aiStaffWeight->fetchColumn() - 70.5) > 0.001) {
        throw new RuntimeException('Mocatch store/AI metric and weight readback mismatch');
    }
    $duplicate = $post('/api/mocatch/body', $payload);
    $findSession->execute([$marker]);
    if (strpos($duplicate['status'], '200') === false
        || $duplicate['body'] !== $first['body']
        || $duplicate['content_type'] !== $first['content_type']
        || count($findSession->fetchAll(PDO::FETCH_ASSOC)) !== 1) {
        throw new RuntimeException('Mocatch duplicate created another session');
    }
    $invalid = $post('/api/mocatch/body', []);
    $findSession->execute([$marker]);
    if (strpos($invalid['status'], '400') === false
        || $invalid['body'] !== 'input is null'
        || $invalid['content_type'] !== 'text/plain; charset=utf-8'
        || count($findSession->fetchAll(PDO::FETCH_ASSOC)) !== 1) {
        throw new RuntimeException('Invalid Mocatch body payload was accepted');
    }
    $redis = new Redis();
    $redis->connect((string)getenv('POC_REDIS_HOST'), (int)getenv('POC_REDIS_PORT'), 1.0);
    $stubReached = $redis->get('poc:mocatch_health_profile:' . $sessionId) === '1';
    $redis->del('poc:mocatch_health_profile:' . $sessionId);
    if (!$stubReached) throw new RuntimeException('Mocatch external dispatch stub not reached');
    echo json_encode(['dialect' => $dialect, 'status' => 'passed',
        'session_id' => $sessionId, 'store_session_raw_weight' => true,
        'ai_weight_and_staff_updated' => true, 'duplicate_idempotent' => true,
        'invalid_rejected' => true,
        'external_dispatch_stubbed' => true]) . PHP_EOL;
    $scoreSpecs = [
        'physical' => ['source' => 'csdf', 'metric' => 'mocatch_physical_score', 'value' => 91,
            'response_base' => 10000000],
        'posture' => ['source' => 'csdf', 'metric' => 'mocatch_posture_score', 'value' => 82,
            'response_base' => 20000000],
        'joint' => ['source' => 'TestScore', 'metric' => 'mocatch_joint_score', 'value' => 77,
            'response_base' => 40000000],
    ];
    $metricCount = $store->prepare('SELECT COUNT(*) FROM ' . $s('health_metric')
        . ' WHERE METRIC_CODE=?');
    $insertMetric = $store->prepare('INSERT INTO ' . $s('health_metric')
        . ' (METRIC_CODE,METRIC_NAME,UNIT,VALUE_TYPE,SUPPORTS_SEGMENT,METRIC_CATEGORY,IS_CORE,DESCRIPTION)'
        . ' VALUES (?,?,?,\'number\',0,\'body\',0,?)');
    $scoreSession = $store->prepare('SELECT SESSION_ID,AI_USER_ID,IS_VALID FROM '
        . $s('measurement_session') . " WHERE VENDOR='mocatch' AND DEVICE_MODEL=?");
    $scoreMetric = $store->prepare('SELECT VALUE FROM ' . $s('health_record')
        . ' WHERE SESSION_ID=? AND METRIC_CODE=?');
    foreach ($scoreSpecs as $reportType => $spec) {
        $metricCount->execute([$spec['metric']]);
        $preexisting = (int)$metricCount->fetchColumn() === 1;
        if (!$preexisting) {
            $insertMetric->execute([$spec['metric'], 'CODEX TEST MOCATCH ' . $reportType,
                'score', 'CODEX TEST temporary ' . $reportType . ' metric']);
            $addedScoreMetrics[] = ['code' => $spec['metric'],
                'name' => 'CODEX TEST MOCATCH ' . $reportType];
        }
        $model = $marker . '-' . strtoupper(substr($reportType, 0, 3));
        $response = $post('/api/mocatch/' . $reportType, [
            'NameID' => $mobile, 'TestTime' => $now,
            'equipmentNo' => $model, $spec['source'] => (string)$spec['value'],
        ]);
        $scoreSession->execute([$model]);
        $rows = array_map('array_change_key_case', $scoreSession->fetchAll(PDO::FETCH_ASSOC));
        if (strpos($response['status'], '200') === false || count($rows) !== 1
            || (int)$rows[0]['ai_user_id'] !== $aiUserId
            || (int)$rows[0]['is_valid'] !== 1) {
            throw new RuntimeException('Mocatch ' . $reportType . ' HTTP session readback mismatch');
        }
        $scoreId = (int)$rows[0]['session_id'];
        if ($response['body'] !== (string)($spec['response_base'] + $scoreId)
            || $response['content_type'] !== 'text/plain; charset=utf-8') {
            throw new RuntimeException('Mocatch ' . $reportType . ' response contract mismatch');
        }
        $scoreMetric->execute([$scoreId, $spec['metric']]);
        if ((int)$scoreMetric->fetchColumn() !== $spec['value']) {
            throw new RuntimeException('Mocatch ' . $reportType . ' score readback mismatch');
        }
        echo json_encode(['dialect' => $dialect, 'status' => 'passed',
            'report_type' => $reportType, 'session_id' => $scoreId,
            'score_dictionary_preexisting' => $preexisting,
            'temporary_metric_seeded' => !$preexisting,
            'score' => $spec['value'],
            'session_raw_metric_readback' => true]) . PHP_EOL;
    }
} finally {
    $sessionIds = [];
    if ($aiUserId > 0) {
        $lookup = $store->prepare('SELECT SESSION_ID FROM ' . $s('measurement_session')
            . " WHERE VENDOR='mocatch' AND AI_USER_ID=?");
        $lookup->execute([$aiUserId]);
        $sessionIds = array_map('intval', $lookup->fetchAll(PDO::FETCH_COLUMN));
    } elseif ($sessionId > 0) {
        $sessionIds = [$sessionId];
    }
    foreach ($sessionIds as $testSessionId) {
        $store->prepare('DELETE FROM ' . $s('health_record') . ' WHERE SESSION_ID=?')->execute([$testSessionId]);
        $store->prepare('DELETE FROM ' . $s('measurement_session_raw') . ' WHERE SESSION_ID=?')->execute([$testSessionId]);
        $store->prepare('DELETE FROM ' . $s('measurement_session') . ' WHERE SESSION_ID=?')->execute([$testSessionId]);
    }
    if ($addedScoreMetrics) {
        $deleteMetric = $store->prepare('DELETE FROM ' . $s('health_metric')
            . ' WHERE METRIC_CODE=? AND METRIC_NAME=?');
        $checkMetric = $store->prepare('SELECT COUNT(*) FROM ' . $s('health_metric')
            . ' WHERE METRIC_CODE=?');
        foreach ($addedScoreMetrics as $metric) {
            $deleteMetric->execute([$metric['code'], $metric['name']]);
            $checkMetric->execute([$metric['code']]);
            if ((int)$checkMetric->fetchColumn() !== 0) {
                throw new RuntimeException('Temporary Mocatch metric cleanup left a row');
            }
        }
    }
    if ($storeStaffId > 0) {
        $store->prepare('DELETE FROM ' . $s('staff') . ' WHERE ID=? AND UUID=?')
            ->execute([$storeStaffId, $staffUuid]);
    }
    if ($aiUserId > 0) {
        foreach (['user_weight', 'user_score_log', 'user_score'] as $table) {
            $ai->prepare('DELETE FROM ' . $a($table) . ' WHERE USER_ID=?')->execute([$aiUserId]);
        }
    }
    if ($aiStaffId > 0) {
        $ai->prepare('DELETE FROM ' . $a('staff') . ' WHERE ID=? AND USER_ID=?')
            ->execute([$aiStaffId, $aiUserId]);
    }
    if ($aiUserId > 0) {
        $ai->prepare('DELETE FROM ' . $a('user') . ' WHERE USER_ID=? AND NICK_NAME=?')
            ->execute([$aiUserId, $marker]);
    }
    $check = $store->prepare('SELECT COUNT(*) FROM ' . $s('measurement_session')
        . " WHERE VENDOR='mocatch' AND DEVICE_MODEL=?");
    $check->execute([$marker]);
    if ((int)$check->fetchColumn() !== 0) {
        throw new RuntimeException('Mocatch synthetic session cleanup left data');
    }
    if ($aiUserId > 0) {
        $checkUserSession = $store->prepare('SELECT COUNT(*) FROM ' . $s('measurement_session')
            . " WHERE VENDOR='mocatch' AND AI_USER_ID=?");
        $checkUserSession->execute([$aiUserId]);
        if ((int)$checkUserSession->fetchColumn() !== 0) {
            throw new RuntimeException('Mocatch synthetic user session cleanup left data');
        }
    }
    $checkStaff = $store->prepare('SELECT COUNT(*) FROM ' . $s('staff') . ' WHERE UUID=?');
    $checkStaff->execute([$staffUuid]);
    $checkAi = $ai->prepare('SELECT COUNT(*) FROM ' . $a('user') . ' WHERE NICK_NAME=?');
    $checkAi->execute([$marker]);
    $checkAiWeight = $ai->prepare('SELECT COUNT(*) FROM ' . $a('user_weight') . ' WHERE USER_ID=?');
    $checkAiWeight->execute([$aiUserId]);
    if ((int)$checkStaff->fetchColumn() !== 0 || (int)$checkAi->fetchColumn() !== 0
        || (int)$checkAiWeight->fetchColumn() !== 0) {
        throw new RuntimeException('Mocatch synthetic identity/weight cleanup left data');
    }
}
