<?php
declare(strict_types=1);

// Read-only A/B shared MySQL or C nonproduction DM source triage.
// No HTTP, DML, names, nicknames, raw user IDs or UUIDs are emitted.
const MAX_DEPARTMENTS = 50;
const MAX_STAFF_LINKS = 2000;
const MAX_QUERY_SECONDS = 5.0;
const DEV_AI_SHA = '210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b';
const DEV_STORE_SHA = '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
const LOCAL_ENV = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';

$mode = (string)($argv[1] ?? '');
if (count($argv) !== 2 || !in_array($mode, ['--mysql', '--dm'], true)
    || getenv('CDX_SCORE_SOURCE_APPROVED') !== 'READONLY_50_DEPARTMENTS_V1'
    || !hash_equals(hash_file('sha256', __FILE__),
        (string)getenv('CDX_SCORE_SOURCE_SHA'))) {
    fwrite(STDERR, "DENIED: exact one-lane read-only source approval 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 source watchdog unavailable');
}
pcntl_async_signals(true);
pcntl_signal(SIGALRM, static function (): void {
    throw new RuntimeException('read-only source 20-second deadline');
});
pcntl_alarm(20);

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

function connectionDm(string $port, string $schema, string $user, string $password): PDO
{
    $db = 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 = $db->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('nonproduction DM business schema drift');
    }
    return $db;
}

function rowsWithinBudget(PDO $db, string $sql, array $bindings = [], int $maxRows = 0): array
{
    $started = microtime(true);
    $statement = $db->prepare($sql);
    $statement->execute($bindings);
    $rows = [];
    while (($row = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
        $rows[] = array_change_key_case($row, CASE_LOWER);
        if ($maxRows > 0 && count($rows) > $maxRows) {
            throw new RuntimeException('bounded source row cap exceeded');
        }
    }
    $statement->closeCursor();
    if (microtime(true) - $started > MAX_QUERY_SECONDS) {
        throw new RuntimeException('per-query five-second source budget exceeded');
    }
    return $rows;
}

try {
    if ($mode === '--mysql') {
        if (PHP_OS_FAMILY !== 'Darwin' || realpath(LOCAL_ENV) !== LOCAL_ENV) {
            throw new RuntimeException('code-local Mac config unavailable');
        }
        $ini = parse_ini_file(LOCAL_ENV, true, INI_SCANNER_RAW);
        if (!is_array($ini) || !isset($ini['DATABASE'], $ini['DATABASE_ZHCT'])) {
            throw new RuntimeException('both code-local business database sections required');
        }
        $ai = connectionMysql($ini['DATABASE'], DEV_AI_SHA);
        $store = connectionMysql($ini['DATABASE_ZHCT'], DEV_STORE_SHA);
        $staffTable = 'ydy_staff';
        $staffDepartmentTable = 'ydy_staff_department';
        $departmentTable = 'ydy_department';
        $scoreTable = 'yoshop_user_score';
        $userTable = 'yoshop_user';
        $limitDept = 'LIMIT 50';
        $limitLinks = 'LIMIT 2001';
    } else {
        if (PHP_OS_FAMILY !== 'Linux') throw new RuntimeException('C host platform drift');
        $raw = @file_get_contents('/proc/53772/environ');
        $cmdline = @file_get_contents('/proc/53772/cmdline');
        if ($raw === false || $cmdline === false
            || strpos($cmdline, '-S' . "\0" . '127.0.0.1:18102') === false
            || strpos($cmdline, '/tmp/zhct-ai-http-20260929.3XDj3w/public') === false) {
            throw new RuntimeException('fixed C isolated process unavailable');
        }
        $env = [];
        foreach (explode("\0", $raw) as $part) {
            $at = strpos($part, '=');
            if ($at !== false) $env[substr($part, 0, $at)] = substr($part, $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 nonproduction target drift');
            }
        }
        if (empty($env['POC_AI_PASSWORD']) || empty($env['POC_PASSWORD'])) {
            throw new RuntimeException('private C credentials unavailable');
        }
        $ai = connectionDm('5238', 'AIZHCT_APP', $env['POC_AI_USER'], $env['POC_AI_PASSWORD']);
        $store = connectionDm('5237', 'ZHCT_APP', $env['POC_USER'], $env['POC_PASSWORD']);
        $staffTable = 'YDY_STAFF';
        $staffDepartmentTable = 'YDY_STAFF_DEPARTMENT';
        $departmentTable = 'YDY_DEPARTMENT';
        $scoreTable = 'YOSHOP_USER_SCORE';
        $userTable = 'YOSHOP_USER';
        $limitDept = 'FETCH FIRST 50 ROWS ONLY';
        $limitLinks = 'FETCH FIRST 2001 ROWS ONLY';
    }

    $stage = 'top-fifty-active-departments';
    $departmentSql = 'SELECT sd.DEPARTMENT_UUID,COUNT(DISTINCT s.AI_USER_ID) STAFF_COUNT FROM '
        . $staffTable . ' s JOIN ' . $staffDepartmentTable . ' 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'
        . ' GROUP BY sd.DEPARTMENT_UUID ORDER BY STAFF_COUNT DESC,sd.DEPARTMENT_UUID ASC '
        . $limitDept;
    $departmentRows = rowsWithinBudget($store, $departmentSql, [], MAX_DEPARTMENTS + 1);
    if (count($departmentRows) > MAX_DEPARTMENTS) {
        throw new RuntimeException('more than fifty departments require separate approval');
    }
    if (!$departmentRows) throw new RuntimeException('no active natural department candidates');
    $departments = array_column($departmentRows, 'department_uuid');
    $departmentStaff = [];
    foreach ($departmentRows as $row) {
        $departmentStaff[(string)$row['department_uuid']] = (int)$row['staff_count'];
    }

    $stage = 'bounded-staff-to-ai-id-mapping';
    $marks = implode(',', array_fill(0, count($departments), '?'));
    $mappingSql = 'SELECT DISTINCT sd.DEPARTMENT_UUID,s.AI_USER_ID FROM '
        . $staffTable . ' s JOIN ' . $staffDepartmentTable . ' 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 sd.DEPARTMENT_UUID IN (' . $marks . ')'
        . ' ORDER BY sd.DEPARTMENT_UUID,s.AI_USER_ID ' . $limitLinks;
    $links = rowsWithinBudget($store, $mappingSql, $departments, MAX_STAFF_LINKS + 1);
    if (count($links) > MAX_STAFF_LINKS) {
        throw new RuntimeException('more than two thousand staff links require separate approval');
    }
    $byDepartment = [];
    $aiIds = [];
    foreach ($links as $link) {
        $department = (string)$link['department_uuid'];
        $userId = (int)$link['ai_user_id'];
        if ($userId <= 0 || !isset($departmentStaff[$department])) {
            throw new RuntimeException('invalid staff-to-ai mapping');
        }
        $byDepartment[$department][$userId] = true;
        $aiIds[$userId] = true;
    }
    if (!$aiIds) throw new RuntimeException('no linked AI user IDs');

    $stage = 'three-score-type-aggregation';
    $users = array_keys($aiIds);
    $marks = implode(',', array_fill(0, count($users), '?'));
    $scoreSql = 'SELECT s.USER_ID,s.TYPE,COUNT(*) SCORE_ROWS,'
        . 'SUM(CASE WHEN s.SCORE>0 THEN 1 ELSE 0 END) POSITIVE_ROWS 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 IN (1,2,3)'
        . ' AND s.USER_ID IN (' . $marks . ')'
        . ' GROUP BY s.USER_ID,s.TYPE';
    $scoreRows = rowsWithinBudget($ai, $scoreSql, $users, MAX_STAFF_LINKS * 3);
    $scoreByUser = [];
    foreach ($scoreRows as $row) {
        $userId = (int)$row['user_id'];
        $type = (int)$row['type'];
        if ($userId <= 0 || $type < 1 || $type > 3) {
            throw new RuntimeException('unexpected score aggregation key');
        }
        $scoreByUser[$userId][$type] = [(int)$row['score_rows'], (int)$row['positive_rows']];
    }
    $winner = null;
    foreach ($byDepartment as $department => $ids) {
        $counts = [1 => 0, 2 => 0, 3 => 0];
        $positive = [1 => 0, 2 => 0, 3 => 0];
        foreach (array_keys($ids) as $id) {
            foreach ([1, 2, 3] as $type) {
                $counts[$type] += $scoreByUser[$id][$type][0] ?? 0;
                $positive[$type] += $scoreByUser[$id][$type][1] ?? 0;
            }
        }
        if (min($counts) > 0 && min($positive) > 0) {
            $winner = ['department_uuid_sha256' => hash('sha256', $department),
                'staff_count' => $departmentStaff[$department],
                'score_type1_rows' => $counts[1], 'score_type2_rows' => $counts[2],
                'score_type3_rows' => $counts[3],
                'positive_type1_rows' => $positive[1],
                'positive_type2_rows' => $positive[2],
                'positive_type3_rows' => $positive[3]];
            break;
        }
    }
    echo json_encode(['status' => $winner ? 'natural-three-ranks-ready' : 'natural-three-ranks-blocked',
        'lane' => $mode === '--mysql' ? 'A-B-shared-dev-MySQL' : 'C-nonprod-DM',
        'scope' => 'top-50-active-departments-only',
        'department_candidates' => count($departments),
        'staff_links_in_memory' => count($links),
        'distinct_ai_users_in_memory' => count($aiIds),
        'positive_three_type_candidate' => $winner,
        'names_or_user_ids_output' => false,
        'business_http_or_dml' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
    pcntl_alarm(0);
    exit($winner ? 0 : 4);
} catch (Throwable $error) {
    pcntl_alarm(0);
    $handler = set_exception_handler(null);
    if (is_callable($handler)) $handler($error);
    exit(3);
}
