<?php
declare(strict_types=1);

$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if (!in_array($phase, ['--run', '--audit'], true) || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXEQ[A-F0-9]{10}$/D', $marker)
    || ($phase === '--run' && getenv('CDX_DEVICE_RUN') !== 'OWNED_EQUIPMENT_V1')) {
    fwrite(STDERR, "DENIED business_http=0 business_dml=0\n"); exit(78);
}
if ($lane === 'B') {
    $config = include '/tmp/zhct-http-store-mysql.dQTTBP/application/config/local/database.php';
    if (!is_array($config) || ($config['database'] ?? '') !== 'zhct'
        || hash('sha256', (string)($config['hostname'] ?? ''))
            !== '47e8be48b0cfd56dcdb9d9d426e6637b245f340b4efad4cd2f084ec5851ecc8e') {
        throw new RuntimeException('B code-local MySQL identity drift');
    }
    $db = new PDO('mysql:host=' . $config['hostname'] . ';port=3306;dbname=zhct;charset=utf8',
        $config['username'], $config['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $port = 18081;
} else {
    if (getenv('POC_DIALECT') !== 'dm' || getenv('POC_HOST') !== '127.0.0.1'
        || getenv('POC_PORT') !== '5237' || getenv('POC_SCHEMA') !== 'ZHCT_APP'
        || getenv('POC_USER') !== 'ZHCT_APP') {
        throw new RuntimeException('C isolated Store DM identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5237;schema=ZHCT_APP',
        getenv('POC_USER'), getenv('POC_PASSWORD'),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'ZHCT_APP') {
        throw new RuntimeException('C schema drift');
    }
    $port = 18080;
}
$one = static function (string $sql, array $args = []) use ($db) {
    $stmt = $db->prepare($sql);
    $stmt->execute($args);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return is_array($row) ? array_change_key_case($row, CASE_LOWER) : null;
};
$ownRow = static function () use ($one, $marker, $lane) {
    $modelField = $lane === 'B' ? '`model`' : '"MODEL"';
    return $one('SELECT id,code,matecode,mate_flag,hearttime,' . $modelField
        . ' AS equipment_model,restaurant_id,face_time,version_number'
        . ' FROM ydy_equipment WHERE code=? AND name=?', [$marker, $marker]);
};
$versionHex = strtoupper(substr(hash('sha256', $marker . 'version'), 0, 32));
$versionUuid = substr($versionHex, 0, 8) . '-' . substr($versionHex, 8, 4)
    . '-' . substr($versionHex, 12, 4) . '-' . substr($versionHex, 16, 4)
    . '-' . substr($versionHex, 20, 12);
$ownVersion = static function () use ($one, $versionUuid) {
    return $one('SELECT id,uuid,code,version_number,url,type FROM ydy_equipment_version'
        . ' WHERE uuid=? AND type=99', [$versionUuid]);
};
$ownedMessages = static function () use ($db, $marker): array {
    $query = $db->prepare('SELECT id,msg_uuid,type,status,feedback_time FROM ydy_mq_message'
        . ' WHERE receive_device_id=? AND publish_device_id=? ORDER BY id');
    $query->execute([$marker, $marker]);
    return array_map(static function (array $row): array {
        return array_change_key_case($row, CASE_LOWER);
    }, $query->fetchAll(PDO::FETCH_ASSOC));
};
$old = $ownRow();
$oldMessages = $ownedMessages();
$oldVersion = $ownVersion();
if ($phase === '--audit') {
    echo json_encode(['lane' => $lane, 'phase' => 'audit', 'marker' => $marker,
        'owned_equipment_count' => $old === null ? 0 : 1,
        'owned_message_count' => count($oldMessages),
        'owned_version_count' => $oldVersion === null ? 0 : 1,
        'business_http' => 0, 'business_dml' => 0]), PHP_EOL;
    exit($old === null && !$oldMessages && $oldVersion === null ? 0 : 2);
}
if ($old !== null || $oldMessages || $oldVersion !== null) {
    throw new RuntimeException('owned equipment/message/version marker exists');
}
$versionTypeCount = (int)$db->query('SELECT COUNT(*) FROM ydy_equipment_version WHERE type=99')
    ->fetchColumn();
if ($versionTypeCount !== 0) throw new RuntimeException('test-only version type already occupied');
$restaurant = $one('SELECT name,morning_start,morning_end,noon_start,noon_end'
    . ' FROM ydy_restaurants WHERE id=1');
if ($restaurant === null) throw new RuntimeException('restaurant-one source absent');
$staffSql = 'SELECT uuid FROM ydy_staff WHERE is_show=1 AND status=1 ORDER BY id DESC'
    . ($lane === 'B' ? ' LIMIT 1' : ' FETCH FIRST 1 ROWS ONLY');
$staffUuid = $db->query($staffSql)->fetchColumn();
if (!is_string($staffUuid) || $staffUuid === '') {
    throw new RuntimeException('no original active staff for sync fixture');
}
$naturalEquipment = $db->query('SELECT id,code,mate_flag,hearttime FROM ydy_equipment'
    . " WHERE code NOT LIKE 'CDXEQ%' ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
$naturalSha = hash('sha256', json_encode($naturalEquipment));
$insert = $db->prepare('INSERT INTO ydy_equipment'
    . ' (code,name,type,restaurant_id,matecode,communicationkey,'
    . ' create_time,create_by,update_time,update_by)'
    . ' VALUES (?,?,1,1,?,?,?,\'CDX\',?,\'CDX\')');
$messageField = $lane === 'B' ? '`message`' : '"MESSAGE"';
$insertMessage = $db->prepare('INSERT INTO ydy_mq_message'
    . ' (msg_uuid,type,publish_device_id,receive_device_id,' . $messageField
    . ',publish_time,staff_uuid,status,create_time) VALUES (?,?,?,?,?,?,?,1,?)');
$insertVersion = $db->prepare('INSERT INTO ydy_equipment_version'
    . ' (uuid,code,version_number,url,type,content,create_time,create_by)'
    . " VALUES (?,1,'1.0.0',?,99,'CDX DB-only version',?,'CDX')");
$uuid = static function (): string {
    $hex = strtoupper(bin2hex(random_bytes(16)));
    return substr($hex, 0, 8) . '-' . substr($hex, 8, 4) . '-'
        . substr($hex, 12, 4) . '-' . substr($hex, 16, 4)
        . '-' . substr($hex, 20, 12);
};
$http = static function (string $path, array $form) use ($port): array {
    $ctx = stream_context_create(['http' => [
        'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'ignore_errors' => true, 'timeout' => 15, 'follow_location' => 0,
    ]]);
    $handle = @fopen('http://127.0.0.1:' . $port . $path, 'rb', false, $ctx);
    if (!is_resource($handle)) throw new RuntimeException('isolated equipment HTTP unavailable');
    $raw = stream_get_contents($handle, 1048577);
    fclose($handle);
    if (!is_string($raw) || strlen($raw) > 1048576) throw new RuntimeException('equipment response cap');
    $body = json_decode($raw, true);
    if (!is_array($body) || (int)($body['code'] ?? -1) !== 0) {
        throw new RuntimeException('business equipment response failed at ' . $path
            . ' code=' . (string)($body['code'] ?? 'nonjson')
            . ' message=' . substr((string)($body['message'] ?? ''), 0, 100));
    }
    return $body;
};
$types = static function ($value) use (&$types) {
    if (!is_array($value)) return gettype($value);
    if (!$value) return [];
    if (array_keys($value) === range(0, count($value) - 1)) return array_map($types, $value);
    ksort($value);
    return array_map($types, $value);
};
$id = 0;
$committed = false;
$error = null;
$result = [];
try {
    $now = date('Y-m-d H:i:s');
    $db->beginTransaction();
    $insert->execute([$marker, $marker, $marker, str_repeat('C', 32), $now, $now]);
    $own = $ownRow();
    if ($own === null || (int)$own['mate_flag'] !== 0 || (int)$own['hearttime'] !== 0) {
        throw new RuntimeException('owned equipment insert defaults differ');
    }
    $id = (int)$own['id'];
    $db->commit();
    $committed = true;

    $start = $http('/p/api/equipmentStart', ['code' => $marker])['data'] ?? null;
    if (!is_array($start) || (int)($start['model'] ?? -1) !== (int)$own['equipment_model']
        || !is_array($start['ali_mqtt'] ?? null)) {
        throw new RuntimeException('equipment startup differs from own equipment row');
    }
    $result['equipmentStart'] = hash('sha256', json_encode($types($start)));

    $beforeBeat = time();
    $heartbeat = $http('/p/api/heartbeat', ['code' => $marker])['data'] ?? null;
    $afterBeat = time();
    $own = $ownRow();
    if (!is_array($heartbeat) || (int)($heartbeat['face_update'] ?? -1) !== 0
        || (int)$own['hearttime'] < $beforeBeat || (int)$own['hearttime'] > $afterBeat) {
        throw new RuntimeException('owned heartbeat writeback invalid');
    }
    $clock = date('H:i:s');
    $expectedMeal = $clock > (string)$restaurant['morning_start']
        && $clock <= (string)$restaurant['morning_end'] ? 1
        : ($clock > (string)$restaurant['noon_start']
            && $clock < (string)$restaurant['noon_end'] ? 2 : 3);
    if ((int)($heartbeat['meal_times'] ?? 0) !== $expectedMeal) {
        throw new RuntimeException('heartbeat meal time differs from own restaurant');
    }
    $result['heartbeat'] = hash('sha256', json_encode($types($heartbeat)));

    $logBody = $http('/p/api/upload_log', ['code' => $marker]);
    $afterLog = $ownRow();
    if ($afterLog === null || (int)$afterLog['hearttime'] < (int)$own['hearttime']
        || !array_key_exists('data', $logBody) || $logBody['data'] !== null) {
        throw new RuntimeException('upload_log hearttime writeback invalid');
    }
    $result['upload_log'] = hash('sha256', json_encode($types($logBody['data'])));

    $paired = $http('/p/api/matecode', ['matecode' => $marker])['data'] ?? null;
    $afterPair = $ownRow();
    if (!is_array($paired) || (string)($paired['code'] ?? '') !== $marker
        || (string)($paired['restaurant_name'] ?? '') !== (string)$restaurant['name']
        || $afterPair === null || (int)$afterPair['mate_flag'] !== 1) {
        throw new RuntimeException('equipment matecode relation/writeback invalid');
    }
    $result['matecode'] = hash('sha256', json_encode($types($paired)));

    $versionUrl = 'https://example.invalid/cdx-version/' . $marker;
    $insertVersion->execute([$versionUuid, $versionUrl, $now]);
    $versionRow = $ownVersion();
    if ($versionRow === null || (string)$versionRow['url'] !== $versionUrl) {
        throw new RuntimeException('own version metadata insert/readback failed');
    }
    $versionBody = $http('/p/api/getNewVersion', [
        'type' => 99, 'code' => $marker, 'version_number' => '0.9.0']);
    $versionData = $versionBody['data'] ?? null;
    $afterVersion = $ownRow();
    if (!is_array($versionData)
        || (int)($versionData['id'] ?? 0) !== (int)$versionRow['id']
        || (string)($versionData['uuid'] ?? '') !== $versionUuid
        || (string)($versionData['url'] ?? '') !== $versionUrl
        || $afterVersion === null || (string)$afterVersion['version_number'] !== '0.9.0') {
        throw new RuntimeException('own version response/equipment update differs from source');
    }
    $result['getNewVersion'] = hash('sha256', json_encode($types($versionData)));

    $syncTime = date('Y-m-d H:i:s');
    $faceMsgUuid = $uuid();
    $userMsgUuid = $uuid();
    $payload = json_encode(['staff_uuid' => $staffUuid,
        'source' => $marker, 'feature' => 'CDX_NO_FACE_IMAGE'], JSON_UNESCAPED_SLASHES);
    if (!is_string($payload)) throw new RuntimeException('owned message JSON encode failed');
    $db->beginTransaction();
    $insertMessage->execute([$faceMsgUuid, 1, $marker, $marker,
        $payload, $syncTime, $staffUuid, $syncTime]);
    $insertMessage->execute([$userMsgUuid, 4, $marker, $marker,
        $payload, $syncTime, $staffUuid, $syncTime]);
    $db->commit();
    if (count($ownedMessages()) !== 2) throw new RuntimeException('own sync message insert count differs');

    $face = $http('/p/api/facedata', ['code' => $marker,
        'type' => 0, 'staff_uuid' => $staffUuid])['data'] ?? null;
    $faceItems = is_array($face) ? ($face['face_data'] ?? null) : null;
    if (!is_array($faceItems) || count($faceItems) !== 1
        || (string)($faceItems[0]['staff_uuid'] ?? '') !== $staffUuid
        || (string)($faceItems[0]['source'] ?? '') !== $marker) {
        throw new RuntimeException('own incremental face message response invalid');
    }
    $faceRow = $one('SELECT status,feedback_time FROM ydy_mq_message'
        . ' WHERE msg_uuid=? AND receive_device_id=?', [$faceMsgUuid, $marker]);
    if ($faceRow === null || (int)$faceRow['status'] !== 3
        || substr((string)$faceRow['feedback_time'], 0, 10) !== substr($syncTime, 0, 10)) {
        throw new RuntimeException('own face message status writeback invalid');
    }
    $result['facedata'] = hash('sha256', json_encode($types($face)));

    $userData = $http('/p/api/userdata', ['code' => $marker, 'type' => 0])['data'] ?? null;
    $userItems = is_array($userData) ? ($userData['face_data'] ?? null) : null;
    if (!is_array($userItems) || count($userItems) !== 1
        || (string)($userItems[0]['staff_uuid'] ?? '') !== $staffUuid
        || (string)($userItems[0]['source'] ?? '') !== $marker) {
        throw new RuntimeException('own incremental user message response invalid');
    }
    $userRow = $one('SELECT status,feedback_time FROM ydy_mq_message'
        . ' WHERE msg_uuid=? AND receive_device_id=?', [$userMsgUuid, $marker]);
    if ($userRow === null || (int)$userRow['status'] !== 3
        || substr((string)$userRow['feedback_time'], 0, 10) !== substr($syncTime, 0, 10)) {
        throw new RuntimeException('own user message status writeback invalid');
    }
    $own = $ownRow();
    if ($own === null || substr((string)$own['face_time'], 0, 10) !== substr($syncTime, 0, 10)) {
        throw new RuntimeException('own equipment face_time writeback invalid');
    }
    $result['userdata'] = hash('sha256', json_encode($types($userData)));

    $feedbackUuid = $uuid();
    $insertMessage->execute([$feedbackUuid, 1, $marker, $marker,
        $payload, $syncTime, '', $syncTime]);
    $feedbackBody = $http('/p/api/feedback', [
        'msg_uuid' => $feedbackUuid, 'device_id' => $marker]);
    $feedbackRow = $one('SELECT status,feedback_time FROM ydy_mq_message'
        . ' WHERE msg_uuid=? AND receive_device_id=?', [$feedbackUuid, $marker]);
    if ($feedbackRow === null || (int)$feedbackRow['status'] !== 2
        || substr((string)$feedbackRow['feedback_time'], 0, 10) !== substr($syncTime, 0, 10)
        || !is_array($feedbackBody['data'] ?? null)) {
        throw new RuntimeException('own feedback message status/response invalid');
    }
    $result['feedback'] = hash('sha256', json_encode($types($feedbackBody['data'])));
} catch (Throwable $caught) {
    $error = $caught;
} finally {
    if ($db->inTransaction()) $db->rollBack();
    foreach ($ownedMessages() as $ownedMessage) {
        $delete = $db->prepare('DELETE FROM ydy_mq_message'
            . ' WHERE id=? AND msg_uuid=? AND receive_device_id=? AND publish_device_id=?');
        $delete->execute([(int)$ownedMessage['id'], (string)$ownedMessage['msg_uuid'], $marker, $marker]);
    }
    $versionRow = $ownVersion();
    if ($versionRow !== null) {
        $delete = $db->prepare('DELETE FROM ydy_equipment_version'
            . ' WHERE id=? AND uuid=? AND type=99');
        $delete->execute([(int)$versionRow['id'], $versionUuid]);
    }
    if ($committed) {
        $delete = $db->prepare('DELETE FROM ydy_equipment WHERE id=? AND code=? AND name=?');
        $delete->execute([$id, $marker, $marker]);
    }
}
if ($ownRow() !== null || $ownedMessages() || $ownVersion() !== null) {
    throw new RuntimeException('owned equipment/messages/version cleanup failed');
}
$afterNatural = $db->query('SELECT id,code,mate_flag,hearttime FROM ydy_equipment'
    . " WHERE code NOT LIKE 'CDXEQ%' ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
if (hash('sha256', json_encode($afterNatural)) !== $naturalSha) {
    throw new RuntimeException('natural equipment rows changed during fixture');
}
if ($error !== null) throw $error;
echo json_encode(['lane' => $lane, 'marker' => $marker,
    'http_business_ok' => 8, 'type_hashes' => $result,
    'owned_equipment_message_version_cleanup_zero' => true,
    'business_dml' => 'one-own-equipment-three-messages-one-version-updates-then-exact-delete',
    'external_dispatch' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
