<?php
/**
 * Slice 4 end-to-end verify:
 *   1) Create tender in PREP; advance to WON via recordResult
 *   2) Create pricing draft version + 2 sections + 4 lines
 *   3) Verify rollup (sum_cost / sum_price / margin_pct)
 *   4) Submit for pricing approval → walk PM → FM → Exec approval chain
 *   5) Verify version → APPROVED, prior drafts SUPERSEDED
 *   6) Apply to tender: submission_amount + bid_price_source
 *   7) Contract terms: FM saves + CRM submits contract chain
 *   8) Walk FM → Exec on the contract chain
 *   9) Verify: linked_project_id set, goods_client_pos row created,
 *      goods_client_po_items populated from the pricing lines
 *
 * Safe to re-run — creates a fresh tender with a distinct title each time.
 */
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';

// Minimal $_SESSION so any model that dips into it doesn't fatal.
$_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 done(int $ok, string $msg): void { echo ($ok ? "  ✓ " : "  ✗ ") . $msg . "\n"; }
function boom(string $why) { echo "\nFATAL: $why\n"; exit(1); }

$db = new DB();
$ac = new ApprovalChain();
$tm = new Tenders();
$pm = new TenderPricing();

$stamp   = date('YmdHis');
$title   = "SLICE 4 E2E · Pricing→Contract auto-project · $stamp";
$uidCrm  = 100; $unmCrm = 'CRM Test User';    $ugCrm  = 'CRM';
$uidPm   = 101; $unmPm  = 'PM Test User';     $ugPm   = 'Project Manager';
$uidFm   = 102; $unmFm  = 'FM Test User';     $ugFm   = 'Finance Manager';
$uidExec = 103; $unmExec= 'Exec Test User';   $ugExec = 'Executive';

/* 1 — Tender ---------------------------------------------------------- */
h('1. Create tender & advance to 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', 'ACME Ltd.', 'goods',
         CURDATE(), 25000000, 'TZS', 'PREP', 'OPEN',
         :u, :un, NOW())",
    [':tn' => 'T-E2E-' . $stamp, ':tt' => $title, ':u' => $uidCrm, ':un' => $unmCrm]
);
$tid = (int)($fb['last_id'] ?? 0);
if ($tid <= 0) boom('Tender insert did not return an id · ' . json_encode($fb));
p("Tender c0d=$tid");
$tm->advanceStage($tid, 'SUBMISSION', 'Advancing to submission', $uidCrm, $unmCrm);
$tm->advanceStage($tid, 'FOLLOW_UP',  'Advancing to follow-up',  $uidCrm, $unmCrm);
$tm->advanceStage($tid, 'OPENING',    'Advancing to opening',    $uidCrm, $unmCrm);
$tm->recordResult($tid, 'WON', date('Y-m-d'), '', 25000000, '', '', $uidCrm, $unmCrm);
$tm->advanceStage($tid, 'AWARDED', 'Awarded — moving to contract stage', $uidCrm, $unmCrm);
$t = $tm->find($tid);
done($t['tender_status'] === 'WON' && $t['tender_stage'] === 'AWARDED',
     "Stage=" . $t['tender_stage'] . " Status=" . $t['tender_status']);

/* 2 — Pricing draft + sections + lines -------------------------------- */
h('2. Create pricing draft with 2 sections & 4 lines');
$vid = $pm->createVersion($tid, null, $uidCrm, $unmCrm);
p("Version c0d=$vid");
$s1 = $pm->addSection($vid, $tid, 'Site prep',  'Access + fencing', $uidCrm);
$s2 = $pm->addSection($vid, $tid, 'Equipment',  'Generators + AC',  $uidCrm);
$pm->addLine($s1, ['item_ref'=>'SP-01','description'=>'Perimeter fencing','qty'=>200,'uom'=>'m',  'unit_cost'=>3500, 'markup_pct'=>25], $uidCrm);
$pm->addLine($s1, ['item_ref'=>'SP-02','description'=>'Site clearing',    'qty'=>1,  'uom'=>'lot','unit_cost'=>1500000,'markup_pct'=>30], $uidCrm);
$pm->addLine($s2, ['item_ref'=>'EQ-01','description'=>'30kVA generator',  'qty'=>2,  'uom'=>'ea', 'unit_cost'=>4200000,'markup_pct'=>20], $uidCrm);
$pm->addLine($s2, ['item_ref'=>'EQ-02','description'=>'2Ton split AC',    'qty'=>4,  'uom'=>'ea', 'unit_cost'=>1100000,'markup_pct'=>22], $uidCrm);
done(1, "Added sections $s1, $s2 with 4 lines");

/* 3 — Rollup ---------------------------------------------------------- */
h('3. Verify rollup');
$v = $pm->findVersion($vid);
$expCost  = round(200*3500 + 1*1500000 + 2*4200000 + 4*1100000, 2);
$expPrice = round((200*3500)*1.25 + (1*1500000)*1.30 + (2*4200000)*1.20 + (4*1100000)*1.22, 2);
p("sum_cost=" . number_format($v['sum_cost'],2)  . " expected=" . number_format($expCost,2));
p("sum_price=" . number_format($v['sum_price'],2) . " expected=" . number_format($expPrice,2));
p("margin_pct=" . $v['margin_pct'] . "%");
done(abs($v['sum_cost']  - $expCost)  < 0.01, 'sum_cost matches');
done(abs($v['sum_price'] - $expPrice) < 0.01, 'sum_price matches');

/* 4 — Submit + walk chain -------------------------------------------- */
h('4. Submit for pricing approval + walk PM → FM → Exec');
$res = $pm->submitForApproval($vid, $uidCrm, $unmCrm, $ugCrm);
if (!$res['ok']) boom('submitForApproval failed: ' . ($res['msg'] ?? '?'));
$aidP = (int)$res['approval_id'];
p("Approval c0d=$aidP created");
foreach ([
    ['role' => 'PM',  'uid' => $uidPm,   'un' => $unmPm,   'ug' => $ugPm],
    ['role' => 'FM',  'uid' => $uidFm,   'un' => $unmFm,   'ug' => $ugFm],
    ['role' => 'EXEC','uid' => $uidExec, 'un' => $unmExec, 'ug' => $ugExec],
] as $step) {
    $r = $ac->approve(['approval_id' => $aidP, 'user_id' => $step['uid'], 'user_name' => $step['un'], 'user_group' => $step['ug'], 'comment' => $step['role'] . ' OK']);
    if (!$r['ok']) boom($step['role'] . " approve failed: " . $r['msg']);
    p($step['role'] . " approved OK");
}
// FinChainsController::postDecisionHook is bypassed in CLI, so simulate its side-effect for TENDER_PRICING:
$a = $ac->approvalById($aidP);
$pm->onPricingDecision($vid, (string)$a['status']);
$v = $pm->findVersion($vid);
done($v['status'] === 'APPROVED', "Version status = " . $v['status']);

/* 5 — Apply as bid amount -------------------------------------------- */
h('5. Apply approved version → tender.submission_amount');
$pm->applyToTender($vid);
$t = $tm->find($tid);
done((float)$t['submission_amount'] > 0 && abs($t['submission_amount'] - $v['sum_price']) < 0.01,
     "submission_amount = " . number_format($t['submission_amount'],2) . " (source=" . $t['bid_price_source'] . ")");

/* 6 — Contract terms + chain ---------------------------------------- */
h('6. FM saves contract terms · CRM submits · FM + Exec approve');
$tm->saveContractTerms($tid, [
    'contract_value'          => $v['sum_price'],
    'vat_rate'                => 18,
    'currency'                => 'TZS',
    'payment_terms'           => 'Net 30',
    'advance_pct'             => 20,
    'retention_pct'           => 5,
    'contract_duration_days'  => 90,
    'expected_start_date'     => date('Y-m-d'),
    'penalty_clause'          => '0.5% per day capped 10%',
    'warranty_period_months'  => 12,
    'notes'                   => 'E2E test terms',
], $uidFm);
// Submit contract chain — different tenders may expose different signatures for this; do it directly.
$refLabel = 'Contract · ' . $t['tender_no'] . ' · TZS ' . number_format((float)$v['sum_price']);
$res = $ac->submit([
    'chain_key'   => 'TENDER_CONTRACT_APPROVAL',
    'entity_type' => 'TENDER_CONTRACT',
    'entity_id'   => $tid,
    'ref_label'   => $refLabel,
    'return_url'  => PROOT . 'Tenders/view/' . $tid . '#tab-contract',
    'user_id'     => $uidCrm, 'user_name' => $unmCrm, 'user_group' => $ugCrm,
]);
if (!$res['ok']) boom('Contract submit failed: ' . $res['msg']);
$aidC = (int)$res['approval_id'];
p("Contract approval c0d=$aidC");
foreach ([
    ['role' => 'FM',  'uid' => $uidFm,   'un' => $unmFm,   'ug' => $ugFm],
    ['role' => 'EXEC','uid' => $uidExec, 'un' => $unmExec, 'ug' => $ugExec],
] as $step) {
    $r = $ac->approve(['approval_id' => $aidC, 'user_id' => $step['uid'], 'user_name' => $step['un'], 'user_group' => $step['ug'], 'comment' => $step['role'] . ' OK']);
    if (!$r['ok']) boom($step['role'] . " approve (contract) failed: " . $r['msg']);
    p($step['role'] . " approved OK");
}
// Simulate FinChainsController::postDecisionHook for TENDER_CONTRACT
$a = $ac->approvalById($aidC);
if ($a['status'] === 'APPROVED') {
    $tm->onContractApproved($tid, $uidExec, $unmExec);
    $t = $tm->find($tid);
    if (!empty($t['linked_project_id'])) {
        $inserted = $pm->snapshotToProject($tid, (int)$t['linked_project_id'], $uidExec, $unmExec);
        p("snapshotToProject inserted $inserted line(s)");
    }
}

/* 7 — Verify auto-project + snapshot -------------------------------- */
h('7. Verify auto-project + baseline snapshot');
$t = $tm->find($tid);
done(!empty($t['linked_project_id']), "linked_project_id=" . (int)$t['linked_project_id']);
$pid = (int)$t['linked_project_id'];

$po = $db->mySelect(
    "SELECT c0d, client_po_no, project_id, linked_tender_id, status, subtotal_sell, subtotal_cog, total
       FROM goods_client_pos WHERE project_id = :p AND linked_tender_id = :t AND deleted = 0",
    [':p' => $pid, ':t' => $tid]
);
done(!empty($po), "goods_client_pos row present (" . count($po) . " row)");
if (!empty($po)) {
    $row = $po[0];
    p("PO#=" . $row['client_po_no'] . " subtotal_sell=" . number_format($row['subtotal_sell'],2) . " total=" . number_format($row['total'],2));
    $items = $db->mySelect("SELECT COUNT(*) n, SUM(line_total_sell) s FROM goods_client_po_items WHERE client_po_id = :p", [':p' => (int)$row['c0d']]);
    p("goods_client_po_items = " . $items[0]['n'] . " lines · sum(line_total_sell) = " . number_format($items[0]['s'] ?? 0, 2));
    done((int)$items[0]['n'] === 4 && abs(($items[0]['s'] ?? 0) - $v['sum_price']) < 0.01,
         '4 items copied · line totals match approved pricing sum');
}

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