<?php

declare(strict_types=1);

$dialect = (string)getenv('POC_DIALECT');
$root = (string)getenv('POC_APP_ROOT');
$base = rtrim((string)getenv('POC_BASE_URL'), '/');
$targetAccount = (string)getenv('POC_REFUND_ACCOUNT');
$ossStub = (string)getenv('POC_OSS_STUB');
if (!in_array($dialect, ['mysql', 'dm'], true) || strpos($root, '/tmp/zhct-') !== 0
    || $base === '' || !preg_match('/^CDXPAY[A-F0-9]{8}$/D', $targetAccount)
    || !is_file($ossStub) || !is_file($root . '/application/base/Security.php')) {
    throw new RuntimeException('Refund export isolated settings invalid');
}
$boundary = json_decode((string)file_get_contents($base . '/__poc_boundary'), true);
if (!is_array($boundary) || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated'])) {
    throw new RuntimeException('Refund export isolation boundary absent');
}
$downloadRoot = $root . '/public/static/download';
if (is_link($downloadRoot) || (file_exists($downloadRoot) && realpath($downloadRoot) !== $downloadRoot)) {
    throw new RuntimeException('Download output points outside isolated runtime');
}
$downloadRootExisted = is_dir($downloadRoot);
$dateDir = $downloadRoot . '/' . date('Ymd');
$dateDirExisted = is_dir($dateDir);
if ($dateDirExisted || is_link($dateDir)) {
    throw new RuntimeException('Export date directory already exists; refuse shared-file cleanup');
}

$env = [];
foreach (['POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER', 'POC_PASSWORD'] as $key) {
    $env[$key] = (string)getenv($key);
}
$runtimePid = (string)getenv('POC_RUNTIME_PID');
if ($dialect === 'dm' && $env['POC_HOST'] === '' && ctype_digit($runtimePid)) {
    foreach (explode("\0", (string)file_get_contents('/proc/' . $runtimePid . '/environ')) as $entry) {
        $parts = explode('=', $entry, 2);
        if (count($parts) === 2 && array_key_exists($parts[0], $env)) $env[$parts[0]] = $parts[1];
    }
}
if ($dialect === 'mysql' && $env['POC_HOST'] === '') {
    $local = $root . '/application/config/local/database.php';
    $config = require (is_file($local) ? $local : $root . '/application/database.php');
    $env = [
        'POC_HOST' => (string)$config['hostname'],
        'POC_PORT' => (string)($config['hostport'] ?? '3306'),
        'POC_SCHEMA' => (string)$config['database'],
        'POC_USER' => (string)$config['username'],
        'POC_PASSWORD' => (string)$config['password'],
    ];
}
if ($env['POC_HOST'] === '' || $env['POC_SCHEMA'] === '' || $env['POC_USER'] === '') {
    throw new RuntimeException('Refund export DB connection missing');
}
$dsn = $dialect === 'dm'
    ? "dm:host={$env['POC_HOST']};port={$env['POC_PORT']};schema={$env['POC_SCHEMA']}"
    : "mysql:host={$env['POC_HOST']};port={$env['POC_PORT']};dbname={$env['POC_SCHEMA']};charset=utf8mb4";
$pdo = new PDO($dsn, $env['POC_USER'], $env['POC_PASSWORD'],
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$prefix = $dialect === 'dm' ? 'YDY_' : 'ydy_';
$jobTable = $prefix . ($dialect === 'dm' ? 'EXPORT_LOG' : 'export_log');
$userTable = $prefix . ($dialect === 'dm' ? 'USER' : 'user');
$refundTable = $prefix . ($dialect === 'dm' ? 'MEAL_ORDER_REFUND' : 'meal_order_refund');
$mealTable = $prefix . ($dialect === 'dm' ? 'MEAL_ORDER' : 'meal_order');
$pendingCount = static function () use ($pdo, $jobTable): int {
    return (int)$pdo->query('SELECT COUNT(*) FROM ' . $jobTable . ' WHERE STATUS=0')->fetchColumn();
};
if ($pendingCount() !== 0) throw new RuntimeException('Other pending export jobs exist');

$sourceStmt = $pdo->prepare('SELECT refund.ID,refund.ORDER_NO AS REFUND_NO,'
    . ' refund.REFUND_AMOUNT,refund.REFUND_STATUS,refund.APPLY_DATE,'
    . ' meal.ORDER_NO,meal.TOTAL_PRICE,u.NAME AS USER_NAME'
    . ' FROM ' . $refundTable . ' refund JOIN ' . $userTable . ' u ON u.ID=refund.USER_ID'
    . ' JOIN ' . $mealTable . ' meal ON meal.ID=refund.MEAL_ORDER_ID'
    . ' WHERE u.ACCOUNT=? AND refund.IS_DELETE=0 AND refund.REFUND_STATUS=2'
    . ' ORDER BY refund.ID DESC ' . ($dialect === 'dm' ? 'FETCH FIRST 2 ROWS ONLY' : 'LIMIT 2'));
$sourceStmt->execute([$targetAccount]);
$sourceRows = array_map(static function (array $row): array {
    return array_change_key_case($row, CASE_LOWER);
}, $sourceStmt->fetchAll(PDO::FETCH_ASSOC));
if (count($sourceRows) !== 1 || (string)$sourceRows[0]['user_name'] !== $targetAccount) {
    throw new RuntimeException('One uniquely named CDXPAY refunded order is required');
}
$source = $sourceRows[0];
$date = substr((string)$source['apply_date'], 0, 10);

$admin = 'CDXPARSTEXP' . strtoupper(bin2hex(random_bytes(6)));
$adminUuid = $admin;
$adminId = 0;
$jobId = 0;
$xlsxPath = '';
$results = [];
require_once $root . '/application/base/Security.php';
$token = \app\base\Security::encrypt(implode("\t", ['0', $adminUuid, $admin, '0', '', '', $admin]));
$post = static function (string $route, array $form, bool $auth = true) use ($base, $token): array {
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($auth) $headers .= 'token: ' . $token . "\r\n";
    $context = stream_context_create(['http' => [
        'method' => 'POST', 'header' => $headers,
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'timeout' => 20, 'ignore_errors' => true,
    ]]);
    $body = file_get_contents($base . $route, false, $context);
    $headersOut = $http_response_header ?? [];
    if (strpos((string)($headersOut[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('Refund export HTTP status mismatch');
    }
    $response = is_string($body) ? json_decode($body, true) : null;
    if (!is_array($response)) throw new RuntimeException('Refund export HTTP response not JSON');
    return $response;
};
$shape = static function ($value) use (&$shape): string {
    if (!is_array($value)) return gettype($value);
    $keys = array_keys($value);
    if ($keys === range(0, count($keys) - 1)) {
        return '[' . implode(',', array_map($shape, $value)) . ']';
    }
    sort($keys, SORT_STRING);
    $parts = [];
    foreach ($keys as $key) $parts[] = $key . ':' . $shape($value[$key]);
    return '{' . implode(',', $parts) . '}';
};
$jobById = $pdo->prepare('SELECT ID,STORE_ID,TYPE,STATUS,FILE_NAME,FILE_PATH,'
    . ' CREATE_BY_UUID,QUERY_CONDITIONS FROM ' . $jobTable . ' WHERE ID=?');
$dbString = static function ($value): string {
    return is_resource($value) ? (string)stream_get_contents($value) : (string)$value;
};
$jobForAdmin = $pdo->prepare('SELECT ID FROM ' . $jobTable
    . ' WHERE CREATE_BY_UUID=? AND TYPE=13 ORDER BY ID DESC');
$deleteJob = $pdo->prepare('DELETE FROM ' . $jobTable . ' WHERE ID=? AND CREATE_BY_UUID=? AND TYPE=13');
$deleteAdmin = $pdo->prepare('DELETE FROM ' . $userTable . ' WHERE ID=? AND UUID=? AND ACCOUNT=?');
try {
    $now = date('Y-m-d H:i:s');
    $insertAdmin = $pdo->prepare('INSERT INTO ' . $userTable
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $insertAdmin->execute([$adminUuid, $admin, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $admin, 0, $now, 'CODEX', 0]);
    $readAdminId = $pdo->prepare('SELECT ID FROM ' . $userTable . ' WHERE UUID=? AND ACCOUNT=?');
    $readAdminId->execute([$adminUuid, $admin]);
    $adminId = (int)$readAdminId->fetchColumn();
    if ($adminId <= 0) throw new RuntimeException('Synthetic export operator insert failed');

    $form = ['start_date' => $date, 'end_date' => $date,
        'nickname' => $targetAccount, 'refund_status' => '2'];
    $anonymous = $post('/p/mealOrder/refundExport', $form, false);
    $jobForAdmin->execute([$adminUuid]);
    $results['anonymous_rejected'] = [
        'status' => (int)($anonymous['code'] ?? -1) === 100
            && $jobForAdmin->fetchAll(PDO::FETCH_ASSOC) === []
            ? 'tested-pass' : 'tested-fail',
        'message' => $anonymous['msg'] ?? $anonymous['message'] ?? null,
        'shape_sha256' => hash('sha256', $shape($anonymous)),
    ];

    $submitted = $post('/p/mealOrder/refundExport', $form);
    $jobForAdmin->execute([$adminUuid]);
    $jobs = $jobForAdmin->fetchAll(PDO::FETCH_ASSOC);
    if ((int)($submitted['code'] ?? -1) !== 0 || count($jobs) !== 1) {
        throw new RuntimeException('Formal refund export did not enqueue exactly one job');
    }
    $jobId = (int)array_change_key_case($jobs[0], CASE_LOWER)['id'];
    $jobById->execute([$jobId]);
    $job = array_change_key_case($jobById->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
    $conditions = json_decode($dbString($job['query_conditions'] ?? ''), true);
    if ((int)($job['type'] ?? 0) !== 13 || (int)($job['status'] ?? -1) !== 0
        || (string)($job['create_by_uuid'] ?? '') !== $adminUuid
        || ($conditions['nickname'] ?? null) !== $targetAccount
        || (int)($conditions['refund_status'] ?? 0) !== 2
        || $pendingCount() !== 1) {
        throw new RuntimeException('Only the isolated refund export task may be pending');
    }
    $results['http_enqueue'] = ['status' => 'tested-pass', 'job_count' => 1,
        'job_type' => 13, 'status_code' => $submitted['code'],
        'message' => $submitted['message'] ?? null,
        'shape_sha256' => hash('sha256', $shape($submitted))];

    $command = escapeshellarg(PHP_BINARY)
        . ' -c ' . escapeshellarg((string)php_ini_loaded_file());
    if ($dialect === 'dm') {
        $driverDir = '/home/dmdba/dmdbms/drivers/php_pdo/';
        $command .= ' -d extension=' . escapeshellarg($driverDir . 'libphp73_dm.so')
            . ' -d extension=' . escapeshellarg($driverDir . 'php73_pdo_dm.so');
    }
    $command .= ' -d auto_prepend_file=' . escapeshellarg($ossStub)
        . ' ' . escapeshellarg($root . '/think') . ' ExportMealOrder';
    $process = proc_open($command,
        [0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
        $pipes, $root);
    if (!is_resource($process)) throw new RuntimeException('Isolated export worker did not start');
    $workerOut = stream_get_contents($pipes[1]);
    $workerErr = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    $workerExit = proc_close($process);
    $jobById->execute([$jobId]);
    $job = array_change_key_case($jobById->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
    if ($workerExit !== 0 || (int)($job['status'] ?? -1) !== 1
        || $dbString($job['file_name'] ?? '') === ''
        || $dbString($job['file_path'] ?? '') === '') {
        throw new RuntimeException('Isolated export worker failed; status=' . ($job['status'] ?? 'missing')
            . ', exit=' . $workerExit . ', output_bytes=' . strlen((string)$workerOut)
            . ', error_bytes=' . strlen((string)$workerErr));
    }
    $results['worker_complete'] = ['status' => 'tested-pass', 'worker_exit' => 0,
        'job_status' => 1, 'pending_jobs' => $pendingCount()];

    $pathPart = parse_url($dbString($job['file_path']), PHP_URL_PATH);
    $fileName = $dbString($job['file_name']);
    if (preg_match('#^[^/]+_' . $jobId . '\.xlsx$#uD', $fileName)) {
        // This explicit job-ID filename is also the safe fallback for finally cleanup.
        $xlsxPath = $dateDir . '/' . $fileName;
    }
    $pathChecks = [
        'path_string' => is_string($pathPart),
        'path_regex' => is_string($pathPart)
            && preg_match('#^/static/download/[0-9]{8}/[^/]+_' . $jobId . '\.xlsx$#uD', $pathPart) === 1,
        // PHP basename() under the CLI C locale can strip a Chinese filename prefix.
        'basename_matches' => is_string($pathPart)
            && substr($pathPart, strrpos($pathPart, '/') + 1) === $fileName,
        'cleanup_name_valid' => $xlsxPath !== '',
    ];
    if (in_array(false, $pathChecks, true)) {
        throw new RuntimeException('Export file path not limited to this job: '
            . json_encode($pathChecks));
    }
    $xlsxPath = $root . '/public' . $pathPart;
    if (realpath($xlsxPath) !== $xlsxPath
        || strpos($xlsxPath, $dateDir . '/') !== 0 || !is_file($xlsxPath)) {
        throw new RuntimeException('Export file is not in private runtime');
    }
    $zip = new ZipArchive();
    if ($zip->open($xlsxPath) !== true) throw new RuntimeException('XLSX ZIP cannot open');
    $sheet = $zip->getFromName('xl/worksheets/sheet1.xml');
    $zip->close();
    $xml = is_string($sheet) ? simplexml_load_string($sheet) : false;
    if (!$xml) throw new RuntimeException('XLSX worksheet XML invalid');
    $xml->registerXPathNamespace('x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
    $rows = $xml->xpath('//x:sheetData/x:row');
    if (!is_array($rows) || count($rows) !== 2) {
        throw new RuntimeException('Refund XLSX must contain header and one source row');
    }
    $namespace = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
    $rows[1]->registerXPathNamespace('x', $namespace);
    $row = [];
    foreach ($rows[1]->xpath('./x:c') as $cell) {
        $cell->registerXPathNamespace('x', $namespace);
        $column = preg_replace('/[0-9]+/', '', (string)$cell['r']);
        $inline = $cell->xpath('./x:is/x:t');
        $numeric = $cell->xpath('./x:v');
        $row[$column] = isset($inline[0]) ? (string)$inline[0]
            : (isset($numeric[0]) ? (string)$numeric[0] : '');
    }
    if (($row['B'] ?? null) !== (string)$source['order_no']
        || ($row['C'] ?? null) !== (string)$source['refund_no']
        || ($row['D'] ?? null) !== $targetAccount
        || bccomp((string)($row['E'] ?? '0'), (string)$source['total_price'], 2) !== 0
        || bccomp((string)($row['F'] ?? '0'), (string)$source['refund_amount'], 2) !== 0) {
        throw new RuntimeException('Non-empty refund XLSX differs from source business rows');
    }
    $results['xlsx_readback'] = ['status' => 'tested-pass', 'business_rows' => 1,
        'file_bytes' => filesize($xlsxPath), 'amounts_match_source' => true];

    $logs = $post('/p/mealOrder/refundExportLogs', ['page' => 1, 'page_size' => 5]);
    $items = (array)($logs['data']['data'] ?? []);
    $matches = array_values(array_filter($items, static function ($item) use ($jobId): bool {
        return is_array($item) && (int)($item['id'] ?? 0) === $jobId;
    }));
    $results['http_logs'] = [
        'status' => (int)($logs['code'] ?? -1) === 0 && count($matches) === 1
            && (int)($matches[0]['status'] ?? -1) === 1
            && (string)($matches[0]['url'] ?? '') === $dbString($job['file_path'])
            ? 'tested-pass' : 'tested-fail',
        'matching_job_rows' => count($matches),
        'shape_sha256' => hash('sha256', $shape($logs)),
    ];
} finally {
    if ($xlsxPath !== '' && is_file($xlsxPath) && strpos($xlsxPath, $dateDir . '/') === 0) {
        unlink($xlsxPath);
    }
    if (is_dir($dateDir) && !$dateDirExisted) rmdir($dateDir);
    if (is_dir($downloadRoot) && !$downloadRootExisted) rmdir($downloadRoot);
    if ($jobId > 0) $deleteJob->execute([$jobId, $adminUuid]);
    if ($adminId > 0) $deleteAdmin->execute([$adminId, $adminUuid, $admin]);
    $jobById->execute([$jobId]);
    if ($jobId > 0 && $jobById->fetch(PDO::FETCH_ASSOC)) {
        throw new RuntimeException('Synthetic export log was not removed');
    }
    if (!$dateDirExisted && is_dir($dateDir)) {
        throw new RuntimeException('Private refund export date directory was not removed');
    }
}
$results['cleanup'] = ['status' => 'tested-pass',
    'job_rows' => 0, 'operator_rows' => 0, 'export_file_exists' => false];
echo json_encode(['dialect' => $dialect, 'module' => 'store-refund-export',
    'results' => $results], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
