<?php
/**
 * Slice 8R-PricingDefaults · org-wide defaults for new pricing versions.
 *
 *  1) Schema present + seed row (id=1)
 *  2) PricingDefaults::get returns the seed row
 *  3) PricingDefaults::save validates ranges (markup 0-500, VAT 0-500, prelims ≥ 0, currency 1-6 chars)
 *  4) PricingDefaults::save round-trips + stamps edited_by / edited_on
 *  5) TenderPricing::createVersion (fresh draft, no clone) picks up defaults
 *  6) TenderPricing::createVersion (cloned) uses seed values not defaults (seed wins)
 *  7) Changing defaults after a draft exists does NOT touch that draft
 *  8) Controller endpoints + view + sidebar wiring present
 *  9) Perms granted for CRM, FM, Executive, Administrator
 * 10) Not granted for PC/PM/Procurement/Accountant
 */
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 (['PricingDefaults.php','TenderPricing.php','Tenders.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();
$pd = new PricingDefaults();
$tp = new TenderPricing();

/* ── 0. Snapshot the seed row for restore ──────────────────────────── */
h('0. Snapshot seed');
$snap = $pd->get();
p("current defaults: markup={$snap['default_markup_pct']} · cont={$snap['default_contingency_pct']} · vat={$snap['default_vat_pct']} · prelims={$snap['default_prelims_amount']} · currency={$snap['default_currency']}");

/* ── 1. Schema ─────────────────────────────────────────────────────── */
h('1. Schema');
$cols = $db->mySelect("SHOW COLUMNS FROM pricing_defaults", []) ?: [];
$cn = array_column($cols, 'Field');
foreach (['default_markup_pct','default_contingency_pct','default_vat_pct','default_prelims_amount','default_currency','notes','edited_by','edited_by_name','edited_on'] as $c) {
    ok(in_array($c, $cn, true), "column present: $c");
}
$row = $db->mySelect("SELECT COUNT(*) n FROM pricing_defaults WHERE c0d = 1", []);
ok((int)$row[0]['n'] === 1, 'seed row (id=1) exists');

/* ── 2. get returns fields ─────────────────────────────────────────── */
h('2. get() returns values');
$g = $pd->get();
ok(is_array($g),                                    'get returns array');
ok(isset($g['default_markup_pct']),                 'default_markup_pct returned');
ok(is_numeric($g['default_markup_pct']),            'markup is numeric');

/* ── 3. save() validation guards ───────────────────────────────────── */
h('3. save() range validation');
$r1 = $pd->save(['default_markup_pct' => -1, 'default_currency' => 'TZS'], 200, 'Verify');
ok(!$r1['ok'] && stripos($r1['msg'], 'markup') !== false, 'negative markup refused');
$r2 = $pd->save(['default_markup_pct' => 20, 'default_vat_pct' => 501, 'default_currency' => 'TZS'], 200, 'Verify');
ok(!$r2['ok'] && stripos($r2['msg'], 'VAT') !== false, 'VAT > 500 refused');
$r3 = $pd->save(['default_markup_pct' => 20, 'default_vat_pct' => 18, 'default_prelims_amount' => -100, 'default_currency' => 'TZS'], 200, 'Verify');
ok(!$r3['ok'] && stripos($r3['msg'], 'prelims') !== false || stripos($r3['msg'], 'negative') !== false, 'negative prelims refused');
$r4 = $pd->save(['default_markup_pct' => 20, 'default_vat_pct' => 18, 'default_currency' => ''], 200, 'Verify');
ok(!$r4['ok'] && stripos($r4['msg'], 'currency') !== false, 'empty currency refused');
$r5 = $pd->save(['default_markup_pct' => 20, 'default_vat_pct' => 18, 'default_currency' => 'TOOLONG'], 200, 'Verify');
ok(!$r5['ok'] && stripos($r5['msg'], 'currency') !== false, 'currency > 6 chars refused');

/* ── 4. save() round-trips + stamps audit ──────────────────────────── */
h('4. save() round-trip');
$rOK = $pd->save([
    'default_markup_pct'      => 27.5,
    'default_contingency_pct' => 3.25,
    'default_vat_pct'         => 18,
    'default_prelims_amount'  => 12500,
    'default_currency'        => 'usd',
    'notes'                   => '8R verify',
], 201, 'Verify User 8R');
ok($rOK['ok'], 'valid save accepted');
$g2 = $pd->get();
ok((float)$g2['default_markup_pct']      === 27.5,  'markup persisted');
ok((float)$g2['default_contingency_pct'] === 3.25,  'contingency persisted');
ok((float)$g2['default_vat_pct']         === 18.0,  'vat persisted');
ok((float)$g2['default_prelims_amount']  === 12500.0,'prelims persisted');
ok($g2['default_currency']               === 'USD', 'currency upper-cased');
ok((int)$g2['edited_by']                 === 201,   'edited_by stamped');
ok($g2['edited_by_name']                 === 'Verify User 8R', 'edited_by_name stamped');
ok(!empty($g2['edited_on']),                        'edited_on stamped');

/* ── 5. createVersion picks up defaults (fresh, no clone) ──────────── */
h('5. createVersion(fresh) inherits defaults');
$tenderRow = $db->mySelect("SELECT c0d FROM tender_tracker WHERE deleted=0 ORDER BY c0d DESC LIMIT 1", []);
if (empty($tenderRow)) boom('no tender available');
$tenderId = (int)$tenderRow[0]['c0d'];

// Retire any existing DRAFT so we can create a fresh one
$existing = $tp->activeDraft($tenderId);
$restoreDraftId = 0;
if ($existing) {
    $restoreDraftId = (int)$existing['c0d'];
    $db->insertUpdate("UPDATE tender_pricing_versions SET status='SUPERSEDED' WHERE c0d = :id", [':id' => $restoreDraftId]);
}
$vid = $tp->createVersion($tenderId, null, 202, 'Verify Creator');
$v = $tp->findVersion($vid);
p("fresh version $vid: markup={$v['default_markup_pct']} · vat={$v['vat_pct']} · cont={$v['contingency_pct']} · prelims={$v['prelims_amount']} · currency={$v['currency']}");
ok((float)$v['default_markup_pct'] === 27.5,  'inherits markup (27.5)');
ok((float)$v['vat_pct']            === 18.0,  'inherits VAT (18)');
ok((float)$v['contingency_pct']    === 3.25,  'inherits contingency (3.25)');
ok((float)$v['prelims_amount']     === 12500.0,'inherits prelims (12500)');
ok($v['currency']                  === 'USD', 'inherits currency (USD)');
ok(stripos((string)$v['notes'], 'Inherited org defaults') !== false, "notes says 'Inherited org defaults'");

/* ── 6. Cloned draft uses SEED not defaults ────────────────────────── */
h('6. createVersion(clone) uses seed values, not defaults');
// Move the fresh draft to SUBMITTED so we can create another (activeDraft blocks a second DRAFT)
$db->insertUpdate("UPDATE tender_pricing_versions SET status='SUBMITTED' WHERE c0d = :id", [':id' => $vid]);
// Change defaults so we can prove clone ignored them
$pd->save(['default_markup_pct' => 99, 'default_contingency_pct' => 0, 'default_vat_pct' => 5, 'default_prelims_amount' => 0, 'default_currency' => 'TZS'], 203, 'Verify');
$vidClone = $tp->createVersion($tenderId, $vid, 203, 'Verify Cloner');
$vc = $tp->findVersion($vidClone);
p("cloned version $vidClone: markup={$vc['default_markup_pct']} · vat={$vc['vat_pct']}");
ok((float)$vc['default_markup_pct'] === 27.5, 'cloned markup = seed markup (27.5) not new default (99)');
ok((float)$vc['vat_pct']            === 18.0, 'cloned VAT = seed VAT (18) not new default (5)');
ok(stripos((string)$vc['notes'], 'Cloned from v') !== false, "notes says 'Cloned from v...'");

/* ── 7. Changing defaults does NOT touch existing versions ─────────── */
h('7. Changing defaults does not touch existing versions');
$before = $tp->findVersion($vid);
$pd->save(['default_markup_pct' => 42, 'default_contingency_pct' => 0, 'default_vat_pct' => 20, 'default_prelims_amount' => 0, 'default_currency' => 'TZS'], 204, 'Verify');
$after = $tp->findVersion($vid);
ok((float)$before['default_markup_pct'] === (float)$after['default_markup_pct'], 'existing version markup unchanged');
ok((float)$before['vat_pct']            === (float)$after['vat_pct'],            'existing version VAT unchanged');

/* ── 8. Controller + view + sidebar surface ────────────────────────── */
h('8. Controller + view + sidebar surface');
$cBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'controllers' . DS . 'PricingDefaultsController.php');
ok(strpos($cBody, 'function indexAction')  !== false, 'PricingDefaultsController::indexAction defined');
ok(strpos($cBody, 'function saveAjAction') !== false, 'PricingDefaultsController::saveAjAction defined');
$vBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'views' . DS . 'PricingDefaults' . DS . 'index.php');
ok(strpos($vBody, 'PricingDefaults/saveAj')      !== false, 'view posts to /PricingDefaults/saveAj');
ok(strpos($vBody, 'default_markup_pct')          !== false, 'markup field present');
ok(strpos($vBody, 'default_currency')            !== false, 'currency field present');
$sidebars = ['administrator','crm','executive','finance_manager'];
foreach ($sidebars as $s) {
    $sBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'views' . DS . 'layouts' . DS . 'partials' . DS . '_sidebar_' . $s . '.php');
    ok(strpos($sBody, 'PricingDefaults') !== false, "sidebar '{$s}' has PricingDefaults link");
}
$tBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'views' . DS . 'Tenders' . DS . 'view.php');
ok(strpos($tBody, 'How items flow to pricing') !== false, 'items-to-pricing explainer present in Tenders/view');
ok(strpos($tBody, 'Defaults come from')        !== false, 'defaults-come-from chip present in Tenders/view');

/* ── 9. Perms surface ──────────────────────────────────────────────── */
h('9. Perms');
$granted = array_map(fn($r) => $r['user_group_name'] . '::' . $r['action_allowed'], $db->mySelect(
    "SELECT g.user_group_name, p.action_allowed FROM user_groups_perms p JOIN user_groups g ON g.c0d=p.user_group_id
      WHERE p.controller_allowed='PricingDefaults' ORDER BY g.user_group_name, p.action_allowed", []) ?: []);
foreach (['CRM','Finance Manager','Executive','Administrator'] as $gname) {
    ok(in_array("$gname::index",  $granted, true), "$gname has index");
    ok(in_array("$gname::saveAj", $granted, true), "$gname has saveAj");
}
foreach (['Projects Coordinator','Project Manager','Accountant','Procurement','Stores','Field Supervisor'] as $gname) {
    ok(!in_array("$gname::saveAj", $granted, true), "$gname does NOT have saveAj");
}

/* ── Z. Restore ────────────────────────────────────────────────────── */
h('Z. Restore');
$pd->save([
    'default_markup_pct'      => $snap['default_markup_pct'],
    'default_contingency_pct' => $snap['default_contingency_pct'],
    'default_vat_pct'         => $snap['default_vat_pct'],
    'default_prelims_amount'  => $snap['default_prelims_amount'],
    'default_currency'        => $snap['default_currency'],
    'notes'                   => $snap['notes'] ?? '',
], (int)($snap['edited_by'] ?? 0), (string)($snap['edited_by_name'] ?? ''));
$db->insertUpdate("UPDATE tender_pricing_versions SET deleted=1 WHERE c0d IN (:v1, :v2)", [':v1' => $vid, ':v2' => $vidClone]);
if ($restoreDraftId > 0) $db->insertUpdate("UPDATE tender_pricing_versions SET status='DRAFT' WHERE c0d = :id", [':id' => $restoreDraftId]);
p('defaults + tender state restored');

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