<?php
/**
 * Slice 8c-Report · Project rollup dashboard · end-to-end.
 *
 * Creates an isolated fresh phase with ONE CR, forces it ACTIVE, sources a paid LPO,
 * verifies:
 *   1) projectSummary numbers roll up correctly (contract_value from BOQ, committed
 *      from in-play CR totals, paid from allocations)
 *   2) phaseRollup returns a row per phase with correct budget + paid
 *   3) pillarRollup sums per pillar across the project
 *   4) SUPERSEDED / DRAFT CRs are excluded from committed
 *   5) Perms + view surface + wiring
 */
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 (['WorksCrs.php','WorksPhases.php','WorksCrFulfilment.php','WorksProjectReport.php','Lpos.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): void { echo "\nFATAL: $why\n"; exit(1); }

$db  = new DB();
$wc  = new WorksCrs();
$wp  = new WorksPhases();
$wf  = new WorksCrFulfilment();
$wpr = new WorksProjectReport();

$pcId = 100; $pcNm = 'Verify PC'; $pcG = 'Projects Coordinator';

/* ── 0. Anchor project ────────────────────────────────────────────────── */
h('0. Pick anchor works project');
$anchor = $db->mySelect(
    "SELECT c0d, project_no FROM c_projects
      WHERE project_type='works' AND deleted=0 ORDER BY c0d DESC LIMIT 1",
    []
);
if (empty($anchor)) boom('No works project. Run slice8cSpine_e2e_verify.php first.');
$prjId = (int)$anchor[0]['c0d'];
p("Project #$prjId · {$anchor[0]['project_no']}");

/* ── 0b. Purge prior report-verify artefacts ─────────────────────────── */
h('0b. Purge prior report-verify artefacts');
$oldPhases = $db->mySelect(
    "SELECT c0d FROM works_phases WHERE project_id = :p AND title LIKE 'ReportVerify %'",
    [':p' => $prjId]
) ?: [];
foreach ($oldPhases as $op) {
    $phId = (int)$op['c0d'];
    $crs = $db->mySelect("SELECT c0d FROM works_crs WHERE phase_id = :p", [':p' => $phId]) ?: [];
    foreach ($crs as $cr) {
        $cid = (int)$cr['c0d'];
        $lpos = $db->mySelect("SELECT c0d FROM lpos WHERE source_cr_id = :c", [':c' => $cid]) ?: [];
        foreach ($lpos as $lp) {
            $lid = (int)$lp['c0d'];
            $db->insertUpdate("DELETE FROM payment_allocations WHERE lpo_id = :l", [':l' => $lid]);
            $db->insertUpdate("DELETE FROM lpo_items           WHERE lpo_id = :l", [':l' => $lid]);
            $db->insertUpdate("DELETE FROM lpos                WHERE c0d   = :l", [':l' => $lid]);
        }
        $db->insertUpdate("DELETE FROM works_phase_items_issued WHERE cr_id = :c", [':c' => $cid]);
        $db->insertUpdate("DELETE FROM works_cr_closure_variance WHERE cr_id = :c", [':c' => $cid]);
        foreach (WorksCrs::PILLARS as $pil => $meta) {
            $db->insertUpdate("DELETE FROM `{$meta['table']}` WHERE cr_id = :c", [':c' => $cid]);
        }
        $db->insertUpdate("DELETE FROM works_cr_activities WHERE cr_id = :c", [':c' => $cid]);
        $db->insertUpdate("DELETE FROM works_crs WHERE c0d = :c", [':c' => $cid]);
    }
    $db->insertUpdate("DELETE FROM works_phase_activities WHERE phase_id = :p", [':p' => $phId]);
    $db->insertUpdate("DELETE FROM works_phase_boq_items  WHERE phase_id = :p", [':p' => $phId]);
    $db->insertUpdate("DELETE FROM works_phases WHERE c0d = :p", [':p' => $phId]);
}
// Orphan verify payments (unlinked from any LPO)
$db->insertUpdate("DELETE FROM payments WHERE payment_no LIKE 'PAY-RV-%'", []);
p("Purged " . count($oldPhases) . " prior report-verify phases");

/* ── 0c. Capture baselines BEFORE we add anything ──────────────────── */
h('0c. Snapshot baselines');
$baseSum = $wpr->projectSummary($prjId);
$basePhaseCount = count($wpr->phaseRollup($prjId));
p("Baseline committed=" . number_format($baseSum['committed_budget']) . " · paid=" . number_format($baseSum['paid']) . " · phases=$basePhaseCount");

/* ── 1. Add a new phase + CR with MATERIALS + MANPOWER ─────────────── */
h('1. Add phase + CR (MATERIALS 12M + MANPOWER 3M) → ACTIVE');
$phId = $wp->create($prjId, [
    'code' => 'RV-' . date('His'),
    'title' => 'ReportVerify ' . date('YmdHis'),
    'planned_start' => date('Y-m-d'),
    'planned_end'   => date('Y-m-d', strtotime('+30 days')),
], $pcId);
$crId = $wc->draft($prjId, $phId, ['title' => 'ReportVerify CR ' . date('His')], $pcId, $pcNm);
$wc->addLine($crId, 'MATERIALS', ['description' => 'Diesel', 'qty' => 4000, 'unit' => 'L', 'unit_cost' => 3000]); // 12M
$wc->addLine($crId, 'MANPOWER',  ['role' => 'Foreman', 'persons' => 1, 'days' => 30, 'rate' => 100000]);          // 3M
$wc->addActivity($crId, ['title' => 'Access track', 'unit' => 'm²', 'planned_qty' => 5000]);
$db->insertUpdate("UPDATE works_crs SET status = 'APPROVED' WHERE c0d = :id", [':id' => $crId]);
$wc->activate($crId);
$cr = $wc->findById($crId);
p("CR c0d=$crId status={$cr['status']} grand_total=" . number_format((float)$cr['grand_total']));
ok($cr['status'] === 'ACTIVE',                                     'CR is ACTIVE');
ok(abs((float)$cr['grand_total'] - 15000000) < 0.01,               'grand_total = 15M (12 + 3)');

/* ── 2. Source + pay an LPO on MATERIALS (partial: 10M of 12M) ────── */
h('2. Source + pay 10M LPO on MATERIALS (10 of 12)');
$lines = $wf->sourceableLines($crId)['MATERIALS'];
$line  = $lines[0];
$lpoRes = $wf->draftLpoFromCrLines(
    $crId, 'MATERIALS',
    ['vendor_id' => 1, 'vendor_name' => 'Furniture Direct Ltd', 'currency' => 'TZS'],
    [ ['line_id' => (int)$line['line_id'], 'qty' => 4000, 'unit_price' => 2500] ], // 10M
    $pcId, $pcNm
);
if (!$lpoRes['ok']) boom('draft LPO failed: ' . ($lpoRes['msg'] ?? '?'));
$lpoId = (int)$lpoRes['id'];
$db->insertUpdate("UPDATE lpos SET cash_out_status='PAID' WHERE c0d = :id", [':id' => $lpoId]);
$payR = $db->insertUpdate(
    "INSERT INTO payments (payment_no, payment_voucher_id, bank_account_id, amount, currency, payment_method, reference_no, payment_date, received_by, created_by, edited_by)
     VALUES (:pn, 0, 1, 10000000, 'TZS', 'BANK_TRANSFER', 'RV-REF', CURDATE(), 'Vendor', :u, :u)",
    [':pn' => 'PAY-RV-' . date('His'), ':u' => $pcId]
);
$payId = (int)$payR['last_id'];
$db->insertUpdate(
    "INSERT INTO payment_allocations (payment_id, lpo_id, amount, allocation_type) VALUES (:p, :l, 10000000, 'FULL')",
    [':p' => $payId, ':l' => $lpoId]
);
p("LPO c0d=$lpoId · PAID · allocation 10M");

/* ── 3. projectSummary numbers ─────────────────────────────────────── */
h('3. projectSummary numbers');
$sum = $wpr->projectSummary($prjId);
p(sprintf("contract=%s committed=%s paid=%s margin=%s (paid %.1f%%)",
    number_format($sum['contract_value']), number_format($sum['committed_budget']),
    number_format($sum['paid']), number_format($sum['margin_vs_boq']), $sum['paid_pct']));
ok($sum['contract_value']   > 0,                                                'contract_value > 0 (BOQ present)');
ok(abs($sum['committed_budget'] - ($baseSum['committed_budget'] + 15000000)) < 0.01, 'committed grew by 15M (our CR)');
ok(abs($sum['paid']             - ($baseSum['paid']             + 10000000)) < 0.01, 'paid grew by 10M (our payment)');
ok(abs($sum['remaining_budget'] - ($sum['committed_budget'] - $sum['paid']))    < 0.01, 'remaining = committed − paid');
ok(abs($sum['margin_vs_boq']    - ($sum['contract_value']    - $sum['committed_budget'])) < 0.01, 'margin = contract − committed');
ok($sum['phase_active']  >= 1,                                                  'phase_active count includes ours');
ok($sum['cr_active']     >= 1,                                                  'cr_active count includes ours');

/* ── 4. phaseRollup returns our phase ──────────────────────────────── */
h('4. phaseRollup contains our phase');
$phaseRows = $wpr->phaseRollup($prjId);
$mine = null;
foreach ($phaseRows as $r) if ((int)$r['phase_id'] === $phId) { $mine = $r; break; }
ok($mine !== null,                                                              'phase appears in rollup');
ok((int)$mine['cr_count'] === 1,                                                'phase.cr_count = 1');
ok(abs((float)$mine['budget'] - 15000000) < 0.01,                               'phase.budget = 15M');
ok(abs((float)$mine['paid']   - 10000000) < 0.01,                               'phase.paid   = 10M');
ok(abs((float)$mine['pillars']['MATERIALS'] - 12000000) < 0.01,                 'phase.pillars.MATERIALS = 12M');
ok(abs((float)$mine['pillars']['MANPOWER']  -  3000000) < 0.01,                 'phase.pillars.MANPOWER  = 3M');
ok(abs((float)$mine['paid_pct'] - round(10000000 / 15000000 * 100, 1)) < 0.1,   'phase.paid_pct correct');

/* ── 5. pillarRollup: MATERIALS + MANPOWER grew ───────────────────── */
h('5. pillarRollup at project level');
$pillars = $wpr->pillarRollup($prjId);
p(sprintf("MATERIALS budget=%s paid=%s variance=%s · MANPOWER budget=%s paid=%s",
    number_format($pillars['MATERIALS']['budget']), number_format($pillars['MATERIALS']['paid']),
    number_format($pillars['MATERIALS']['variance']),
    number_format($pillars['MANPOWER']['budget']),  number_format($pillars['MANPOWER']['paid'])));
ok($pillars['MATERIALS']['budget'] >= 12000000, 'MATERIALS budget >= 12M');
ok($pillars['MATERIALS']['paid']   >= 10000000, 'MATERIALS paid   >= 10M');
ok($pillars['MANPOWER']['budget']  >= 3000000,  'MANPOWER  budget >=  3M');
ok($pillars['MANPOWER']['paid']    === 0.0 || $pillars['MANPOWER']['paid'] === 0, 'MANPOWER paid = 0 (no LPO sourced)');
// Variance = paid - budget; under-spend is negative
ok($pillars['MATERIALS']['variance'] < 0, 'MATERIALS variance < 0 (under-spend)');

/* ── 6. Guard: DRAFT/SUPERSEDED CRs excluded from committed ─────────── */
h('6. DRAFT CR does not inflate committed');
$sumBefore = $wpr->projectSummary($prjId);
$dCr = $wc->draft($prjId, $phId, ['title' => 'ReportVerify DRAFT ' . date('His')], $pcId, $pcNm);
$wc->addLine($dCr, 'PRELIMINARIES', ['description' => 'Ghost line', 'amount' => 999999999]);
$sumAfter = $wpr->projectSummary($prjId);
ok(abs($sumAfter['committed_budget'] - $sumBefore['committed_budget']) < 0.01, 'DRAFT CR ignored in committed');
// Clean up the DRAFT so reruns are clean (also killed by 0b purge on next run)
$db->insertUpdate("DELETE FROM works_cr_preliminaries WHERE cr_id = :c", [':c' => $dCr]);
$db->insertUpdate("DELETE FROM works_crs WHERE c0d = :c", [':c' => $dCr]);

/* ── 7. Cross-project index ─────────────────────────────────────────── */
h('7. Cross-project index');
$idx = $wpr->projectsWithReport();
$found = false;
foreach ($idx as $ir) if ((int)$ir['c0d'] === $prjId) { $found = true; break; }
ok($found,                                       'anchor project appears in projectsWithReport()');
ok(count($idx) >= 1,                             'at least one project in the index');

/* ── 8. Perms + view surface ───────────────────────────────────────── */
h('8. Perms + view surface');
foreach (['Projects Coordinator','Project Manager','Field Supervisor','Procurement','Finance Manager','Accountant','Executive','Administrator'] as $role) {
    $rr = $db->mySelect("SELECT c0d FROM user_groups WHERE user_group_name = :n", [':n' => $role]);
    $gid = (int)($rr[0]['c0d'] ?? 0);
    foreach (['index','view'] as $act) {
        $r = $db->mySelect(
            "SELECT 1 FROM user_groups_perms WHERE user_group_id = :g AND controller_allowed = 'WorksProjectReport' AND action_allowed = :a LIMIT 1",
            [':g' => $gid, ':a' => $act]
        );
        ok(!empty($r), "$role · WorksProjectReport/$act");
    }
}
foreach (['view','index'] as $v) {
    ok(is_file(ROOT . DS . 'app' . DS . 'views' . DS . 'WorksProjectReport' . DS . "$v.php"), "view file exists: WorksProjectReport/$v.php");
}
// Wire: WorksBoq/view links to the report + sidebar entries
$boqBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'views' . DS . 'WorksBoq' . DS . 'view.php');
ok(strpos($boqBody, 'WorksProjectReport/view/') !== false, 'BOQ view links to project report');
foreach (['projects_coordinator','project_manager','field_supervisor','administrator','executive'] as $role) {
    $body = (string)file_get_contents(ROOT . DS . 'app' . DS . 'views' . DS . 'layouts' . DS . 'partials' . DS . "_sidebar_$role.php");
    ok(strpos($body, "PROOT; ?>WorksProjectReport") !== false, "_sidebar_$role has WorksProjectReport link");
}

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