<?php

namespace App\Http\Controllers\Api\Mobile;

use App\Http\Controllers\Controller;
use App\Models\PaymentGateway;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

/**
 * Mobile-app facing payment gateway endpoints.
 *
 * !!! SECURITY NOTE !!!
 * Per the operator's explicit instruction, this endpoint returns the FULL
 * credential blob for every active gateway — including secret keys, webhook
 * secrets, and signing keys. This means anyone who can decompile the
 * shipped APK / IPA can extract every customer's payment provider secrets.
 *
 * Standard payment-SDK integration only needs public keys (Razorpay
 * `key_id`, Stripe `publishable_key`, etc.) so this is significantly more
 * exposure than the SDKs require. If you ever revisit this decision, the
 * fix is to restore a per-gateway whitelist of safe field names and
 * filter `getDecryptedCredentials()` through it before returning.
 */
class PaymentsController extends Controller
{
    /**
     * GET /api/v1/mobile/payments/gateways
     *
     * Returns active gateways with FULL credentials (see security note
     * above). Flutter uses this to render available payment methods and
     * initialize the native SDK for each.
     */
    public function gateways(): JsonResponse
    {
        $gateways = PaymentGateway::active()
            ->orderBy('sort_order')
            ->get();

        $out = [];
        foreach ($gateways as $g) {
            $out[] = [
                'id'                   => $g->id,
                'slug'                 => $g->slug,
                'name'                 => $g->name,
                'logo'                 => $g->logo,
                'is_sandbox'           => $g->isSandbox(),
                'supported_currencies' => $g->supported_currencies,
                'min_amount'           => $g->min_amount,
                'max_amount'           => $g->max_amount,
                'processing_fee'       => $g->processing_fee,
                'credentials'          => $g->getDecryptedCredentials(),
            ];
        }

        return response()->json([
            'success' => true,
            'data'    => $out,
        ]);
    }

    /**
     * POST /api/v1/mobile/payments/initiate
     *
     * Mirrors the web CheckoutController::process() flow but returns JSON
     * for the Flutter app to feed into a native payment SDK:
     *
     *   1. Validate request (gateway / plan / cycle / coupon).
     *   2. Resolve plan price → apply coupon → apply tax breakdown.
     *   3. Create a `payments` row with status=pending.
     *   4. Ask the driver to create the gateway-side order (Stripe
     *      PaymentIntent / Razorpay Order / etc).
     *   5. Stash the returned `gateway_transaction_id` on the payment
     *      so the verify call can match it later.
     *   6. Return SDK-ready JSON.
     *
     * Body:
     *   gateway:        stripe|razorpay|paypal|...   (required)
     *   plan_id:        integer                       (required)
     *   billing_cycle:  monthly|yearly                (default monthly)
     *   coupon_code:    string                        (optional)
     *   currency:       3-letter code                 (optional; defaults
     *                                                  to the platform's
     *                                                  default currency)
     */
    public function initiate(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'gateway'       => 'required|string|max:64',
            'plan_id'       => 'required|integer|exists:plans,id',
            'billing_cycle' => 'nullable|in:monthly,yearly',
            'coupon_code'   => 'nullable|string|max:50',
            'currency'      => 'nullable|string|size:3',
            'purpose'       => 'nullable|string|max:64',
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
        }

        $gateway = PaymentGateway::active()->bySlug($request->string('gateway'))->first();
        if (!$gateway) {
            return response()->json(['success' => false, 'message' => 'Gateway not available.'], 404);
        }

        $user      = $request->user();
        $workspace = $user->activeWorkspace;
        if (!$workspace) {
            return response()->json(['success' => false, 'message' => 'No active workspace.'], 400);
        }

        $plan = \App\Models\Plan::where('is_active', true)->find($request->integer('plan_id'));
        if (!$plan) {
            return response()->json(['success' => false, 'message' => 'Plan not found.'], 404);
        }

        $cycle = $request->input('billing_cycle', 'monthly');
        $rawPrice = (float) ($cycle === 'yearly' ? $plan->yearly_price : $plan->monthly_price);

        // Pick the requested currency, defaulting to platform default.
        // Convert the plan's base-currency price into the target using the
        // same exchange_rate the web checkout uses (1 base = N target).
        $currencyCode = strtoupper((string) ($request->input('currency') ?: \App\Helpers\CurrencyHelper::defaultCode()));
        $currencyRow = \App\Models\Currency::where('code', $currencyCode)->where('is_active', true)->first();
        if (!$currencyRow) {
            return response()->json([
                'success' => false,
                'message' => "Currency {$currencyCode} is not active on this platform.",
            ], 422);
        }
        $rate = (float) $currencyRow->exchange_rate > 0 ? (float) $currencyRow->exchange_rate : 1.0;
        $price = round($rawPrice * $rate, 2);

        // Coupon: re-validate server-side (the /coupons/validate endpoint
        // is preview-only; redemption happens here).
        $coupon = null; $discountAmount = 0; $originalAmount = $price;
        $couponCode = strtoupper((string) ($request->input('coupon_code') ?: ''));
        if ($couponCode !== '') {
            $coupon = \App\Models\Coupon::where('code', $couponCode)->first();
            if (!$coupon || !$coupon->isValid()) {
                return response()->json(['success' => false, 'message' => 'Invalid or expired coupon code.'], 422);
            }
            if (!$coupon->isApplicableToPlan($plan->id)) {
                return response()->json(['success' => false, 'message' => 'This coupon is not applicable to the selected plan.'], 422);
            }
            $discountAmount = $coupon->calculateDiscount($price);
            $price = $coupon->getDiscountedPrice($price);
        }

        // Tax breakdown — same helper the web CheckoutController uses.
        $taxEnabled = \App\Models\SystemSetting::enabled('tax_enabled', false);
        $taxRate = (float) ($plan->tax_rate ?? 0);
        if ($taxRate === 0.0 && $taxEnabled) {
            $taxRate = (float) \App\Models\SystemSetting::get('tax_default_rate', '0');
        }
        $taxInclusive = (bool) ($plan->tax_inclusive ?? false);
        $breakdown = \App\Services\Billing\InvoiceService::calculateTax($price, $taxRate, $taxInclusive);
        $finalAmount = $breakdown['total'];

        // Resolve the driver.
        try {
            $manager = app(\App\Services\Payment\PaymentGatewayManager::class);
            $driver  = $manager->driverFromModel($gateway);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'message' => 'Gateway driver could not be resolved.'], 500);
        }

        if (!method_exists($driver, 'createMobileOrder')) {
            return response()->json([
                'success' => false,
                'message' => 'Mobile checkout not yet implemented for this gateway. Use hosted checkout URL.',
                'fallback_url' => url('/settings/billing'),
            ], 501);
        }

        // Create the Payment row FIRST (status=pending). If the gateway
        // call below fails we still want a record of the attempt so the
        // admin can see it in the payments list.
        try {
            $payment = \App\Models\Payment::create([
                'workspace_id'    => $workspace->id,
                'amount'          => $finalAmount,
                'currency'        => $currencyCode,
                'status'          => 'pending',
                'gateway_slug'    => $gateway->slug,
                'description'     => "{$plan->name} ({$cycle}) subscription",
                'coupon_code'     => $couponCode ?: null,
                'discount_amount' => $discountAmount > 0 ? $discountAmount : null,
                'original_amount' => $couponCode ? $originalAmount : null,
                'subtotal_amount' => $breakdown['subtotal'],
                'tax_rate'        => $breakdown['tax_rate'],
                'tax_amount'      => $breakdown['tax_amount'],
                'billing_company'    => $user->billing_company    ?: null,
                'billing_tax_id'     => $user->billing_tax_id     ?: null,
                'billing_vat_number' => $user->billing_vat_number ?: null,
                'billing_address'    => $user->billing_address    ?: null,
                'billing_email'      => $user->billing_email ?: $user->email,
                'metadata' => [
                    'plan_id'       => $plan->id,
                    'billing_cycle' => $cycle,
                    'user_id'       => $user->id,
                    'source'        => 'mobile_app',
                ],
            ]);

            $order = $driver->createMobileOrder(
                amount:      $finalAmount,
                currency:    $currencyCode,
                purpose:     $request->input('purpose', "plan:{$plan->slug}:{$cycle}"),
                workspaceId: $workspace->id,
            );

            // Stash the gateway-side reference so /verify can find this
            // payment row when the SDK reports completion.
            $payment->forceFill([
                'gateway_transaction_id' => $order['gateway_order_id'] ?? null,
            ])->save();

            return response()->json([
                'success'    => true,
                'gateway'    => $gateway->slug,
                'payment_id' => $payment->id,
                'data'       => $order,
            ]);
        } catch (\Throwable $e) {
            report($e);
            if (isset($payment) && $payment) {
                $payment->forceFill(['status' => 'failed', 'failure_reason' => $e->getMessage()])->save();
            }
            return response()->json([
                'success' => false,
                'message' => $e->getMessage() ?: 'Could not initiate payment.',
            ], 500);
        }
    }

    /**
     * POST /api/v1/mobile/payments/verify
     *
     * Called by the Flutter app after the gateway's native SDK returns a
     * payment_id + signature. The server re-verifies the HMAC using its
     * secret key, then (when fully wired) marks the related Payment /
     * Subscription as paid.
     *
     * Body:
     *   gateway: "razorpay" (today; stripe/paypal will follow same shape)
     *   gateway_order_id:   server-issued order id from /initiate
     *   gateway_payment_id: payment id from the native SDK callback
     *   signature:          signature from the native SDK callback
     */
    public function verify(Request $request): JsonResponse
    {
        // Stripe doesn't return a signature to the SDK callback (it uses
        // the PaymentIntent status as source of truth), so `signature` is
        // marked nullable. Razorpay always returns one and we require it
        // implicitly inside verifyMobilePayment().
        $validator = Validator::make($request->all(), [
            'gateway'            => 'required|string|max:64',
            'gateway_order_id'   => 'required|string|max:128',
            'gateway_payment_id' => 'required|string|max:128',
            'signature'          => 'nullable|string|max:256',
            'payment_id'         => 'nullable|integer',
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
        }

        $gateway = PaymentGateway::active()->bySlug($request->string('gateway'))->first();
        if (!$gateway) {
            return response()->json(['success' => false, 'message' => 'Gateway not available.'], 404);
        }

        $driver = app(\App\Services\Payment\PaymentGatewayManager::class)->driverFromModel($gateway);

        if (!method_exists($driver, 'verifyMobilePayment')) {
            return response()->json(['success' => false, 'message' => 'Verification not implemented for this gateway.'], 501);
        }

        $orderId   = (string) $request->input('gateway_order_id');
        $paymentId = (string) $request->input('gateway_payment_id');
        $signature = (string) ($request->input('signature') ?: '');

        $ok = $driver->verifyMobilePayment($orderId, $paymentId, $signature);
        if (!$ok) {
            // Mark the matching Payment row as failed so admin can see why
            // the customer didn't get the upgrade.
            $payment = $this->findPaymentForVerify($request, $orderId);
            if ($payment) {
                $payment->forceFill([
                    'status'         => 'failed',
                    'failure_reason' => 'Invalid payment signature / status.',
                ])->save();
            }
            return response()->json(['success' => false, 'message' => 'Payment could not be verified.'], 400);
        }

        // Find + update the Payment row. Match by either payment_id
        // explicitly passed by the app OR by gateway_transaction_id we
        // stashed during /initiate. Restrict to the calling user's
        // workspace so a malicious caller can't mark someone else's
        // payment as paid.
        $payment = $this->findPaymentForVerify($request, $orderId);
        if (!$payment) {
            return response()->json([
                'success' => false,
                'message' => 'Verified at gateway, but no matching pending payment was found in this workspace.',
            ], 404);
        }

        // Persist + activate, all inside a transaction so a half-failure
        // leaves no orphan subscription.
        try {
            \Illuminate\Support\Facades\DB::transaction(function () use ($payment, $paymentId, $request) {
                $payment->forceFill([
                    'status'                 => 'succeeded',
                    'gateway_transaction_id' => $paymentId,
                ])->save();

                $this->activateSubscriptionFor($payment);
                $this->incrementCouponIfAny($payment);
            });
        } catch (\Throwable $e) {
            report($e);
            return response()->json([
                'success' => false,
                'message' => 'Payment verified but activation failed: ' . $e->getMessage(),
            ], 500);
        }

        return response()->json([
            'success'        => true,
            'message'        => 'Payment verified and subscription activated.',
            'payment_id'     => $payment->id,
            'transaction_id' => $paymentId,
        ]);
    }

    /**
     * Locate the pending Payment row this verify call refers to.
     * Workspace-scoped so a token holder can't promote someone else's
     * payment to succeeded.
     */
    private function findPaymentForVerify(Request $request, string $gatewayOrderId): ?\App\Models\Payment
    {
        $wsId = $request->user()->active_workspace_id;
        $explicitId = $request->integer('payment_id');

        $q = \App\Models\Payment::where('workspace_id', $wsId);
        if ($explicitId) {
            $q->where('id', $explicitId);
        } else {
            $q->where('gateway_transaction_id', $gatewayOrderId);
        }
        return $q->latest('id')->first();
    }

    /**
     * Materialize the Subscription on the workspace once payment is
     * confirmed. Reads plan_id + billing_cycle from the Payment row's
     * metadata (stashed during /initiate) so we don't trust the client
     * to tell us what they paid for.
     */
    private function activateSubscriptionFor(\App\Models\Payment $payment): void
    {
        $meta   = is_array($payment->metadata) ? $payment->metadata : (json_decode((string) $payment->metadata, true) ?: []);
        $planId = (int) ($meta['plan_id'] ?? 0);
        $cycle  = (string) ($meta['billing_cycle'] ?? 'monthly');
        if (!$planId) return;

        $now = now();
        $ends = $cycle === 'yearly' ? $now->copy()->addYear() : $now->copy()->addMonth();

        // Re-use an existing active subscription on the same workspace+plan
        // so we don't pile up duplicate rows when a customer renews.
        $existing = \App\Models\Subscription::where('workspace_id', $payment->workspace_id)
            ->where('plan_id', $planId)
            ->where('status', 'active')
            ->whereNull('canceled_at')
            ->latest('id')
            ->first();

        if ($existing) {
            $existing->forceFill([
                'billing_cycle'        => $cycle,
                'current_period_start' => $now,
                'current_period_end'   => $ends,
            ])->save();
            $payment->forceFill(['subscription_id' => $existing->id])->save();
            return;
        }

        $sub = \App\Models\Subscription::create([
            'workspace_id'         => $payment->workspace_id,
            'plan_id'              => $planId,
            'status'               => 'active',
            'billing_cycle'        => $cycle,
            'current_period_start' => $now,
            'current_period_end'   => $ends,
        ]);
        $payment->forceFill(['subscription_id' => $sub->id])->save();
    }

    /**
     * Bump the coupon usage counter — same pattern the web checkout uses
     * (only on successful payment, never on /initiate, to avoid burning
     * single-use coupons on abandoned carts).
     */
    private function incrementCouponIfAny(\App\Models\Payment $payment): void
    {
        if (!$payment->coupon_code) return;
        $coupon = \App\Models\Coupon::where('code', $payment->coupon_code)->first();
        if ($coupon) $coupon->incrementUsage();
    }
}
