<?php

namespace App\Controllers;

use App\Models\BookingModel;
use App\Models\Control\AdminActivityLogModel;
use App\Models\Control\InvoiceModel;
use App\Models\Control\SubscriptionModel;
use App\Models\Control\TenantRecordModel;
use App\Models\Control\TransactionModel;
use App\Models\FolioModel;
use App\Models\FolioPaymentModel;
use App\Models\GuestModel;
use App\Models\NotificationModel;
use App\Models\PaymentAttemptModel;
use CodeIgniter\HTTP\ResponseInterface;

/**
 * Receives Hubtel Checkout's payment-status callback. Called machine-to-
 * machine by Hubtel — no session, no subdomain, no CSRF token — so the
 * tenant is resolved explicitly from a URL segment (see routes.php and
 * the 'tenant'/'csrf' filter exceptions for webhooks/* in Filters.php)
 * rather than the usual subdomain/session chain.
 *
 * Always responds 200 once the payload has been parsed and matched (or
 * safely ignored), even on business-logic failure, so Hubtel doesn't
 * retry-storm an endpoint that already understood the request. Every
 * call is logged onto its payment_attempts row for support/debugging.
 */
class PaymentWebhookController extends BaseController
{
    public function hubtelCheckout(string $tenantSlug)
    {
        if (! $this->resolveTenant($tenantSlug)) {
            return $this->response->setStatusCode(404)->setJSON(['error' => 'Unknown tenant']);
        }

        // A malformed/empty body is a caller error, not a server error —
        // getJSON() throws on invalid JSON, so this must not be allowed to
        // bubble into an uncaught 500 for a webhook that machines call
        // unattended. Respond 400 and move on rather than crash.
        try {
            $payload = $this->request->getJSON(true) ?? [];
        } catch (\Throwable $e) {
            log_message('warning', 'Hubtel webhook: invalid JSON body — ' . $e->getMessage());

            return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid JSON body']);
        }

        log_message('info', 'Hubtel webhook received: ' . json_encode($payload));

        $data            = $payload['Data'] ?? [];
        $clientReference = $data['ClientReference'] ?? null;
        $status          = $data['Status'] ?? null;

        if ($clientReference === null) {
            return $this->response->setStatusCode(200)->setJSON(['received' => true, 'note' => 'no ClientReference']);
        }

        $attempts = new PaymentAttemptModel();
        $attempt  = $attempts->findByClientReference($clientReference);

        if ($attempt === null) {
            log_message('warning', "Hubtel webhook: unrecognized ClientReference {$clientReference}");

            return $this->response->setStatusCode(200)->setJSON(['received' => true, 'note' => 'unrecognized reference']);
        }

        // Idempotency: Hubtel may call back more than once for the same
        // checkout. Once we've already recorded a final status, do nothing.
        if ($attempt->status !== 'pending') {
            return $this->response->setStatusCode(200)->setJSON(['received' => true, 'note' => 'already processed']);
        }

        $isSuccess = strtolower((string) $status) === 'success';

        $attempts->update($attempt->id, [
            'status'                => $isSuccess ? 'success' : 'failed',
            'provider_payment_type' => $data['PaymentDetails']['PaymentType'] ?? null,
            'raw_response'          => json_encode($payload),
        ]);

        if ($isSuccess) {
            if ($attempt->purpose === 'folio_payment') {
                $this->completeFolioPayment($attempt, $data);
            } elseif ($attempt->purpose === 'booking_deposit') {
                $this->completeBookingDeposit($attempt, $data);
            }
        }

        return $this->response->setStatusCode(200)->setJSON(['received' => true]);
    }

    /**
     * Same Hubtel Checkout callback shape as hubtelCheckout() above, but
     * for the PLATFORM's own Hubtel account (subscription payments FROM
     * hotels — see Admin\InvoiceController::requestHubtelPayment()) rather
     * than any tenant's. No tenant/slug in the URL at all — this is
     * control-DB-only and never touches service('tenantDb'), so it must
     * not call anything from App\Controllers\BaseController that assumes
     * a tenant is resolved (same reasoning as Admin\BaseAdminController).
     */
    public function platformHubtelCheckout()
    {
        try {
            $payload = $this->request->getJSON(true) ?? [];
        } catch (\Throwable $e) {
            log_message('warning', 'Platform Hubtel webhook: invalid JSON body — ' . $e->getMessage());

            return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid JSON body']);
        }

        log_message('info', 'Platform Hubtel webhook received: ' . json_encode($payload));

        $data            = $payload['Data'] ?? [];
        $clientReference = $data['ClientReference'] ?? null;
        $status          = $data['Status'] ?? null;

        if ($clientReference === null) {
            return $this->response->setStatusCode(200)->setJSON(['received' => true, 'note' => 'no ClientReference']);
        }

        $transactions = new TransactionModel();
        $transaction  = $transactions->findByClientReference($clientReference);

        if ($transaction === null) {
            log_message('warning', "Platform Hubtel webhook: unrecognized ClientReference {$clientReference}");

            return $this->response->setStatusCode(200)->setJSON(['received' => true, 'note' => 'unrecognized reference']);
        }

        // Idempotency — same reasoning as hubtelCheckout() above.
        if ($transaction->status !== 'pending') {
            return $this->response->setStatusCode(200)->setJSON(['received' => true, 'note' => 'already processed']);
        }

        $isSuccess   = strtolower((string) $status) === 'success';
        $paymentType = $data['PaymentDetails']['PaymentType'] ?? '';
        $method      = str_contains(strtolower($paymentType), 'card') ? 'hubtel_card' : 'hubtel_momo';

        $transactions->update($transaction->id, [
            'status'       => $isSuccess ? 'success' : 'failed',
            'method'       => $method,
            'reference'    => $data['SalesInvoiceId'] ?? $data['CheckoutId'] ?? $clientReference,
            'raw_response' => json_encode($payload),
        ]);

        if ($isSuccess && $transaction->invoice_id !== null) {
            $invoices = new InvoiceModel();
            $invoice  = $invoices->find($transaction->invoice_id);

            if ($invoice !== null && $invoice->status !== 'paid') {
                $invoices->markPaid($transaction->invoice_id);

                // A subscription that was trial/past_due and just had its
                // invoice paid is active again — same nudge a human admin
                // gives via Admin\SubscriptionController::updateStatus()
                // when marking an invoice paid manually.
                if ($invoice->subscription_id !== null) {
                    $subscriptions = new SubscriptionModel();
                    $subscription  = $subscriptions->find($invoice->subscription_id);

                    if ($subscription !== null && in_array($subscription->status, ['trial', 'past_due'], true)) {
                        $subscriptions->update($subscription->id, ['status' => 'active']);
                    }
                }
            }
        }

        (new AdminActivityLogModel())->log(null, 'invoices', 'Platform Hubtel webhook: transaction ' . ($isSuccess ? 'succeeded' : 'failed') . " for reference {$clientReference}.");

        return $this->response->setStatusCode(200)->setJSON(['received' => true]);
    }

    private function resolveTenant(string $slug): bool
    {
        $tenant = (new TenantRecordModel())->findActiveBySlug($slug);

        if ($tenant === null) {
            return false;
        }

        service('tenantContext')->setTenant($tenant);

        return true;
    }

    private function completeFolioPayment(object $attempt, array $data): void
    {
        if ($attempt->folio_id === null) {
            return;
        }

        $paymentType = $data['PaymentDetails']['PaymentType'] ?? '';
        $method      = str_contains(strtolower($paymentType), 'card') ? 'hubtel_card' : 'hubtel_momo';

        (new FolioPaymentModel())->insert([
            'folio_id'    => $attempt->folio_id,
            'amount'      => $attempt->amount,
            'method'      => $method,
            'reference'   => $data['SalesInvoiceId'] ?? $data['CheckoutId'] ?? $attempt->client_reference,
            'recorded_by' => null,
            'paid_at'     => date('Y-m-d H:i:s'),
        ]);

        (new NotificationModel())->notify(
            'Hubtel payment of ' . format_money($attempt->amount) . " confirmed on folio #{$attempt->folio_id}.",
            'folios/' . $attempt->folio_id,
        );
    }

    private function completeBookingDeposit(object $attempt, array $data): void
    {
        if ($attempt->booking_id === null) {
            return;
        }

        $bookings = new BookingModel();
        $booking  = $bookings->find($attempt->booking_id);

        if ($booking === null || $booking->status !== 'pending_deposit') {
            return; // already handled, or booking was cancelled in the meantime
        }

        $bookings->update($attempt->booking_id, ['status' => 'confirmed']);

        (new NotificationModel())->notify(
            "Deposit confirmed for booking #{$attempt->booking_id} — now Confirmed.",
            'bookings/' . $attempt->booking_id,
        );

        $guest = (new GuestModel())->find($booking->guest_id);

        if ($guest !== null) {
            $nights = (strtotime($booking->check_out_date) - strtotime($booking->check_in_date)) / 86400;

            service('messagingService')->send(
                'booking_confirmation',
                $guest->id,
                $guest->phone,
                "Hi {$guest->full_name}, your deposit has been received and your booking is confirmed for " .
                    "{$booking->check_in_date} to {$booking->check_out_date} ({$nights} night(s)). We look forward to hosting you!",
                [$guest->full_name, $booking->check_in_date, $booking->check_out_date],
            );
        }
    }
}
