<?php
/**
 * TenderExecKickoffBrief · Slice 8Z-s.
 *
 * The FM+Executive → PM handoff bundle. One row per tender, updated
 * during the TENDER_CONTRACT_APPROVAL chain:
 *   · FM_VERIFY step   → FM fills the financial gates + financial risks
 *   · EXEC_APPROVE step → Exec fills the kickoff essentials + guardrails
 *                         + operational gates + strategic risks + reporting cadence
 *
 * Ownership per field enforced in save() (mirrors Tenders::SEAT_RBAC pattern):
 * fields the caller's group doesn't own are silently skipped, so both roles
 * can hit the same endpoint without stepping on each other.
 *
 * PM reads the merged brief post-provisioning on Projects/view/{pid}.
 */
class TenderExecKickoffBrief extends Model
{
    /** Section → list of columns · used by save() RBAC + the view's per-section rendering. */
    public const SECTIONS = [
        // A · Exec — kickoff essentials
        'kickoff'       => ['priority','target_kickoff_date','instructions_to_pm'],
        // B · Exec — guardrails
        'guardrails'    => ['margin_floor_pct','change_order_threshold_pct'],
        // C-FM · FM — financial gates
        'fm_gates'      => ['gate_insurance_activated','gate_advance_requested','gate_tra_efiscal_setup','gate_wcf_nssf_verified'],
        // C-Exec · Exec — operational gates
        'exec_gates'    => ['gate_site_visit','gate_mobilization','gate_long_lead','gate_permits','gate_osha_safety_plan'],
        // D-FM · FM — financial risks
        'fm_risks'      => ['risk_client_payment','risk_fx_exposure','risk_wcf_nssf_compliance'],
        // D-Exec · Exec — operational / strategic risks
        'exec_risks'    => ['risk_tight_schedule','risk_site_access','risk_scope_ambiguity','risk_ppra_reporting'],
        // E · Exec — reporting cadence
        'reporting'     => ['reporting_cadence','escalation_contact_user_id','escalation_contact_name'],
    ];

    /** Which sections each role can edit. */
    private const RBAC = [
        'FINANCE MANAGER' => ['fm_gates','fm_risks'],
        'EXECUTIVE'       => ['kickoff','guardrails','exec_gates','exec_risks','reporting'],
        'ADMINISTRATOR'   => ['kickoff','guardrails','fm_gates','exec_gates','fm_risks','exec_risks','reporting'],
    ];

    /** Human labels for the checkbox fields · used by view + PM briefing pane. */
    public const LABELS = [
        // FM gates
        'gate_insurance_activated' => 'Insurance activated',
        'gate_advance_requested'   => 'Advance request raised',
        'gate_tra_efiscal_setup'   => 'TRA e-fiscal invoicing set up',
        'gate_wcf_nssf_verified'   => 'WCF / NSSF compliance verified',
        // Exec gates
        'gate_site_visit'          => 'Site visit / survey',
        'gate_mobilization'        => 'Mobilization plan',
        'gate_long_lead'           => 'Long-lead item orders',
        'gate_permits'             => 'Permits / wayleaves',
        'gate_osha_safety_plan'    => 'OSHA site safety plan',
        // FM risks
        'risk_client_payment'      => 'Client payment history',
        'risk_fx_exposure'         => 'FX exposure',
        'risk_wcf_nssf_compliance' => 'WCF / NSSF compliance risk',
        // Exec risks
        'risk_tight_schedule'      => 'Tight schedule',
        'risk_site_access'         => 'Difficult site access',
        'risk_scope_ambiguity'     => 'Scope ambiguity',
        'risk_ppra_reporting'      => 'PPRA reporting (public tender)',
    ];

    public const PRIORITIES = ['LOW','NORMAL','HIGH'];
    public const REPORTING_CADENCES = ['WEEKLY','BIWEEKLY','MONTHLY','ADHOC'];

    public function forTender(int $tenderId): array
    {
        $db = new DB();
        $r  = $db->mySelect("SELECT * FROM tender_exec_kickoff_brief WHERE tender_id = :t", [':t' => $tenderId]);
        return $r[0] ?? [];
    }

    /** Which sections the given user's group is allowed to edit. */
    public function editableSectionsFor(string $userGroup): array
    {
        return self::RBAC[strtoupper(trim($userGroup))] ?? [];
    }

    /**
     * Save fields the caller's role owns. Fields outside their RBAC are silently
     * dropped (won't fail the save — mirrors SEAT_RBAC pattern in Tenders). Upserts
     * the row; unlike TenderServiceSpec::save we always fetch existing first so we
     * can update fm_edited_* vs exec_edited_* stamps precisely.
     *
     * Returns ['ok' => bool, 'id' => int, 'sections_written' => [...]].
     */
    public function save(int $tenderId, array $d, int $userId, string $userName, string $userGroup): array
    {
        if ($tenderId <= 0) return ['ok' => false, 'msg' => 'tender_id required.'];
        $editable = $this->editableSectionsFor($userGroup);
        if (empty($editable)) return ['ok' => false, 'msg' => 'You are not permitted to edit the kickoff brief.'];

        $allowedFields = [];
        $written = [];
        foreach ($editable as $sec) {
            if (empty(self::SECTIONS[$sec])) continue;
            foreach (self::SECTIONS[$sec] as $col) {
                if (array_key_exists($col, $d)) { $allowedFields[$col] = $d[$col]; $written[$sec] = true; }
            }
        }
        if (empty($allowedFields)) return ['ok' => true, 'id' => 0, 'sections_written' => [], 'msg' => 'No editable fields in payload.'];

        $normalised = $this->normalise($allowedFields);
        $isFm       = in_array(strtoupper($userGroup), ['FINANCE MANAGER','ADMINISTRATOR'], true) && (isset($written['fm_gates']) || isset($written['fm_risks']));
        $isExec     = in_array(strtoupper($userGroup), ['EXECUTIVE','ADMINISTRATOR'], true) && (isset($written['kickoff']) || isset($written['guardrails']) || isset($written['exec_gates']) || isset($written['exec_risks']) || isset($written['reporting']));

        $db = new DB();
        $existing = $this->forTender($tenderId);

        if (empty($existing)) {
            // First insert — seed with the normalised fields + audit
            $cols  = array_keys($normalised);
            $binds = array_map(fn($c) => ':' . $c, $cols);
            $sql   = "INSERT INTO tender_exec_kickoff_brief (tender_id, created_by, created_by_name";
            if ($isFm)   $sql .= ", fm_edited_by, fm_edited_by_name, fm_edited_on";
            if ($isExec) $sql .= ", exec_edited_by, exec_edited_by_name, exec_edited_on";
            if (!empty($cols)) $sql .= ", " . implode(', ', $cols);
            $sql .= ") VALUES (:t, :cb, :cbn";
            if ($isFm)   $sql .= ", :fmb, :fmbn, NOW()";
            if ($isExec) $sql .= ", :xb, :xbn, NOW()";
            if (!empty($binds)) $sql .= ", " . implode(', ', $binds);
            $sql .= ")";

            $params = [':t' => $tenderId, ':cb' => $userId, ':cbn' => $userName];
            if ($isFm)   $params += [':fmb' => $userId, ':fmbn' => $userName];
            if ($isExec) $params += [':xb'  => $userId, ':xbn'  => $userName];
            foreach ($normalised as $k => $v) $params[':' . $k] = $v;

            $r = $db->insertUpdate($sql, $params);
            return ['ok' => true, 'id' => (int)($r['last_id'] ?? 0), 'sections_written' => array_keys($written)];
        }

        // UPDATE
        if (empty($normalised)) return ['ok' => true, 'id' => (int)$existing['c0d'], 'sections_written' => []];
        $sets = implode(', ', array_map(fn($c) => "$c = :$c", array_keys($normalised)));
        if ($isFm)   $sets .= ", fm_edited_by = :fmb, fm_edited_by_name = :fmbn, fm_edited_on = NOW()";
        if ($isExec) $sets .= ", exec_edited_by = :xb, exec_edited_by_name = :xbn, exec_edited_on = NOW()";
        $params = [':t' => $tenderId];
        if ($isFm)   $params += [':fmb' => $userId, ':fmbn' => $userName];
        if ($isExec) $params += [':xb'  => $userId, ':xbn'  => $userName];
        foreach ($normalised as $k => $v) $params[':' . $k] = $v;
        $db->insertUpdate("UPDATE tender_exec_kickoff_brief SET $sets WHERE tender_id = :t", $params);
        return ['ok' => true, 'id' => (int)$existing['c0d'], 'sections_written' => array_keys($written)];
    }

    /** Compute the budget-ceiling guardrail on the fly (contract_value × (1 − margin_floor_pct/100)). */
    public function budgetCeiling(int $tenderId): ?float
    {
        $brief = $this->forTender($tenderId);
        if (empty($brief) || $brief['margin_floor_pct'] === null || $brief['margin_floor_pct'] === '') return null;
        $db = new DB();
        $r = $db->mySelect("SELECT contract_value FROM tender_contract_terms WHERE tender_id = :t", [':t' => $tenderId]);
        $cv = (float)($r[0]['contract_value'] ?? 0);
        if ($cv <= 0) return null;
        $floor = (float)$brief['margin_floor_pct'];
        return round($cv * (1 - $floor / 100), 2);
    }

    private function normalise(array $d): array
    {
        $out = [];
        foreach ($d as $k => $v) {
            switch ($k) {
                case 'priority':
                    $s = strtoupper(trim((string)$v));
                    $out[$k] = in_array($s, self::PRIORITIES, true) ? $s : 'NORMAL';
                    break;
                case 'reporting_cadence':
                    $s = strtoupper(trim((string)$v));
                    $out[$k] = ($s === '' || !in_array($s, self::REPORTING_CADENCES, true)) ? null : $s;
                    break;
                case 'target_kickoff_date':
                    $s = trim((string)$v);
                    $out[$k] = ($s === '' ? null : $s);
                    break;
                case 'instructions_to_pm':
                case 'escalation_contact_name':
                    $out[$k] = trim((string)$v) ?: null;
                    break;
                case 'margin_floor_pct':
                case 'change_order_threshold_pct':
                    $out[$k] = ($v === null || $v === '') ? null : (float)$v;
                    break;
                case 'escalation_contact_user_id':
                    $out[$k] = ($v === null || $v === '') ? null : (int)$v;
                    break;
                default:
                    // gate_* + risk_* are booleans
                    if (strpos($k, 'gate_') === 0 || strpos($k, 'risk_') === 0) {
                        $out[$k] = !empty($v) ? 1 : 0;
                    } else {
                        $out[$k] = $v;
                    }
            }
        }
        return $out;
    }
}
