<?php
declare(strict_types=1);

// Reverse, bounded source triage. AI top-50 positive user IDs per type first;
// Store staff/department lookup second. No HTTP, DML, names or raw IDs output.
const RANK_MAX_PER_TYPE = 50;
const RANK_MAX_IDS = 150;
const RANK_MAX_LINKS = 500;
const RANK_MAX_QUERY_SECONDS = 5.0;
const RANK_DEV_AI_SHA = '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b';
const RANK_DEV_STORE_SHA = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
const RANK_LOCAL_ENV = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';

$lane = (string)($argv[1] ?? '');
if (count($argv) !== 2 || !in_array($lane, ['--mysql', '--dm'], true)
    || getenv('CDX_SCORE_REVERSE_APPROVED') !== 'TOP50_POSITIVE_USERS_PER_TYPE_V1'
    || !hash_equals(hash_file('sha256', __FILE__),
        (string)getenv('CDX_SCORE_REVERSE_SHA'))) {
    fwrite(STDERR, "DENIED: reviewed one-lane reverse source read required\n");
    exit(78);
}
if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 70500) {
    fwrite(STDERR, "DENIED: PHP7.4 required\n");
    exit(78);
}
$stage = 'runtime-boundary';
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted warning', 0, $severity);
});
set_exception_handler(static function (Throwable $e) use (&$stage): void {
    echo json_encode(['status' => 'halt', 'stage' => $stage,
        'class' => get_class($e), 'code' => (string)$e->getCode(),
        'error_sha256' => hash('sha256', $e->getMessage())],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(3);
});
if (!function_exists('pcntl_alarm') || !function_exists('pcntl_async_signals')) {
    throw new RuntimeException('read-only 20-second watchdog unavailable');
}
pcntl_async_signals(true);
pcntl_signal(SIGALRM, static function (): void {
    throw new RuntimeException('read-only 20-second deadline');
});
pcntl_alarm(20);

function rankMysql(array $config, string $expected): PDO
{
    if (strtolower((string)($config['TYPE'] ?? '')) !== 'mysql') {
        throw new RuntimeException('code-local development dialect changed');
    }
    $pdo = new PDO('mysql:host=' . $config['HOSTNAME'] . ';port=' . (int)$config['HOSTPORT']
        . ';dbname=' . $config['DATABASE'] . ';charset=' . ($config['CHARSET'] ?? 'utf8'),
        $config['USERNAME'], $config['PASSWORD'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER,
            PDO::ATTR_TIMEOUT => 5, PDO::ATTR_EMULATE_PREPARES => false]);
    $identity = (string)$pdo->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")
        ->fetchColumn();
    if (!hash_equals($expected, hash('sha256', $identity))) {
        throw new RuntimeException('not approved code-local development database');
    }
    return $pdo;
}

function rankDm(string $port, string $schema, string $user, string $password): PDO
{
    $pdo = new PDO('dm:host=127.0.0.1;port=' . $port . ';schema=' . $schema,
        $user, $password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $identity = $pdo->query("SELECT USER AS LOGIN_USER,"
        . "SYS_CONTEXT('USERENV','CURRENT_SCHEMA') AS CURRENT_SCHEMA FROM DUAL")
        ->fetch(PDO::FETCH_ASSOC);
    if (!is_array($identity)
        || strtoupper((string)$identity['login_user']) !== $user
        || strtoupper((string)$identity['current_schema']) !== $schema) {
        throw new RuntimeException('C nonproduction business schema drift');
    }
    return $pdo;
}

function rankRows(PDO $pdo, string $sql, array $params, int $limit): array
{
    $start = microtime(true);
    $statement = $pdo->prepare($sql);
    $statement->execute($params);
    $rows = [];
    while (($row = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
        $rows[] = array_change_key_case($row, CASE_LOWER);
        if (count($rows) > $limit) throw new RuntimeException('bounded query row cap exceeded');
    }
    $statement->closeCursor();
    if (microtime(true) - $start > RANK_MAX_QUERY_SECONDS) {
        throw new RuntimeException('per-query five-second budget exceeded');
    }
    return $rows;
}

try {
    if ($lane === '--mysql') {
        if (PHP_OS_FAMILY !== 'Darwin' || realpath(RANK_LOCAL_ENV) !== RANK_LOCAL_ENV) {
            throw new RuntimeException('code-local Mac environment unavailable');
        }
        $env = parse_ini_file(RANK_LOCAL_ENV, true, INI_SCANNER_RAW);
        if (!is_array($env) || !isset($env['DATABASE'], $env['DATABASE_ZHCT'])) {
            throw new RuntimeException('two code-local development databases required');
        }
        $ai = rankMysql($env['DATABASE'], RANK_DEV_AI_SHA);
        $store = rankMysql($env['DATABASE_ZHCT'], RANK_DEV_STORE_SHA);
        [$scoreTable, $userTable, $staffTable, $linkTable, $departmentTable] = [
            'yoshop_user_score', 'yoshop_user', 'ydy_staff',
            'ydy_staff_department', 'ydy_department'];
        $limitUsers = 'LIMIT 50';
        $limitLinks = 'LIMIT 501';
    } else {
        if (PHP_OS_FAMILY !== 'Linux') throw new RuntimeException('C host platform drift');
        $raw = @file_get_contents('/proc/53772/environ');
        $command = @file_get_contents('/proc/53772/cmdline');
        if ($raw === false || $command === false
            || strpos($command, '-S' . "\0" . '127.0.0.1:18102') === false
            || strpos($command, '/tmp/zhct-ai-http-20260929.3XDj3w/public') === false) {
            throw new RuntimeException('fixed C isolated process unavailable');
        }
        $machineId = trim((string)@file_get_contents('/etc/machine-id'));
        $host = gethostname();
        $socket = (string)shell_exec("ss -H -ltnpe '( sport = :18102 )' 2>/dev/null");
        if ($machineId === '' || $host === false
            || hash('sha256', $host . '|' . $machineId)
                !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9'
            || strpos($socket, '127.0.0.1:18102') === false
            || strpos($socket, 'pid=53772,') === false
            || !preg_match('/\bino:(\d+)\b/', $socket, $matches)
            || $matches[1] !== '441267'
            || @readlink('/proc/53772/fd/3') !== 'socket:[441267]') {
            throw new RuntimeException('fixed C host or socket ownership drift');
        }
        $env = [];
        foreach (explode("\0", $raw) as $entry) {
            $at = strpos($entry, '=');
            if ($at !== false) $env[substr($entry, 0, $at)] = substr($entry, $at + 1);
        }
        foreach (['POC_DIALECT' => 'dm', 'POC_EXTERNAL_STUBS' => '1',
            'POC_AI_HOST' => '127.0.0.1', 'POC_AI_PORT' => '5238',
            'POC_AI_SCHEMA' => 'AIZHCT_APP', 'POC_AI_USER' => 'AIZHCT_APP',
            'POC_HOST' => '127.0.0.1', 'POC_PORT' => '5237',
            'POC_SCHEMA' => 'ZHCT_APP', 'POC_USER' => 'ZHCT_APP'] as $key => $value) {
            if (($env[$key] ?? '') !== $value) {
                throw new RuntimeException('fixed C DM test instance identity drift');
            }
        }
        if (empty($env['POC_AI_PASSWORD']) || empty($env['POC_PASSWORD'])) {
            throw new RuntimeException('C private database credentials unavailable');
        }
        $ai = rankDm('5238', 'AIZHCT_APP', $env['POC_AI_USER'], $env['POC_AI_PASSWORD']);
        $store = rankDm('5237', 'ZHCT_APP', $env['POC_USER'], $env['POC_PASSWORD']);
        [$scoreTable, $userTable, $staffTable, $linkTable, $departmentTable] = [
            'YOSHOP_USER_SCORE', 'YOSHOP_USER', 'YDY_STAFF',
            'YDY_STAFF_DEPARTMENT', 'YDY_DEPARTMENT'];
        $limitUsers = 'FETCH FIRST 50 ROWS ONLY';
        $limitLinks = 'FETCH FIRST 501 ROWS ONLY';
    }

    $stage = 'three-types-top-fifty-valid-user-ids';
    $idsByType = [];
    $allIds = [];
    foreach ([1, 2, 3] as $type) {
        $sql = 'SELECT s.USER_ID,MAX(s.SCORE) BEST_SCORE FROM ' . $scoreTable . ' s'
            . ' JOIN ' . $userTable . ' u ON u.USER_ID=s.USER_ID AND u.IS_DELETE=0'
            . ' WHERE s.STORE_ID=10001 AND s.TYPE=? AND s.SCORE>0'
            . ' GROUP BY s.USER_ID ORDER BY BEST_SCORE DESC,s.USER_ID ASC ' . $limitUsers;
        $rows = rankRows($ai, $sql, [$type], RANK_MAX_PER_TYPE);
        $idsByType[$type] = [];
        foreach ($rows as $row) {
            $id = (int)($row['user_id'] ?? 0);
            if ($id <= 0 || (int)($row['best_score'] ?? 0) <= 0) {
                throw new RuntimeException('invalid positive score candidate');
            }
            $idsByType[$type][$id] = true;
            $allIds[$id] = true;
        }
        if (!$idsByType[$type]) {
            echo json_encode(['status' => 'blocked-natural-score-type-empty',
                'lane' => $lane === '--mysql' ? 'A-B-shared-dev-MySQL' : 'C-nonprod-DM',
                'missing_type' => $type, 'scope' => 'top-50-positive-users-per-type',
                'names_or_ids_output' => false, 'http_or_dml' => false],
                JSON_UNESCAPED_SLASHES), PHP_EOL;
            pcntl_alarm(0);
            exit(4);
        }
    }
    if (count($allIds) > RANK_MAX_IDS) {
        throw new RuntimeException('more than 150 unique AI candidate users');
    }

    $stage = 'bounded-store-staff-department-mapping';
    $marks = implode(',', array_fill(0, count($allIds), '?'));
    $sql = 'SELECT DISTINCT sd.DEPARTMENT_UUID,s.AI_USER_ID FROM '
        . $staffTable . ' s JOIN ' . $linkTable . ' sd ON sd.STAFF_UUID=s.UUID'
        . ' JOIN ' . $departmentTable . ' d ON d.UUID=sd.DEPARTMENT_UUID'
        . ' WHERE s.IS_SHOW=1 AND s.AI_USER_ID>0 AND d.STATUS=1 AND d.IS_SHOW=1'
        . ' AND s.AI_USER_ID IN (' . $marks . ')'
        . ' ORDER BY sd.DEPARTMENT_UUID,s.AI_USER_ID ' . $limitLinks;
    $links = rankRows($store, $sql, array_keys($allIds), RANK_MAX_LINKS + 1);
    if (count($links) > RANK_MAX_LINKS) {
        throw new RuntimeException('more than 500 Store department links');
    }
    $byDepartment = [];
    foreach ($links as $link) {
        $department = (string)($link['department_uuid'] ?? '');
        $id = (int)($link['ai_user_id'] ?? 0);
        if ($department === '' || $id <= 0 || !isset($allIds[$id])) {
            throw new RuntimeException('invalid Store staff-to-department relation');
        }
        foreach ([1, 2, 3] as $type) {
            if (isset($idsByType[$type][$id])) $byDepartment[$department][$type][$id] = true;
        }
    }
    $winner = null;
    foreach ($byDepartment as $department => $types) {
        $counts = [1 => count($types[1] ?? []), 2 => count($types[2] ?? []),
            3 => count($types[3] ?? [])];
        if (min($counts) > 0) {
            $winner = ['department_uuid_sha256' => hash('sha256', $department),
                'type1_users' => $counts[1], 'type2_users' => $counts[2],
                'type3_users' => $counts[3]];
            break;
        }
    }
    pcntl_alarm(0);
    echo json_encode(['status' => $winner ? 'natural-three-type-department-found'
        : 'no-three-type-department-within-bounded-sample',
        'lane' => $lane === '--mysql' ? 'A-B-shared-dev-MySQL' : 'C-nonprod-DM',
        'scope' => 'top-50-positive-users-per-type-only',
        'candidate_users_by_type' => [1 => count($idsByType[1]),
            2 => count($idsByType[2]), 3 => count($idsByType[3])],
        'distinct_candidate_users_in_memory' => count($allIds),
        'store_links_in_memory' => count($links),
        'candidate' => $winner, 'names_or_ids_output' => false,
        'http_or_dml' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit($winner ? 0 : 4);
} catch (Throwable $e) {
    pcntl_alarm(0);
    $handler = set_exception_handler(null);
    if (is_callable($handler)) $handler($e);
    exit(3);
}
