<?php
/**
 * Slice 8Z-v · Services PM Workspace smoke test.
 * Runs against project 634 (Vodacom PM · linked to tender 400).
 */
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 (['Projects.php','ServiceContracts.php','TenderServiceSpec.php',
          'ProjectServiceOverheadAnnotations.php','ProjectOpsDocuments.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();
$pid = 634;
$sc  = new ServiceContracts();
$pm  = new Projects();
$ohn = new ProjectServiceOverheadAnnotations();
$opd = new ProjectOpsDocuments();

/* Cleanup any prior smoke rows */
$db->insertUpdate("DELETE FROM project_service_overhead_annotations WHERE project_id = :p", [':p' => $pid]);
$db->insertUpdate("UPDATE service_contracts SET pm_spec_confirmed_on = NULL, pm_spec_confirmed_by = NULL, pm_spec_confirmed_by_name = NULL WHERE project_id = :p", [':p' => $pid]);

/* ── 1. Service contract exists for project 634 ───────────────────── */
h('1. Auto-created service contract exists');
$scRow = $db->mySelect("SELECT c0d, contract_no, status, spare_policy, invoice_cadence, payment_terms_days, callout_fee FROM service_contracts WHERE project_id = :p AND deleted = 0", [':p' => $pid]);
ok(!empty($scRow),                                    'service_contract row exists for project 634');
$cid = (int)($scRow[0]['c0d'] ?? 0);
ok($cid > 0,                                          "contract id = {$cid}");
ok((string)$scRow[0]['status'] === 'DRAFT',           'status = DRAFT');
ok((string)$scRow[0]['spare_policy'] === 'CLIENT',    'spare_policy = CLIENT (from tender spec)');
ok((int)$scRow[0]['payment_terms_days'] === 45,       'payment_terms_days = 45 (from tender spec)');

/* ── 2. Batch A · PM confirms spec ────────────────────────────────── */
h('2. PM confirm spec (Batch A)');
$sc->confirmSpec($cid, 999, 'Smoke PM');
$scAfter = $db->mySelect("SELECT pm_spec_confirmed_on, pm_spec_confirmed_by_name FROM service_contracts WHERE c0d = :id", [':id' => $cid]);
ok(!empty($scAfter[0]['pm_spec_confirmed_on']),                       'pm_spec_confirmed_on stamped');
ok((string)$scAfter[0]['pm_spec_confirmed_by_name'] === 'Smoke PM',   'pm_spec_confirmed_by_name recorded');

/* ── 3. Batch E · overhead annotation ─────────────────────────────── */
h('3. PM annotates a tender overhead line (Batch E)');
$ohRs = $db->mySelect(
    "SELECT o.c0d FROM pricing_version_overheads o
       JOIN tender_pricing_versions v ON v.c0d = o.version_id
      WHERE v.tender_id = 400 AND o.deleted = 0 LIMIT 1", []);
$tohId = (int)($ohRs[0]['c0d'] ?? 0);
ok($tohId > 0, "picked tender overhead id {$tohId}");
$noteRes = $ohn->save($pid, $tohId, ['actual_amount' => 35000000, 'notes' => 'Bumped 5% for OT premium'], 999, 'Smoke PM');
ok($noteRes['ok'] === true,                                'save returned ok');
$keyed = $ohn->forProjectKeyed($pid);
ok(!empty($keyed[$tohId]),                                 'annotation exists for that overhead');
ok((float)$keyed[$tohId]['actual_amount'] === 35000000.0,  'actual_amount persisted');
ok(strpos((string)$keyed[$tohId]['notes'], 'OT premium') !== false, 'notes persisted');

/* Second save = update, not duplicate */
$ohn->save($pid, $tohId, ['actual_amount' => 36000000, 'notes' => 'Revised'], 999, 'Smoke PM');
$keyed2 = $ohn->forProjectKeyed($pid);
ok((float)$keyed2[$tohId]['actual_amount'] === 36000000.0, 're-save updates in place');
$countRs = $db->mySelect("SELECT COUNT(*) n FROM project_service_overhead_annotations WHERE project_id = :p AND tender_overhead_id = :t", [':p' => $pid, ':t' => $tohId]);
ok((int)$countRs[0]['n'] === 1,                            'still exactly one row for (project, overhead)');

/* ── 4. Batch F · PM saves instructions ───────────────────────────── */
h('4. PM saves structured instructions (Batch F)');
$origPmId = (int)($db->mySelect("SELECT project_manager_id FROM c_projects WHERE c0d = :p", [':p' => $pid])[0]['project_manager_id'] ?? 0);
$db->insertUpdate("UPDATE c_projects SET project_manager_id = 999 WHERE c0d = :p", [':p' => $pid]);
$_SESSION['user_group_name'] = 'Project Manager';
try {
    $pm->saveSvcInstructions($pid, [
        'templates_needed'   => 'Quarterly PM visit sheet',
        'sites_instructions' => 'Batch 1: 10 Coast sites',
        'team_instructions'  => '2 FS techs + 2 pickups',
    ], 999);
    $pRow = $db->mySelect("SELECT pm_svc_templates_needed, pm_svc_sites_instructions, pm_svc_team_instructions FROM c_projects WHERE c0d = :p", [':p' => $pid]);
    ok(strpos((string)$pRow[0]['pm_svc_templates_needed'], 'Quarterly PM') !== false, 'templates_needed persisted');
    ok(strpos((string)$pRow[0]['pm_svc_sites_instructions'], 'Coast sites') !== false, 'sites_instructions persisted');
    ok(strpos((string)$pRow[0]['pm_svc_team_instructions'], 'pickups') !== false, 'team_instructions persisted');
} finally {
    $db->insertUpdate("UPDATE c_projects SET project_manager_id = :o WHERE c0d = :p", [':o' => $origPmId, ':p' => $pid]);
}

/* ── 5. Batch G · mandatory-check gate ────────────────────────────── */
h('5. Ops docs mandatory check (Batch G)');
$mand = $opd->mandatoryCheck($pid);
ok($mand['ok'] === false,                                  'no ops docs yet → mandatory-check fails');
$mCnt = 0; foreach (ProjectOpsDocuments::SUBTYPES as $s) if (!empty($s['mandatory'])) $mCnt++;
ok(count($mand['missing']) === $mCnt,                      "missing count = {$mCnt} mandatory subtypes");
ok(in_array('SIGNED_CONTRACT', $mand['missing'], true),    'SIGNED_CONTRACT flagged missing');
ok(in_array('CAR_INSURANCE',   $mand['missing'], true),    'CAR_INSURANCE flagged missing');
ok(in_array('OSHA_METHOD_STATEMENT', $mand['missing'], true), 'OSHA_METHOD_STATEMENT flagged missing');

/* ── 6. Cleanup ───────────────────────────────────────────────────── */
$db->insertUpdate("DELETE FROM project_service_overhead_annotations WHERE project_id = :p", [':p' => $pid]);
$db->insertUpdate("UPDATE service_contracts SET pm_spec_confirmed_on = NULL, pm_spec_confirmed_by = NULL, pm_spec_confirmed_by_name = NULL WHERE project_id = :p", [':p' => $pid]);
$db->insertUpdate("UPDATE c_projects SET pm_svc_templates_needed = NULL, pm_svc_sites_instructions = NULL, pm_svc_team_instructions = NULL WHERE c0d = :p", [':p' => $pid]);

/* ── 7. HTTP bootstrap ────────────────────────────────────────────── */
h('7. HTTP bootstrap Projects/view/634');
$ch = curl_init('http://localhost/rsflow.dspera/Projects/view/' . $pid);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
ok($code === 200 || $code === 302,                                     "HTTP {$code}");
ok(!preg_match('/Fatal error|Parse error|Uncaught/i', (string)$body), 'no fatal / parse / uncaught');

echo "\n=== DONE (state restored) ===\n";
