<?php
/**
 * Slice 8-UX end-to-end verify — regrouped contract terms + Exec override + audit trail.
 *
 *   1) Fresh tender at AWARDED/WON.
 *   2) FM captures a first-cut set of terms → creates row (no audit rows expected for insert).
 *   3) FM edits one field → 1 audit row with post_approval=0.
 *   4) Submit chain; FM + Exec approve → status APPROVED (locked).
 *   5) FM tries to edit locked terms → BLOCKED with "only Exec/Admin".
 *   6) Exec tries to edit locked terms WITHOUT reason → BLOCKED with "reason required".
 *   7) Exec edits locked terms WITH reason (changes 3 fields) → 3 audit rows with post_approval=1.
 *   8) contractTermsAudit() returns rows newest-first; each field's old→new is visible.
 */
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'] 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();

$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';

$stamp = date('YmdHis');
h('1. Fresh tender @ AWARDED/WON');
$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', 'goods',
         CURDATE(), 100000000, 'TZS', 'PREP', 'OPEN', :u, :un, NOW())",
    [':tn' => 'T-E2E8UX-' . $stamp, ':tt' => 'SLICE 8-UX regroup + audit', ':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'), '', 100000000, '', '', $uidCrm, $unmCrm);
$tm->advanceStage($tid, 'AWARDED', 'a', $uidCrm, $unmCrm);
p("Tender c0d=$tid @ AWARDED/WON");

/* 2 — Insert first-cut terms (fresh row, no diff to log yet) */
h('2. FM saves first-cut terms (insert path — no audit rows expected)');
$firstCut = [
    'contract_no'          => 'VOD-2026-001',
    'contract_date'        => date('Y-m-d'),
    'contract_value'       => 100000000,
    'payment_basis'        => 'MILESTONE',
    'payment_terms'        => '30% advance · 60% on delivery · 10% retention',
    'advance_pct'          => 30,
    'apg_required'         => 1,
    'apg_bank'             => 'CRDB Bank',
    'apg_validity_months'  => 18,
    'performance_bond_value'=> 10000000,
    'pg_type'              => 'BANK',
    'pg_bank'              => 'NMB',
    'retention_pct'        => 10,
    'warranty_months'      => 12,
    'ld_pct_per_day'       => 0.5,
    'ld_cap_pct'           => 10,
    'vat_treatment'        => 'EXCLUSIVE',
    'tax_treatment'        => 'TAXABLE',
    'wht_pct'              => 5,
    'gl_code'              => '4200-CIV',
    'fx_matches_cost_base' => 1,
    'notes'                => 'Standard T&Cs',
];
$tm->saveContractTerms($tid, $firstCut, $uidFm, $ugFm, $unmFm, '');
$auditN = count($tm->contractTermsAudit($tid));
ok($auditN === 0, "No audit rows on insert (got $auditN)");

/* 3 — Small pre-approval edit → 1 audit row, post_approval=0 */
h('3. FM edits advance_pct 30 → 25 pre-approval → 1 audit row, post_approval=0');
$firstCut['advance_pct'] = 25;
$tm->saveContractTerms($tid, $firstCut, $uidFm, $ugFm, $unmFm, '');
$audit = $tm->contractTermsAudit($tid);
p("audit rows: " . count($audit));
ok(count($audit) === 1 && $audit[0]['field_name'] === 'advance_pct' && (int)$audit[0]['post_approval'] === 0,
   "audit[0] is advance_pct · old=" . ($audit[0]['old_value'] ?? '?') . " → new=" . ($audit[0]['new_value'] ?? '?') . " · post_approval=" . (int)$audit[0]['post_approval']);

/* 4 — Submit chain + approve */
h('4. Submit + FM approve + Exec approve → status APPROVED (locked)');
$res = $ac->submit(['chain_key'=>'TENDER_CONTRACT_APPROVAL','entity_type'=>'TENDER_CONTRACT','entity_id'=>$tid,'ref_label'=>'S8ux','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'=>'ok','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'=>'ok','assignments'=>['PROJECT_MANAGER'=>['user_id'=>$pmId,'user_name'=>$pmNm]]]);
$status = $tm->contractApprovalStatus($tid);
ok($status === 'APPROVED', "chain status = $status");

/* 5 — FM tries to edit locked terms → blocked */
h('5. FM edits locked terms → must be BLOCKED');
try {
    $tm->saveContractTerms($tid, $firstCut, $uidFm, $ugFm, $unmFm, 'trying');
    ok(false, 'FM was allowed to edit locked terms (should be blocked)');
} catch (\Throwable $e) {
    ok(strpos($e->getMessage(), 'only Exec/Admin') !== false, "FM blocked: " . $e->getMessage());
}

/* 6 — Exec tries to edit locked terms WITHOUT reason → blocked */
h('6. Exec edits locked terms WITHOUT reason → must be BLOCKED');
try {
    $tm->saveContractTerms($tid, $firstCut, $uidExec, $ugExec, $unmExec, '');
    ok(false, 'Exec was allowed to edit without a reason (should be blocked)');
} catch (\Throwable $e) {
    ok(strpos($e->getMessage(), 'reason is required') !== false, "Exec blocked without reason: " . $e->getMessage());
}

/* 7 — Exec edits locked terms WITH reason, 3 field changes */
h('7. Exec edits locked terms WITH reason — 3 field changes → 3 audit rows post_approval=1');
$firstCut['advance_pct']         = 20;                      // 25 → 20
$firstCut['warranty_months']     = 24;                      // 12 → 24
$firstCut['notes']               = 'Renegotiated: warranty +12m, advance down to 20%';
$tm->saveContractTerms($tid, $firstCut, $uidExec, $ugExec, $unmExec, 'Client renegotiated warranty coverage and advance %');
$audit = $tm->contractTermsAudit($tid);
p("Total audit rows now: " . count($audit));
$postRows = array_values(array_filter($audit, fn($r) => (int)$r['post_approval'] === 1));
p("post-approval rows: " . count($postRows));
foreach ($postRows as $r) p(sprintf("  · %-16s %s → %s  by %s (%s)  reason: %s",
    $r['field_name'], (string)($r['old_value'] ?? '—'), (string)($r['new_value'] ?? '—'),
    $r['changed_by_name'], $r['changed_by_group'], $r['reason']));
ok(count($postRows) === 3, '3 post-approval audit rows written');
$fields = array_column($postRows, 'field_name');
sort($fields);
ok($fields === ['advance_pct','notes','warranty_months'], 'Correct fields recorded: ' . implode(', ', $fields));
foreach ($postRows as $r) {
    if ($r['reason'] === '' || $r['changed_by_group'] !== $ugExec) {
        ok(false, "audit row {$r['field_name']} missing reason or wrong actor group");
        continue;
    }
}
ok(true, 'Every post-approval row carries reason + Executive group');

/* 8 — Newest-first ordering */
h('8. contractTermsAudit is newest-first');
$audit = $tm->contractTermsAudit($tid);
$ok = true;
for ($i = 1; $i < count($audit); $i++) {
    if ($audit[$i-1]['changed_on'] < $audit[$i]['changed_on']) { $ok = false; break; }
}
ok($ok, "Rows sorted newest→oldest by changed_on");

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