<?php
/**
 * Slice 8Z-z-D + 8Z-z-E smoke test.
 * Runs against contract 12 and the first seeded site there.
 * Uses distinctive request_no + callout_no prefixes so pre-existing seeded data
 * is untouched.
 */
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 ([
    'ServiceContracts.php','SpareRequests.php','ApprovalChain.php','Callouts.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();
$sc = new ServiceContracts();
$sr = new SpareRequests();
$co = new Callouts();
$ac = new ApprovalChain();

$CID = 12;
$UID_PC   = 6;   // PC user (from seed)
$UID_PM   = 5;   // PM user
$UID_PROC = 21;  // Procurement — fetched below if not this

$pcRow = $db->mySelect("SELECT c0d, full_name FROM users WHERE c0d = 6", []);
$pmRow = $db->mySelect("SELECT c0d, full_name FROM users WHERE c0d = 5", []);
$procRow = $db->mySelect("SELECT u.c0d, u.full_name FROM users u JOIN user_groups g ON g.c0d = u.user_group_id WHERE g.user_group_name = 'Procurement' AND u.status = 'ACTIVE' AND u.deleted = 0 LIMIT 1", []);
if (empty($procRow)) { echo "✗ no Procurement user seeded — cannot test terminal step\n"; exit(1); }
$UID_PROC = (int)$procRow[0]['c0d'];
$PN_PC   = (string)$pcRow[0]['full_name'];
$PN_PM   = (string)$pmRow[0]['full_name'];
$PN_PROC = (string)$procRow[0]['full_name'];

$sites = $sc->sitesFor($CID);
if (count($sites) < 1) { echo "✗ need seed_vodacom_sites.php first\n"; exit(1); }
$siteId = (int)$sites[0]['c0d'];
$siteCode = (string)$sites[0]['site_code'];

/* Cleanup — only rows this test creates. Uses SMOKE prefix on reason field. */
$db->insertUpdate("DELETE FROM spare_request_items WHERE request_id IN (SELECT c0d FROM spare_requests WHERE reason LIKE 'SMOKE%')", []);
$db->insertUpdate("DELETE FROM spare_requests WHERE reason LIKE 'SMOKE%'", []);
$db->insertUpdate("DELETE FROM callouts WHERE description LIKE 'SMOKE%'", []);

/* ── 1. Raise a callout at this site ────────────────────────────── */
h('1. Callout raise via Callouts::saveIntake');
$catRow = $db->mySelect("SELECT c0d FROM callout_categories WHERE code = 'URGENT' LIMIT 1", []);
$catId = (int)$catRow[0]['c0d'];
$coId = $co->saveIntake([
    'contract_id' => $CID, 'category_id' => $catId, 'site_id' => $siteId,
    'reported_by_name' => 'Test PC · smoke',
    'description' => 'SMOKE · Genset failing to start after outage', 'is_emergency' => 0,
], $UID_PC, $PN_PC);
ok($coId > 0, "callout raised · id=$coId");

$listAtSite = $co->listForSite($siteId);
$mine = array_filter($listAtSite, fn($c) => (int)$c['c0d'] === $coId);
ok(count($mine) === 1, 'appears in Callouts::listForSite');

/* ── 2. Spare-request lifecycle from callout ────────────────────── */
h('2. Spare-request draft + items');
$saveR = $sr->save([
    'contract_id' => $CID, 'site_id' => $siteId, 'callout_id' => $coId,
    'urgency' => 'URGENT', 'who_pays' => 'MIXED',
    'reason' => 'SMOKE · Battery + AVR after callout ' . $coId,
], $UID_PC);
ok($saveR['ok'] && ($saveR['id'] ?? 0) > 0, 'draft header saved · id=' . ($saveR['id'] ?? 0));
$reqId = (int)$saveR['id'];
$reqNo = (string)$saveR['request_no'];
ok(preg_match('/^SR-\d{4}-\d{4}$/', $reqNo) === 1, "request_no format OK ($reqNo)");

// Add two items — one CLIENT-billable, one INTERNAL
$item1 = $sr->itemSave($reqId, [
    'part_name_free' => 'AVR module', 'qty' => 1, 'uom' => 'ea', 'unit_cost_estimate' => 250000,
    'who_pays_line' => 'CLIENT',
]);
$item2 = $sr->itemSave($reqId, [
    'part_name_free' => 'Battery 12V 100Ah', 'qty' => 2, 'uom' => 'ea', 'unit_cost_estimate' => 180000,
    'who_pays_line' => 'INTERNAL',
]);
ok($item1['ok'] && $item2['ok'], '2 items saved');

$items = $sr->itemsFor($reqId);
ok(count($items) === 2, 'itemsFor returns 2');
$clientLines = array_filter($items, fn($i) => $i['who_pays_line'] === 'CLIENT');
$intLines    = array_filter($items, fn($i) => $i['who_pays_line'] === 'INTERNAL');
ok(count($clientLines) === 1 && count($intLines) === 1, 'per-line who_pays_line honoured');

/* ── 3. Submit for approval → status flips to AWAITING_POC ──────── */
h('3. Submit → AWAITING_POC · SPARE_REQUEST_APPROVAL chain starts');
$subR = $sr->submitForApproval($reqId, $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
ok($subR['ok'] === true, 'submit returned ok');
ok(($subR['approval_id'] ?? 0) > 0, 'fin_approvals row created · id=' . ($subR['approval_id'] ?? 0));

$statusAfterSubmit = $db->mySelect("SELECT status FROM spare_requests WHERE c0d = :id", [':id' => $reqId])[0]['status'];
ok($statusAfterSubmit === 'AWAITING_POC', "spare_requests.status = AWAITING_POC (got $statusAfterSubmit)");

// Re-submit must fail — already in progress
$subR2 = $sr->submitForApproval($reqId, $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
ok($subR2['ok'] === false, 're-submit rejected (already in progress)');

/* ── 4. PC records POC confirmation → advances to AWAITING_PM ───── */
h('4. POC confirmation captured → chain advances');
$pocR = $sr->recordPocConfirmation($reqId, [
    'decision' => 'APPROVE', 'method' => 'WHATSAPP',
    'comment'  => 'John Mwanza confirmed via WhatsApp voice note.',
], $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
ok(!empty($pocR['ok']) && $pocR['ok'] === true, 'POC APPROVE recorded');

$statusAfterPoc = $db->mySelect("SELECT status, poc_confirmation_method, poc_approved_on FROM spare_requests WHERE c0d = :id", [':id' => $reqId])[0];
ok($statusAfterPoc['status'] === 'AWAITING_PM', 'status flipped to AWAITING_PM');
ok($statusAfterPoc['poc_confirmation_method'] === 'WHATSAPP', 'poc_confirmation_method stamped');
ok(!empty($statusAfterPoc['poc_approved_on']), 'poc_approved_on stamped');

$approval = $ac->approvalFor('SPARE_REQUEST', $reqId);
ok($approval['status'] === 'IN_PROGRESS', 'approval still IN_PROGRESS');
$stepRow = $db->mySelect("SELECT step_key FROM fin_chain_steps WHERE c0d = :id", [':id' => (int)$approval['current_step_id']])[0];
ok($stepRow['step_key'] === 'PM_APPROVAL', 'chain now on PM_APPROVAL');

/* ── 5. PM approves → advances to PROCUREMENT_TRIAGE ────────────── */
h('5. PM approval → PROCUREMENT_TRIAGE');
$ac->approve([
    'approval_id' => (int)$approval['c0d'], 'user_id' => $UID_PM,
    'user_name' => $PN_PM, 'user_group' => 'PROJECT MANAGER',
    'comment' => 'PM approves — mixed billing OK.',
]);
$approval = $ac->approvalFor('SPARE_REQUEST', $reqId);
$stepRow = $db->mySelect("SELECT step_key FROM fin_chain_steps WHERE c0d = :id", [':id' => (int)$approval['current_step_id']])[0];
ok($stepRow['step_key'] === 'PROCUREMENT_TRIAGE', 'chain now on PROCUREMENT_TRIAGE');
ok($approval['status'] === 'IN_PROGRESS', 'still IN_PROGRESS at step 3');

/* ── 6. Procurement triage terminal ─────────────────────────────── */
h('6. Procurement triage · PROCURE');
$procR = $sr->recordProcurementDecision($reqId, 'PROCURE', 'Awarded to CityBattery — 3 day lead time.', $UID_PROC, $PN_PROC, 'PROCUREMENT');
ok($procR['ok'] === true, 'procurement decision recorded');
$sReq = $sr->find($reqId);
ok($sReq['procurement_decision'] === 'PROCURE', 'procurement_decision = PROCURE');
ok($sReq['status'] === 'SOURCING', 'status = SOURCING (branch: PROCURE)');

$approval = $ac->approvalFor('SPARE_REQUEST', $reqId);
ok($approval['status'] === 'APPROVED', 'chain terminated APPROVED');

/* ── 7. Emergency-cash branch on a fresh request ────────────────── */
h('7. Emergency-cash triage branch');
$save2 = $sr->save([
    'contract_id' => $CID, 'site_id' => $siteId,
    'urgency' => 'EMERGENCY', 'who_pays' => 'INTERNAL',
    'reason' => 'SMOKE · Cash-out shortcut path',
], $UID_PC);
$req2 = (int)$save2['id'];
$sr->itemSave($req2, ['part_name_free' => 'Fuel injector · adhoc', 'qty' => 1, 'uom' => 'ea', 'unit_cost_estimate' => 90000]);
$sr->submitForApproval($req2, $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
$sr->recordPocConfirmation($req2, ['decision' => 'APPROVE', 'method' => 'PHONE'], $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
$ap2 = $ac->approvalFor('SPARE_REQUEST', $req2);
$ac->approve(['approval_id' => (int)$ap2['c0d'], 'user_id' => $UID_PM, 'user_name' => $PN_PM, 'user_group' => 'PROJECT MANAGER', 'comment' => 'PM ok emergency']);
$sr->recordProcurementDecision($req2, 'EMERGENCY_CASH', 'Cash disbursed via petty cash.', $UID_PROC, $PN_PROC, 'PROCUREMENT');
$sReq2 = $sr->find($req2);
ok($sReq2['procurement_decision'] === 'EMERGENCY_CASH', 'branch 2: procurement_decision = EMERGENCY_CASH');
ok($sReq2['status'] === 'SOURCING', 'branch 2: status = SOURCING');

/* ── 8. FROM_STOCK branch ───────────────────────────────────────── */
h('8. FROM_STOCK triage branch');
$save3 = $sr->save([
    'contract_id' => $CID, 'site_id' => $siteId,
    'urgency' => 'ROUTINE', 'who_pays' => 'INTERNAL',
    'reason' => 'SMOKE · Stock-pick path',
], $UID_PC);
$req3 = (int)$save3['id'];
$sr->itemSave($req3, ['part_name_free' => 'Oil filter', 'qty' => 4, 'uom' => 'ea', 'unit_cost_estimate' => 15000]);
$sr->submitForApproval($req3, $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
$sr->recordPocConfirmation($req3, ['decision' => 'APPROVE', 'method' => 'EMAIL'], $UID_PC, $PN_PC, 'PROJECTS COORDINATOR');
$ap3 = $ac->approvalFor('SPARE_REQUEST', $req3);
$ac->approve(['approval_id' => (int)$ap3['c0d'], 'user_id' => $UID_PM, 'user_name' => $PN_PM, 'user_group' => 'PROJECT MANAGER', 'comment' => 'PM ok']);
$sr->recordProcurementDecision($req3, 'FROM_STOCK', 'Picked from Kariakoo store.', $UID_PROC, $PN_PROC, 'PROCUREMENT');
$sReq3 = $sr->find($req3);
ok($sReq3['status'] === 'STOCK_ISSUED', 'branch 3: status = STOCK_ISSUED');

/* ── 9. Site-listing view ───────────────────────────────────────── */
h('9. Site listing helpers');
$atSite = $sr->listForSite($siteId);
$smokeReqs = array_filter($atSite, fn($r) => strpos($r['reason'], 'SMOKE') === 0);
ok(count($smokeReqs) === 3, "listForSite returned this test's 3 requests");

/* Cleanup */
$db->insertUpdate("DELETE FROM spare_request_items WHERE request_id IN (SELECT c0d FROM spare_requests WHERE reason LIKE 'SMOKE%')", []);
$db->insertUpdate("DELETE FROM fin_approval_log WHERE approval_id IN (SELECT c0d FROM fin_approvals WHERE entity_type = 'SPARE_REQUEST' AND entity_id IN (SELECT c0d FROM spare_requests WHERE reason LIKE 'SMOKE%'))", []);
$db->insertUpdate("DELETE FROM fin_approvals WHERE entity_type = 'SPARE_REQUEST' AND entity_id IN (SELECT c0d FROM spare_requests WHERE reason LIKE 'SMOKE%')", []);
$db->insertUpdate("DELETE FROM spare_requests WHERE reason LIKE 'SMOKE%'", []);
$db->insertUpdate("DELETE FROM callouts WHERE description LIKE 'SMOKE%'", []);

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