<?php

use Core\Controller;
use Core\Auth;
use Core\Pricing;
use Core\Flutterwave;

/**
 * SubscriptionController — the subscription payment flow, reached two ways:
 *
 *  1. Voluntary: a still-authenticated org_user clicks "Upgrade now" on the
 *     trial banner any time during an active trial. They stay logged in
 *     throughout — Flutterwave redirects back to a session that never left.
 *  2. Forced: the trial has expired. Controller::enforceTrialStatus already
 *     logged them out and stashed which org/user in the session before
 *     redirecting here, so nothing here ever runs with a live authenticated
 *     session for an expired org.
 *
 * Payment is handled entirely by Flutterwave's hosted checkout — we never
 * render our own card form, only redirect to/verify against their API.
 */
class SubscriptionController extends Controller
{
    private UserAuthModel $userModel;

    public function __construct()
    {
        parent::__construct();
        $this->userModel = new UserAuthModel();
    }

    public function showPay(): void
    {
        $orgId = $this->resolveBillingOrgId();
        if (!$orgId) {
            $this->redirect(APP_URL . '/login');
        }

        $org = $this->dbQueryOne("SELECT * FROM organizations WHERE id = ?", [$orgId]);
        if (!$org) {
            Auth::clearBillingContext();
            $this->redirect(APP_URL . '/login');
        }

        if ($org['subscription_status'] === 'active') {
            Auth::clearBillingContext();
            $this->setFlash('success', 'Your subscription is already active.');
            $this->redirect(Auth::check() ? APP_URL . '/dashboard' : APP_URL . '/login');
        }

        $pricing = Pricing::resolveForOrg($orgId);

        $this->render('subscription/pay', [
            'pageTitle' => 'Complete Your Subscription',
            'org'       => $org,
            'pricing'   => $pricing,
            'flwReady'  => Flutterwave::isConfigured(),
        ], 'auth');
    }

    public function initiate(): void
    {
        if (!$this->validateCsrf()) {
            $this->setFlash('error', 'Invalid security token. Please try again.');
            $this->redirect(APP_URL . '/billing/pay');
        }

        $orgId = $this->resolveBillingOrgId();
        if (!$orgId) {
            $this->redirect(APP_URL . '/login');
        }

        $org = $this->dbQueryOne("SELECT * FROM organizations WHERE id = ?", [$orgId]);
        if (!$org) {
            $this->redirect(APP_URL . '/login');
        }

        if (!Flutterwave::isConfigured()) {
            $this->setFlash('error', 'Payments are temporarily unavailable. Please contact support.');
            $this->redirect(APP_URL . '/billing/pay');
        }

        $pricing = Pricing::resolveForOrg($orgId);
        if (!$pricing || $pricing['amount'] <= 0) {
            $this->setFlash('error', 'Subscription pricing has not been configured yet. Please contact support.');
            $this->redirect(APP_URL . '/billing/pay');
        }

        $billingUserId = $_SESSION['billing_user_id'] ?? (Auth::check() ? Auth::id() : null);
        $user = $billingUserId
            ? $this->dbQueryOne("SELECT * FROM users WHERE id = ?", [$billingUserId])
            : $this->dbQueryOne("SELECT * FROM users WHERE org_id = ? AND entity_type = 'org_user' ORDER BY id ASC LIMIT 1", [$orgId]);

        $txRef = 'HLMX-' . $orgId . '-' . strtoupper(bin2hex(random_bytes(6)));

        $this->dbExecute(
            "INSERT INTO subscription_payments (org_id, user_id, plan_id, tx_ref, amount, currency_code, status, created_at, updated_at)
             VALUES (?, ?, ?, ?, ?, ?, 'pending', NOW(), NOW())",
            [$orgId, $user['id'] ?? null, $pricing['plan']['id'], $txRef, $pricing['amount'], $pricing['currency']]
        );

        $result = Flutterwave::initializePayment([
            'tx_ref'         => $txRef,
            'amount'         => $pricing['amount'],
            'currency'       => $pricing['currency'],
            'redirect_url'   => APP_URL . '/billing/pay/callback',
            'customer'       => [
                'email' => $user['email'] ?? $org['email'],
                'name'  => trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')) ?: $org['name'],
            ],
            'customizations' => [
                'title'       => APP_NAME . ' Subscription',
                'description' => $pricing['plan']['name'] . ' plan — ' . $org['name'],
            ],
            'meta' => ['org_id' => $orgId, 'plan_id' => $pricing['plan']['id']],
        ]);

        if (($result['status'] ?? '') === 'success' && !empty($result['data']['link'])) {
            $this->redirect($result['data']['link']);
        }

        logAudit('subscription_payment_init_failed', 'billing', 'organization', $orgId, null, ['response' => $result]);
        $this->setFlash('error', 'Could not start payment: ' . ($result['message'] ?? 'unknown error') . '. Please try again.');
        $this->redirect(APP_URL . '/billing/pay');
    }

    /** Browser lands here after Flutterwave's hosted checkout. */
    public function callback(): void
    {
        $status        = $this->getGet('status', '');
        $txRef         = $this->getGet('tx_ref', '');
        $transactionId = $this->getGet('transaction_id', '');

        $payment = $txRef ? $this->dbQueryOne("SELECT * FROM subscription_payments WHERE tx_ref = ?", [$txRef]) : null;
        if (!$payment) {
            $this->setFlash('error', 'We could not find that payment. Please try again or contact support.');
            $this->redirect(APP_URL . '/billing/pay');
        }

        // The webhook may have already confirmed this before the browser got back here.
        if ($payment['status'] === 'successful') {
            $this->completeLogin((int)$payment['org_id']);
            return;
        }

        if ($status !== 'successful' || !$transactionId) {
            $this->setFlash('error', 'Payment was not completed. You can try again below.');
            $this->redirect(APP_URL . '/billing/pay');
        }

        $verified = Flutterwave::verifyTransaction($transactionId);
        $this->applyVerifiedPayment($payment, $verified);

        $refreshed = $this->dbQueryOne("SELECT * FROM subscription_payments WHERE id = ?", [$payment['id']]);
        if (($refreshed['status'] ?? '') === 'successful') {
            $this->completeLogin((int)$payment['org_id']);
            return;
        }

        $this->setFlash('error', 'We could not verify your payment. If you were charged, please contact support.');
        $this->redirect(APP_URL . '/billing/pay');
    }

    /** Server-to-server confirmation — the reliable path if the browser never makes it back to callback(). */
    public function webhook(): void
    {
        $signature = $_SERVER['HTTP_VERIF_HASH'] ?? '';
        $expected  = Flutterwave::webhookHash();

        if ($expected === '' || !hash_equals($expected, $signature)) {
            http_response_code(401);
            echo 'invalid signature';
            return;
        }

        $raw  = file_get_contents('php://input');
        $data = json_decode((string)$raw, true);

        $txRef         = $data['data']['tx_ref'] ?? null;
        $transactionId = $data['data']['id'] ?? null;
        if (!$txRef || !$transactionId) {
            http_response_code(400);
            echo 'bad payload';
            return;
        }

        $payment = $this->dbQueryOne("SELECT * FROM subscription_payments WHERE tx_ref = ?", [$txRef]);
        if (!$payment || $payment['status'] === 'successful') {
            http_response_code(200);
            echo 'ok';
            return;
        }

        $verified = Flutterwave::verifyTransaction((string)$transactionId);
        $this->applyVerifiedPayment($payment, $verified);

        http_response_code(200);
        echo 'ok';
    }

    // ─── Private helpers ─────────────────────────────────────────

    /**
     * Who are we billing? Either:
     *  - the session already has billing context (set by
     *    Controller::enforceTrialStatus after a forced, trial-expired
     *    logout), or
     *  - the visitor is still fully authenticated and wants to pay early —
     *    the "Upgrade now" banner link reaches here on day 1 of a 7-day
     *    trial just as validly as on the last day, so this must NOT require
     *    the trial to actually be expired.
     *
     * Contractor/supplier portal users are excluded even though they share
     * the client org's org_id — they're the org's guests, not the
     * subscriber, and must never be able to view/trigger its billing.
     */
    private function resolveBillingOrgId(): ?int
    {
        $orgId = $_SESSION['billing_org_id'] ?? null;
        if ($orgId) return (int)$orgId;

        if (Auth::check() && !Auth::isSuperAdmin() && !Auth::isContractorUser() && !Auth::isSupplierUser()) {
            return Auth::orgId();
        }
        return null;
    }

    private function applyVerifiedPayment(array $payment, array $verified): void
    {
        $v  = $verified['data'] ?? [];
        $ok = ($verified['status'] ?? '') === 'success'
            && ($v['status'] ?? '') === 'successful'
            && ($v['tx_ref'] ?? '') === $payment['tx_ref']
            && strtoupper((string)($v['currency'] ?? '')) === strtoupper($payment['currency_code'])
            && (float)($v['amount'] ?? 0) >= (float)$payment['amount'];

        if (!$ok) {
            $this->dbExecute(
                "UPDATE subscription_payments SET status = 'failed', gateway_response = ?, updated_at = NOW() WHERE id = ?",
                [json_encode($verified), $payment['id']]
            );
            return;
        }

        $this->dbExecute(
            "UPDATE subscription_payments SET status = 'successful', flw_transaction_id = ?, gateway_response = ?, paid_at = NOW(), updated_at = NOW() WHERE id = ?",
            [(string)($v['id'] ?? ''), json_encode($verified), $payment['id']]
        );

        $this->dbExecute(
            "UPDATE organizations SET subscription_status = 'active', plan_id = ? WHERE id = ?",
            [$payment['plan_id'], $payment['org_id']]
        );

        logAudit('subscription_payment_successful', 'billing', 'organization', (int)$payment['org_id'], null, [
            'amount' => $payment['amount'], 'currency' => $payment['currency_code'], 'tx_ref' => $payment['tx_ref'],
        ]);

        if (!empty($payment['user_id'])) {
            $user = $this->dbQueryOne("SELECT * FROM users WHERE id = ?", [$payment['user_id']]);
            if ($user) {
                $htmlBody = $this->renderPaymentEmail($user, $payment);
                sendEmail(
                    $user['email'],
                    trim($user['first_name'] . ' ' . $user['last_name']),
                    'Payment received — ' . APP_NAME,
                    $htmlBody
                );
            }
        }
    }

    private function completeLogin(int $orgId): void
    {
        // Voluntary path: the user never left — their session is still fully
        // authenticated for this exact org. Just refresh the cached session
        // data (Auth::login() again) so subscription_status flips from
        // 'trial' to 'active' immediately and the countdown banner disappears
        // without needing a fresh login.
        if (Auth::check() && Auth::orgId() === $orgId) {
            Auth::clearBillingContext();
            $refreshed = $this->userModel->findByIdWithOrg((int)Auth::id());
            if ($refreshed) {
                Auth::login($refreshed);
            }
            $this->setFlash('success', 'Payment received! Your subscription is now active.');
            $this->redirect(APP_URL . '/dashboard');
            return;
        }

        // Forced path: enforceTrialStatus() logged them out earlier and
        // stashed who to log back in as.
        $userId = $_SESSION['billing_user_id'] ?? null;
        Auth::clearBillingContext();

        if (!$userId) {
            $row = $this->dbQueryOne("SELECT id FROM users WHERE org_id = ? AND entity_type = 'org_user' ORDER BY id ASC LIMIT 1", [$orgId]);
            $userId = $row['id'] ?? null;
        }

        if ($userId) {
            $user = $this->userModel->findByIdWithOrg((int)$userId);
            if ($user) {
                $this->userModel->updateLastLogin((int)$userId);
                Auth::login($user);
                Auth::clearOtpPending();
                logAudit('login_success', 'auth', 'user', (int)$userId);
                $this->setFlash('success', 'Payment received! Welcome back.');
                $this->redirect(APP_URL . '/dashboard');
                return;
            }
        }

        $this->setFlash('success', 'Payment received! Please log in to continue.');
        $this->redirect(APP_URL . '/login');
    }

    private function renderPaymentEmail(array $user, array $payment): string
    {
        $userName = $user['first_name'];
        $amount   = formatCurrency((float)$payment['amount'], $payment['currency_code']);
        ob_start();
        include ROOT_PATH . '/modules/auth/views/email_templates/payment_received.php';
        return ob_get_clean();
    }
}
