<?php
/**
 * Slice 8c-Payments · Close the money loop back to the CR pillar dashboard.
 *
 * Assumes Slice 8c-Fulfil verify has produced (or can produce) a MATERIALS line + an LPO
 * stamped source_cr_id / source_cr_pillar. This verify walks the LPO through the payment
 * pipeline (prep PV → schedule → record payment) and asserts that:
 *  - payment_allocations row is created on payment
 *  - lpos.cash_out_status flips to PAID
 *  - WorksCrFulfilment::paymentsFor() surfaces the payment
 *  - WorksCrFulfilment::progressPerPillar() now reports paid > 0 and paid_pct > 0
 */
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','WorksCrFulfilment.php','Lpos.php','PaymentVouchers.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();
$wf  = new WorksCrFulfilment();
$pvs = new PaymentVouchers();

/* ── 1. Anchor: an ACTIVE Works CR with at least one materials line ────── */
h('1. Anchor ACTIVE Works CR');
$anchor = $db->mySelect(
    "SELECT c0d, project_id, phase_id FROM works_crs WHERE status='ACTIVE' AND deleted=0 ORDER BY c0d DESC LIMIT 1",
    []
);
if (empty($anchor)) boom('No ACTIVE Works CR. Run slice8cSpine_e2e_verify.php first.');
$crId    = (int)$anchor[0]['c0d'];
$phaseId = (int)$anchor[0]['phase_id'];
p("CR c0d=$crId · phase=$phaseId");

/* ── 1b. Purge prior verify artifacts so reruns work cleanly ─────────── */
h('1b. Purge prior verify artifacts on this CR');
$oldLpos = $db->mySelect("SELECT c0d FROM lpos WHERE source_cr_id = :c", [':c' => $crId]) ?: [];
foreach ($oldLpos as $ol) {
    $lid = (int)$ol['c0d'];
    // Payments + allocations
    $pvs2 = $db->mySelect("SELECT c0d FROM payment_vouchers WHERE lpo_id = :l", [':l' => $lid]) ?: [];
    foreach ($pvs2 as $pv) {
        $pays = $db->mySelect("SELECT c0d FROM payments WHERE payment_voucher_id = :pv", [':pv' => (int)$pv['c0d']]) ?: [];
        foreach ($pays as $pay) {
            $db->insertUpdate("DELETE FROM payment_allocations WHERE payment_id = :p", [':p' => (int)$pay['c0d']]);
            $db->insertUpdate("DELETE FROM payments             WHERE c0d = :p", [':p' => (int)$pay['c0d']]);
        }
        $db->insertUpdate("DELETE FROM payment_vouchers WHERE c0d = :pv", [':pv' => (int)$pv['c0d']]);
    }
    $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' => $crId]);

$oldLines = $db->mySelect(
    "SELECT c0d, total FROM works_cr_materials WHERE cr_id = :c AND description LIKE 'Diesel (verify %'",
    [':c' => $crId]
) ?: [];
$sumOld = 0;
foreach ($oldLines as $ol) $sumOld += (float)$ol['total'];
if ($sumOld > 0) {
    $db->insertUpdate(
        "UPDATE works_crs SET total_materials = total_materials - :s, grand_total = grand_total - :s WHERE c0d = :c",
        [':s' => $sumOld, ':c' => $crId]
    );
    $db->insertUpdate("DELETE FROM works_cr_materials WHERE cr_id = :c AND description LIKE 'Diesel (verify %'", [':c' => $crId]);
}
p("Purged " . count($oldLpos) . " lpos · " . count($oldLines) . " verify materials lines");

/* ── 2. Inject a fresh MATERIALS line + source an LPO ────────────────── */
h('2. Inject MATERIALS line + source an LPO');
$desc = 'Diesel (verify ' . date('His') . ')';
$db->insertUpdate(
    "INSERT INTO works_cr_materials (cr_id, line_no, description, qty, unit, unit_cost, total, created_on)
     VALUES (:c, 1, :d, 4000, 'L', 3000, 12000000, NOW())",
    [':c' => $crId, ':d' => $desc]
);
$db->insertUpdate(
    "UPDATE works_crs
        SET total_materials = total_materials + 12000000,
            grand_total     = grand_total     + 12000000
      WHERE c0d = :c",
    [':c' => $crId]
);
$lines = $wf->sourceableLines($crId)['MATERIALS'];
$line  = null;
foreach ($lines as $l) if ($l['description'] === $desc) { $line = $l; break; }
if (!$line) boom('injected line not returned by sourceableLines');

$pickQty  = 3000;
$pickPrice = 2950;
$res = $wf->draftLpoFromCrLines(
    $crId, 'MATERIALS',
    ['vendor_id' => 1, 'vendor_name' => 'Furniture Direct Ltd', 'currency' => 'TZS'],
    [ ['line_id' => (int)$line['line_id'], 'qty' => $pickQty, 'unit_price' => $pickPrice] ],
    100, 'Verify PC'
);
if (!$res['ok']) boom("draftLpoFromCrLines failed: " . ($res['msg'] ?? '?'));
$lpoId = (int)$res['id'];
p("LPO {$res['lpo_no']} (c0d=$lpoId) · amount=" . number_format(($pickQty * $pickPrice)));

/* ── 3. Force LPO through the approval chain (bypass in verify) ──────── */
h('3. Force LPO to APPROVED (bypass chain for verify)');
$db->insertUpdate(
    "UPDATE lpos SET cash_out_status = 'APPROVED', edited_on = NOW() WHERE c0d = :id",
    [':id' => $lpoId]
);
$state = $db->mySelect("SELECT cash_out_status FROM lpos WHERE c0d = :id", [':id' => $lpoId]);
ok((string)$state[0]['cash_out_status'] === 'APPROVED', 'LPO flipped to APPROVED');

/* ── 4. prepFromLpo → PV DRAFT ─────────────────────────────────────── */
h('4. Accountant preps PV from LPO');
$prep = $pvs->prepFromLpo($lpoId, 4, 'Verify Accountant', 'Pay diesel supplier · verify');
if (!$prep['ok']) boom('prepFromLpo failed: ' . ($prep['msg'] ?? '?'));
$pvId = (int)$prep['id'];
p("PV {$prep['pv_no']} (c0d=$pvId)");
$pvRow = $db->mySelect("SELECT * FROM payment_vouchers WHERE c0d = :id", [':id' => $pvId]);
ok((string)$pvRow[0]['status'] === 'DRAFT',  'PV status DRAFT');
ok((int)$pvRow[0]['lpo_id']    === $lpoId,   'PV linked to LPO');
ok(abs((float)$pvRow[0]['amount'] - ($pickQty * $pickPrice)) < 0.01, 'PV amount matches LPO');

/* ── 5. schedule → PV SCHEDULED ───────────────────────────────────── */
h('5. FM schedules PV');
$sch = $pvs->schedule($pvId, date('Y-m-d', strtotime('+2 days')), 1, 3, 'Verify FM', '');
if (!$sch['ok']) boom('schedule failed: ' . ($sch['msg'] ?? '?'));
$pvRow2 = $db->mySelect("SELECT status, scheduled_on, bank_account_id FROM payment_vouchers WHERE c0d = :id", [':id' => $pvId]);
ok((string)$pvRow2[0]['status']         === 'SCHEDULED', 'PV status SCHEDULED');
ok(!empty($pvRow2[0]['scheduled_on']),                   'PV scheduled_on stamped');
ok((int)$pvRow2[0]['bank_account_id']   === 1,           'PV bank_account_id stamped');

/* ── 6. recordPayment → payment + allocation + LPO PAID ─────────────── */
h('6. FM records payment');
$pay = $pvs->recordPayment($pvId, [
    'payment_method'  => 'BANK_TRANSFER',
    'reference_no'    => 'CRDB-EFT-' . date('Ymd-His'),
    'payment_date'    => date('Y-m-d'),
    'bank_account_id' => 1,
], 3);
if (!$pay['ok']) boom('recordPayment failed: ' . ($pay['msg'] ?? '?'));
$paymentId = (int)$pay['payment_id'];
p("Payment {$pay['payment_no']} (c0d=$paymentId)");

// PV → PAID
$pvRow3 = $db->mySelect("SELECT status, paid_on FROM payment_vouchers WHERE c0d = :id", [':id' => $pvId]);
ok((string)$pvRow3[0]['status'] === 'PAID', 'PV flipped to PAID');
ok(!empty($pvRow3[0]['paid_on']),           'PV paid_on stamped');

// LPO → PAID
$lRow = $db->mySelect("SELECT cash_out_status FROM lpos WHERE c0d = :id", [':id' => $lpoId]);
ok((string)$lRow[0]['cash_out_status'] === 'PAID', 'LPO flipped to PAID');

// allocation row
$alloc = $db->mySelect(
    "SELECT c0d, lpo_id, amount, allocation_type FROM payment_allocations WHERE payment_id = :p",
    [':p' => $paymentId]
);
ok(count($alloc) === 1,                                    'exactly 1 payment_allocations row');
ok((int)$alloc[0]['lpo_id'] === $lpoId,                    'allocation.lpo_id = LPO');
ok(abs((float)$alloc[0]['amount'] - ($pickQty * $pickPrice)) < 0.01, 'allocation amount matches');

/* ── 7. paymentsFor() surfaces the payment on the CR fulfil view ──── */
h('7. paymentsFor() surfaces the payment');
$pays = $wf->paymentsFor($crId);
$found = false;
foreach ($pays as $r) if ((int)$r['lpo_id'] === $lpoId && (int)$r['payment_id'] === $paymentId) { $found = $r; break; }
ok($found !== false,                                     'payment appears in paymentsFor()');
ok(is_array($found) && (string)$found['source_cr_pillar'] === 'MATERIALS', 'pillar surfaced from LPO stamp');
ok(is_array($found) && (string)$found['pv_no'] === $prep['pv_no'],         'PV number surfaced');

/* ── 8. progressPerPillar reports paid > 0 for MATERIALS ────────────── */
h('8. progressPerPillar returns paid');
$prg = $wf->progressPerPillar($crId);
$m   = $prg['MATERIALS'];
p(sprintf("MATERIALS budget=%s sourced=%s delivered=%s paid=%s (sourced %.1f%% · delivered %.1f%% · paid %.1f%%)",
    number_format($m['budget']), number_format($m['sourced']), number_format($m['delivered']),
    number_format($m['paid']), $m['sourced_pct'], $m['delivered_pct'], $m['paid_pct']));
ok(abs($m['paid'] - ($pickQty * $pickPrice)) < 0.01, 'paid = allocation amount');
ok($m['paid_pct'] > 0,                                'paid_pct > 0');
ok($m['sourced'] >= $m['paid'],                       'sourced >= paid (invariant)');
ok(abs($m['remaining'] - ($m['budget'] - $m['paid'])) < 0.01, 'remaining = budget - paid');

// Other pillars unaffected
$otherPaid = 0;
foreach ($prg as $pil => $ppr) if ($pil !== 'MATERIALS') $otherPaid += (float)$ppr['paid'];
ok($otherPaid === 0.0 || $otherPaid === 0, 'other pillars still show paid = 0');

/* ── 9. View surface: fulfil.php renders the paid bar + payments block ─ */
h('9. Fulfil view exposes paid measure');
$viewBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'views' . DS . 'WorksCrs' . DS . 'fulfil.php');
ok(strpos($viewBody, "paid_pct")  !== false, 'view references paid_pct');
ok(strpos($viewBody, "paymentsFor") === false && strpos($viewBody, "\$payments") !== false, 'view iterates $payments');
ok(strpos($viewBody, "Payments · ")  !== false, 'view has Payments card');

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