<?php
/**
 * Slice 8Z-u · End-to-end smoke for the PM Kickoff Brief render on Projects/view.
 *
 * Temporarily links Vodacom sample tender 400 to an existing project, seeds a
 * kickoff brief with FM + Exec halves, asserts the controller's data hydration
 * shape, then restores state.
 */
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__DIR__));
require_once ROOT . DS . 'config' . DS . 'config.php';
require_once ROOT . DS . 'core' . DS . 'Application.php';
require_once ROOT . DS . 'core' . DS . 'Model.php';
require_once ROOT . DS . 'core' . DS . 'DB.php';
foreach (['Tenders.php','TenderExecKickoffBrief.php'] as $m) require_once ROOT . DS . 'app' . DS . 'models' . DS . $m;

function ok(bool $c, string $m): void { echo ($c ? "  ✓ " : "  ✗ ") . $m . "\n"; }
function h(string $t): void { echo "\n==== $t ====\n"; }

$db  = new DB();
$tid = 400;
$tm  = new Tenders();
$kbm = new TenderExecKickoffBrief();

/* Find a project we can temporarily link to (pick the latest) */
$pRs = $db->mySelect("SELECT c0d FROM c_projects WHERE deleted = 0 ORDER BY c0d DESC LIMIT 1", []);
if (empty($pRs)) { echo "✗ no project to test against.\n"; exit(1); }
$pid = (int)$pRs[0]['c0d'];

/* Snapshot original tender_tracker link so we can restore */
$origLinkRs = $db->mySelect("SELECT linked_project_id FROM tender_tracker WHERE c0d = :t", [':t' => $tid]);
$origLink   = !empty($origLinkRs) ? (int)$origLinkRs[0]['linked_project_id'] : 0;

/* Clean any prior smoke brief */
$db->insertUpdate("DELETE FROM tender_exec_kickoff_brief WHERE tender_id = :t", [':t' => $tid]);

/* ── Seed a full brief (FM + Exec halves) ─────────────────────────── */
h('1. Seed FM half + Exec half on tender 400');
$fmRes = $kbm->save($tid, [
    'gate_insurance_activated' => 1,
    'gate_advance_requested'   => 1,
    'gate_tra_efiscal_setup'   => 1,
    'gate_wcf_nssf_verified'   => 1,
    'risk_client_payment'      => 1,
    'risk_fx_exposure'         => 0,
    'risk_wcf_nssf_compliance' => 0,
], 42, 'FM Test', 'Finance Manager');
ok($fmRes['ok'] === true, 'FM half saved');

$execRes = $kbm->save($tid, [
    'priority'                     => 'HIGH',
    'target_kickoff_date'          => '2026-09-15',
    'instructions_to_pm'           => "Kick off with a site visit before any spend.\nBrief Vodacom NOC lead within 48h of PM assignment.",
    'margin_floor_pct'             => 15.50,
    'change_order_threshold_pct'   => 5.00,
    'gate_site_visit'              => 1,
    'gate_mobilization'            => 1,
    'gate_long_lead'               => 0,
    'gate_permits'                 => 0,
    'gate_osha_safety_plan'        => 1,
    'risk_tight_schedule'          => 1,
    'risk_site_access'             => 0,
    'risk_scope_ambiguity'         => 1,
    'risk_ppra_reporting'          => 0,
    'reporting_cadence'            => 'WEEKLY',
    'escalation_contact_name'      => 'CFO · direct line',
], 99, 'Exec Test', 'Executive');
ok($execRes['ok'] === true, 'Exec half saved');

/* ── Link tender 400 → project $pid so Projects/view hydrates the brief ── */
h("2. Link tender {$tid} to project {$pid} for the render test");
$db->insertUpdate("UPDATE tender_tracker SET linked_project_id = :p WHERE c0d = :t", [':p' => $pid, ':t' => $tid]);
$lnk = $db->mySelect("SELECT linked_project_id FROM tender_tracker WHERE c0d = :t", [':t' => $tid]);
ok((int)$lnk[0]['linked_project_id'] === $pid, "tender {$tid}.linked_project_id = {$pid}");

/* ── Verify sourceTenderContextForProject returns the tender ─────── */
h('3. sourceTenderContextForProject returns the tender');
$ctx = $tm->sourceTenderContextForProject($pid);
ok(!empty($ctx['tender']),               'context has tender key');
ok((int)$ctx['tender']['c0d'] === $tid,  "context.tender.c0d = {$tid}");

/* ── Fetch brief via the same helper the controller uses ─────────── */
h('4. Brief hydration');
$brief   = $kbm->forTender($tid);
$ceiling = $kbm->budgetCeiling($tid);
ok(!empty($brief),                          'brief row present');
ok((string)$brief['priority'] === 'HIGH',   'priority = HIGH');
ok((int)$brief['gate_insurance_activated'] === 1, 'FM gate persisted');
ok((int)$brief['risk_tight_schedule'] === 1, 'Exec risk persisted');
ok(!empty($brief['instructions_to_pm']),    'instructions_to_pm persisted');
ok(!empty($brief['fm_edited_on']),          'fm_edited_on stamped');
ok(!empty($brief['exec_edited_on']),        'exec_edited_on stamped');
/* Ceiling may be null if the linked project's tender has no contract_value row */
echo "  · budget_ceiling = " . ($ceiling === null ? 'null (expected when contract_value not set)' : number_format((float)$ceiling)) . "\n";

/* ── HTTP bootstrap check ─────────────────────────────────────────── */
h('5. HTTP bootstrap check on Projects/view/{pid}');
$url = 'http://localhost/rsflow.dspera/Projects/view/' . $pid;
$ch  = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_FOLLOWLOCATION => false]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
ok($code === 200 || $code === 302, "HTTP {$code} (200=rendered · 302=login gate)");
$fatal = ($body && preg_match('/Fatal error|Parse error|Uncaught/i', $body)) ? true : false;
ok(!$fatal, 'no Fatal / Parse / Uncaught in response body');

/* ── Cleanup ─────────────────────────────────────────────────────── */
h('6. Restore original state');
$db->insertUpdate("UPDATE tender_tracker SET linked_project_id = :p WHERE c0d = :t", [':p' => $origLink ?: null, ':t' => $tid]);
$db->insertUpdate("DELETE FROM tender_exec_kickoff_brief WHERE tender_id = :t", [':t' => $tid]);
ok(true, "tender {$tid}.linked_project_id restored to " . ($origLink ?: 'NULL'));
ok(true, 'kickoff brief row cleaned up');

echo "\n=== DONE ===\n";
