<?php
/**
 * Slice 8b-1 · Field Supervisor as a first-class project seat.
 *
 *   1) Drive a Services tender through Slice 5 pipeline to project-provisioned state.
 *   2) Confirm project_starter_tasks has an ASSIGN_FS row with owner_slot=FIELD_SUPERVISOR.
 *   3) Assign the FS user as owner, complete the task.
 *   4) Verify c_projects.fs_id/fs_name populated.
 *   5) Verify c_projects_role_changes has a FIELD_SUPERVISOR row with correct actor + reason.
 *   6) Verify the ProjectStarterController's viewAction now selects fs_id/fs_name (via a
 *      direct query rather than instantiating the view — same shape).
 *   7) ASSIGN_PC follow-up: same generic seat-hydration path works for PC too (regression).
 */
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';
$_SESSION = $_SESSION ?? [];
foreach (['ApprovalChain.php','Tenders.php','TenderPricing.php','ProjectStarter.php','Notifications.php'] as $m) require_once ROOT . DS . 'app' . DS . 'models' . DS . $m;

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

$db = new DB();
$ac = new ApprovalChain();
$tm = new Tenders();
$ps = new ProjectStarter();

$uidCrm = 100; $unmCrm = 'CRM Test User';       $ugCrm  = 'CRM';
$uidFm  = 3;   $unmFm  = 'Finance Manager Test'; $ugFm  = 'Finance Manager';
$uidExec= 2;   $unmExec= 'Executive Test';      $ugExec = 'Executive';
$accId  = 4;   $accNm  = 'Accountant Test';
$procId = 8;   $procNm = 'Procurement Test';
$pmId   = 5;   $pmNm   = 'Project Manager Test';
$fsRow  = $db->mySelect("SELECT c0d, full_name FROM users WHERE user_group_name='Field Supervisor' AND status='ACTIVE' AND deleted=0 ORDER BY c0d LIMIT 1", []);
$fsId   = (int)($fsRow[0]['c0d'] ?? 0);
$fsNm   = (string)($fsRow[0]['full_name'] ?? '');
$pcRow  = $db->mySelect("SELECT c0d, full_name FROM users WHERE user_group_name='Projects Coordinator' AND status='ACTIVE' AND deleted=0 ORDER BY c0d LIMIT 1", []);
$pcId   = (int)($pcRow[0]['c0d'] ?? 0);
$pcNm   = (string)($pcRow[0]['full_name'] ?? '');
if ($fsId <= 0) boom('No active Field Supervisor user in DB');

$stamp = date('YmdHis');

/* 1 — Drive Services pipeline through provisioning */
h('1. Drive Services pipeline → project provisioned');
$fb = $db->insertUpdate(
    "INSERT INTO tender_tracker
        (tender_no, tender_title, source_type, buyer_name, project_type,
         date_received, estimated_value, currency, tender_stage, tender_status,
         created_by, created_by_name, created_on)
     VALUES (:tn, :tt, 'PRIVATE_CLIENT', 'Vodacom TZ', 'services',
         CURDATE(), 40000000, 'TZS', 'PREP', 'OPEN', :u, :un, NOW())",
    [':tn' => 'T-8B1-' . $stamp, ':tt' => 'SLICE 8b-1 FS seat verify', ':u' => $uidCrm, ':un' => $unmCrm]
);
$tid = (int)($fb['last_id'] ?? 0);
foreach (['SUBMISSION','FOLLOW_UP','OPENING'] as $s) $tm->advanceStage($tid, $s, 'x', $uidCrm, $unmCrm);
$tm->recordResult($tid, 'WON', date('Y-m-d'), '', 40000000, '', '', $uidCrm, $unmCrm);
$tm->advanceStage($tid, 'AWARDED', 'a', $uidCrm, $unmCrm);
$tm->saveContractTerms($tid, ['contract_value' => 40000000, 'vat_rate' => 18, 'currency' => 'TZS', 'payment_terms' => 'Net 30', 'advance_pct' => 15, 'retention_pct' => 5, 'contract_duration_days' => 90], $uidFm, $ugFm, $unmFm);
$res = $ac->submit(['chain_key'=>'TENDER_CONTRACT_APPROVAL','entity_type'=>'TENDER_CONTRACT','entity_id'=>$tid,'ref_label'=>'x','return_url'=>PROOT.'Tenders/view/'.$tid,'user_id'=>$uidCrm,'user_name'=>$unmCrm,'user_group'=>$ugCrm]);
$aid = (int)$res['approval_id'];
$ac->approve(['approval_id'=>$aid,'user_id'=>$uidFm,'user_name'=>$unmFm,'user_group'=>$ugFm,'comment'=>'','assignments'=>['ACCOUNTANT'=>['user_id'=>$accId,'user_name'=>$accNm],'PROCUREMENT'=>['user_id'=>$procId,'user_name'=>$procNm]]]);
$ac->approve(['approval_id'=>$aid,'user_id'=>$uidExec,'user_name'=>$unmExec,'user_group'=>$ugExec,'comment'=>'','assignments'=>['PROJECT_MANAGER'=>['user_id'=>$pmId,'user_name'=>$pmNm]]]);
$aRow = $ac->approvalById($aid);
$stg = $ac->decodePayload((string)($aRow['step_payload_json'] ?? ''));
$tm->onContractApproved($tid, $uidExec, $unmExec, $stg['assignments'] ?? []);
$t = $tm->find($tid); $pid = (int)$t['linked_project_id'];
p("Project $pid provisioned");
ok($pid > 0, 'Project auto-created');
// Confirm fs_id is initialised to 0
$row = $db->mySelect("SELECT fs_id, fs_name FROM c_projects WHERE c0d = :p", [':p' => $pid])[0];
ok((int)$row['fs_id'] === 0 && $row['fs_name'] === '', 'fs_id + fs_name start empty');

/* 2 — Confirm ASSIGN_FS task shape */
h('2. ASSIGN_FS task seeded with owner_slot=FIELD_SUPERVISOR');
$tasks = $ps->tasksFor($pid);
$assignFs = null; $assignPc = null;
foreach ($tasks as $tt) {
    if ($tt['task_key'] === 'ASSIGN_FS') $assignFs = $tt;
    if ($tt['task_key'] === 'ASSIGN_PC') $assignPc = $tt;
}
if (!$assignFs) boom('ASSIGN_FS task not found in Services pack');
p("ASSIGN_FS · owner_slot=" . $assignFs['owner_slot'] . " · pre-assigned=" . ($assignFs['owner_user_id'] ?: 'none'));
ok($assignFs['owner_slot'] === 'FIELD_SUPERVISOR', "owner_slot is FIELD_SUPERVISOR (dropdown will filter to FSs)");
ok((int)$assignFs['owner_user_id'] === 0, 'Not pre-assigned (PM will pick manually)');

/* 3 — Assign the FS user, complete */
h('3. PM assigns FS user, completes ASSIGN_FS');
$ps->assignOwner((int)$assignFs['c0d'], $fsId, $fsNm, $pmId);
$ps->complete((int)$assignFs['c0d'], $pmId, $pmNm, 'Nominated FS per Services onboarding');
$fsTaskAfter = $ps->findTask((int)$assignFs['c0d']);
ok($fsTaskAfter['status'] === 'DONE', 'ASSIGN_FS marked DONE');

/* 4 — c_projects.fs_id populated */
h('4. c_projects.fs_id / fs_name populated');
$row = $db->mySelect("SELECT fs_id, fs_name FROM c_projects WHERE c0d = :p", [':p' => $pid])[0];
p("fs_id={$row['fs_id']} · fs_name={$row['fs_name']}");
ok((int)$row['fs_id'] === $fsId && $row['fs_name'] === $fsNm, 'FS seat hydrated with expected user');

/* 5 — role-changes audit row */
h('5. c_projects_role_changes has a FIELD_SUPERVISOR row');
$audit = $db->mySelect("SELECT role_slot, new_user_id, new_user_name, changed_by, changed_by_name, reason FROM c_projects_role_changes WHERE project_id = :p AND role_slot = 'FIELD_SUPERVISOR' ORDER BY c0d DESC LIMIT 1", [':p' => $pid]);
if (empty($audit)) boom('No FIELD_SUPERVISOR audit row');
$a = $audit[0];
p("audit: slot={$a['role_slot']} · new={$a['new_user_name']} (id {$a['new_user_id']}) · by={$a['changed_by_name']} · reason=\"{$a['reason']}\"");
ok((int)$a['new_user_id'] === $fsId, 'new_user_id matches FS');
ok((int)$a['changed_by'] === $pmId, 'changed_by is the PM who acted');
ok(strpos($a['reason'], 'ASSIGN_FS') !== false, 'Reason references ASSIGN_FS task');

/* 6 — Regression: ASSIGN_PC still works via the same generic seat-map */
h('6. Regression: ASSIGN_PC uses the same generic path');
ok($assignPc && $assignPc['owner_slot'] === 'PROJECTS_COORDINATOR', 'ASSIGN_PC owner_slot is PROJECTS_COORDINATOR');
if ($assignPc && $pcId > 0) {
    $ps->assignOwner((int)$assignPc['c0d'], $pcId, $pcNm, $pmId);
    $ps->complete((int)$assignPc['c0d'], $pmId, $pmNm, 'Nominated PC');
    $row2 = $db->mySelect("SELECT pc_id, pc_name FROM c_projects WHERE c0d = :p", [':p' => $pid])[0];
    ok((int)$row2['pc_id'] === $pcId, 'ASSIGN_PC still hydrates pc_id via generic path');
    $auditPc = $db->mySelect("SELECT COUNT(*) n FROM c_projects_role_changes WHERE project_id = :p AND role_slot = 'PROJECTS_COORDINATOR'", [':p' => $pid])[0];
    ok((int)$auditPc['n'] >= 1, 'PROJECTS_COORDINATOR audit row still written');
}

echo "\n=== END ===\n";
