<?php

namespace App\Http\Controllers\Api\Mobile;

use App\Http\Controllers\Controller;
use App\Models\KbDocument;
use App\Models\Payment;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\UsageRecord;
use App\Models\Workspace;
use App\Services\Billing\InvoiceService;
use App\Services\Billing\StripeService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * Mobile Billing & Invoices endpoints.
 *
 * Mirrors the web Settings → Billing tab. Reads from:
 *   • subscriptions     — current plan, billing cycle, renewal date
 *   • plans             — list of available plans for the upgrade picker
 *   • usage_records     — metered usage (ai_replies, campaigns, storage)
 *   • plan_features     — per-feature limits (resolved via Plan::featureLimit)
 *   • payments          — invoice history (Stripe + other gateways)
 *
 * Mutations are handled through Stripe's Billing Portal (`portal_url`) —
 * mobile opens it in a browser tab. We don't implement card-update / cancel
 * directly because Stripe requires SCA and a hosted UI for compliance.
 */
class BillingController extends Controller
{
    /**
     * GET /api/v1/mobile/billing
     * Full billing summary — subscription, current plan, usage cards, invoice
     * history, available upgrade plans.
     */
    public function show(Request $request): JsonResponse
    {
        $user = $request->user();
        $workspace = $user->active_workspace_id
            ? Workspace::find($user->active_workspace_id)
            : null;

        if (!$workspace) {
            return response()->json(['message' => 'No active workspace.'], 422);
        }

        $subscription = Subscription::where('workspace_id', $workspace->id)
            ->with('plan')
            ->latest()
            ->first();
        $currentPlan = $subscription?->plan;

        return response()->json([
            'subscription' => $subscription ? [
                'id'                  => $subscription->id,
                'status'              => $subscription->status,
                'billing_cycle'       => $subscription->billing_cycle,
                'current_period_start' => $subscription->current_period_start?->toIso8601String(),
                'current_period_end'  => $subscription->current_period_end?->toIso8601String(),
                'cancelled_at'        => $subscription->cancelled_at?->toIso8601String(),
                'cancel_at_period_end' => (bool) ($subscription->cancel_at_period_end ?? false),
                'trial_ends_at'       => $subscription->trial_ends_at?->toIso8601String(),
                'renews_label'        => $subscription->current_period_end
                    ? 'Renews ' . $subscription->current_period_end->format('M j, Y')
                    : null,
            ] : null,
            'current_plan' => $currentPlan ? $this->shapePlan($currentPlan, $subscription?->billing_cycle, true) : null,
            'usage'        => $this->buildUsage($workspace, $currentPlan),
            'available_plans' => Plan::where('is_active', true)
                ->orderBy('sort_order')
                ->orderBy('monthly_price')
                ->get()
                ->map(fn (Plan $p) => $this->shapePlan($p, $subscription?->billing_cycle, $currentPlan?->id === $p->id))
                ->all(),
            'portal_url' => $this->portalUrl(),
            'links' => [
                'upgrade_endpoint' => '/api/v1/mobile/billing/upgrade',
                'invoices_endpoint' => '/api/v1/mobile/billing/invoices',
            ],
        ]);
    }

    /**
     * GET /api/v1/mobile/billing/invoices
     * Paginated invoice/payment history. One row per Payment record — Stripe
     * payments include a hosted invoice URL when the Stripe webhook recorded
     * one in `metadata.invoice_url`.
     */
    public function invoices(Request $request): JsonResponse
    {
        $user = $request->user();
        $workspaceId = $user->active_workspace_id;
        if (!$workspaceId) {
            return response()->json(['data' => [], 'meta' => $this->emptyMeta()]);
        }

        $perPage = (int) min(max((int) $request->query('per_page', 20), 5), 100);

        $page = Payment::where('workspace_id', $workspaceId)
            ->orderByDesc('created_at')
            ->paginate($perPage);

        return response()->json([
            'data' => $page->getCollection()->map(fn (Payment $p) => [
                'id'           => $p->id,
                'amount'       => (float) $p->amount,
                'currency'     => strtoupper((string) ($p->currency ?: 'USD')),
                'status'       => (string) $p->status,
                'description'  => (string) ($p->description ?? ''),
                'gateway'      => $p->gateway_slug ?: ($p->stripe_payment_id ? 'stripe' : null),
                'invoice_id'   => $p->stripe_invoice_id,
                'invoice_url'  => (string) (data_get($p->metadata, 'invoice_url') ?? data_get($p->metadata, 'hosted_invoice_url') ?? ''),
                'refunded'     => $p->refund_amount && (float) $p->refund_amount > 0,
                'refund_amount' => (float) ($p->refund_amount ?? 0),
                'refunded_at'  => $p->refunded_at?->toIso8601String(),
                'created_at'   => $p->created_at?->toIso8601String(),
                'paid_label'   => $p->created_at?->format('M j, Y'),
            ])->all(),
            'meta' => [
                'current_page' => $page->currentPage(),
                'per_page'     => $page->perPage(),
                'total'        => $page->total(),
                'has_more'     => $page->hasMorePages(),
            ],
        ]);
    }

    /**
     * GET /api/v1/mobile/billing/invoices/{id}/download
     * Stream the invoice PDF for one Payment record. Generates the PDF on
     * demand via InvoiceService::ensurePdf() if it does not exist yet — so a
     * payment that succeeded before this feature shipped still returns a
     * working file instead of a 404.
     *
     * Scoping: the payment must belong to the caller's active workspace.
     * Admins (is_admin=true) can pull any invoice for support purposes,
     * mirroring the web InvoiceController behaviour.
     *
     * The response is binary application/pdf with an attachment filename so
     * the mobile client can save it directly to device storage.
     */
    public function downloadInvoice(Request $request, InvoiceService $invoices, int $id): StreamedResponse|JsonResponse
    {
        $user = $request->user();

        $payment = Payment::find($id);
        if (! $payment) {
            return response()->json(['message' => 'Invoice not found.'], 404);
        }

        $workspaceId = (int) ($user->active_workspace_id ?? 0);
        $isAdmin     = (bool) ($user->is_admin ?? false);
        if (! $isAdmin && (int) $payment->workspace_id !== $workspaceId) {
            return response()->json(['message' => 'You do not have access to this invoice.'], 403);
        }

        $path = $invoices->ensurePdf($payment);

        if (! Storage::disk('local')->exists($path)) {
            return response()->json(['message' => 'Invoice file missing.'], 404);
        }

        $filename = ($payment->invoice_number ?: 'INV-'.$payment->id).'.pdf';

        return response()->streamDownload(
            fn () => print Storage::disk('local')->get($path),
            $filename,
            [
                'Content-Type'  => 'application/pdf',
                'Cache-Control' => 'private, max-age=0, must-revalidate',
            ]
        );
    }

    /**
     * GET /api/v1/mobile/billing/portal
     * Returns the Stripe Customer Billing Portal URL. Mobile opens it in a
     * browser tab — the user manages cards, downloads invoices, and cancels
     * there. Same flow the web Settings → Billing > "Manage billing" uses.
     *
     * Four distinct failure modes — each returns a specific message so the
     * mobile app can show actionable UI instead of a generic error.
     */
    public function portal(Request $request): JsonResponse
    {
        $user = $request->user();
        $workspaceId = $user->active_workspace_id;
        if (!$workspaceId) {
            return response()->json([
                'message' => 'No active workspace.',
                'reason'  => 'no_workspace',
            ], 422);
        }

        $workspace = Workspace::find($workspaceId);
        if (!$workspace) {
            return response()->json([
                'message' => 'No active workspace.',
                'reason'  => 'no_workspace',
            ], 422);
        }

        $stripe = app(StripeService::class);
        if (!$stripe->isConfigured()) {
            return response()->json([
                'message' => 'Stripe is not configured on this installation. The site admin needs to set STRIPE_SECRET.',
                'reason'  => 'stripe_not_configured',
            ], 422);
        }

        // Most common cause for new accounts: never subscribed, so no Stripe
        // customer was ever created. The Stripe portal can't do anything
        // without a customer — direct mobile to the upgrade flow instead.
        if (empty($workspace->stripe_id) && empty($workspace->stripe_customer_id)) {
            return response()->json([
                'message'          => 'You don\'t have a paid subscription yet. Upgrade to a paid plan first to access the billing portal.',
                'reason'           => 'no_stripe_customer',
                'upgrade_endpoint' => '/api/v1/mobile/billing/upgrade',
            ], 422);
        }

        try {
            $session = $stripe->createCustomerPortalSession($workspace);
            $url = $session->url ?? null;
        } catch (\Stripe\Exception\InvalidRequestException $e) {
            // Most common case: admin hasn't enabled the Customer Portal in
            // the Stripe Dashboard yet. Stripe returns a clear message.
            return response()->json([
                'message' => 'Stripe rejected the portal request: ' . $e->getMessage(),
                'reason'  => 'stripe_portal_not_enabled',
                'hint'    => 'The site admin needs to enable the Customer Portal in the Stripe Dashboard at https://dashboard.stripe.com/test/settings/billing/portal',
            ], 422);
        } catch (\Throwable $e) {
            return response()->json([
                'message' => 'Could not open billing portal: ' . $e->getMessage(),
                'reason'  => 'stripe_error',
            ], 502);
        }

        if (!$url) {
            return response()->json([
                'message' => 'Stripe returned an empty portal URL.',
                'reason'  => 'stripe_error',
            ], 502);
        }

        return response()->json([
            'portal_url' => $url,
            'expires_in' => 600,
            'note'       => 'Open in a browser to manage payment methods, download invoices, and cancel your subscription.',
        ]);
    }

    /**
     * POST /api/v1/mobile/billing/upgrade
     * Starts a Stripe Checkout session for a plan change. Mobile redirects
     * the user to the returned `checkout_url` in a browser tab.
     */
    public function upgrade(Request $request): JsonResponse
    {
        $user = $request->user();
        $workspace = $user->active_workspace_id
            ? Workspace::find($user->active_workspace_id)
            : null;

        if (!$workspace) {
            return response()->json(['message' => 'No active workspace.'], 422);
        }

        $role = $user->workspaceRole($workspace->id);
        if (!in_array($role, ['owner', 'admin'], true)) {
            return response()->json(['message' => 'Only workspace owners and admins can change the plan.'], 403);
        }

        $validator = Validator::make($request->all(), [
            'plan_id'       => ['required', 'integer', 'exists:plans,id'],
            'billing_cycle' => ['required', 'string', 'in:monthly,yearly'],
        ]);
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $plan = Plan::where('is_active', true)->find($request->input('plan_id'));
        if (!$plan) {
            return response()->json(['message' => 'Plan not found or inactive.'], 404);
        }

        $stripe = app(StripeService::class);
        if (!$stripe->isConfigured()) {
            return response()->json([
                'message' => 'Stripe is not configured on this installation. Ask the admin to set STRIPE_SECRET.',
            ], 422);
        }

        try {
            $session = $stripe->createCheckoutSession($workspace, $plan, (string) $request->input('billing_cycle'));
        } catch (\Throwable $e) {
            return response()->json([
                'message' => 'Could not start checkout: ' . $e->getMessage(),
            ], 502);
        }

        return response()->json([
            'message'       => 'Open checkout_url in a browser to complete the upgrade.',
            'checkout_url'  => $session->url ?? null,
            'session_id'    => $session->id ?? null,
            'plan'          => $this->shapePlan($plan, (string) $request->input('billing_cycle'), false),
        ]);
    }

    // ── Helpers ──────────────────────────────────────────────────────

    private function shapePlan(Plan $plan, ?string $billingCycle, bool $isCurrent): array
    {
        $monthly = (float) $plan->monthly_price;
        $yearly  = (float) ($plan->yearly_price ?? round($monthly * 12 * 0.8, 0));
        $cycle = $billingCycle === 'yearly' ? 'yearly' : 'monthly';
        $price = $cycle === 'yearly' ? $yearly : $monthly;

        return [
            'id'            => $plan->id,
            'name'          => $plan->name,
            'slug'          => $plan->slug,
            'description'   => $plan->description,
            'monthly_price' => $monthly,
            'yearly_price'  => $yearly,
            'currency'      => 'USD',
            'price'         => $price,
            'price_label'   => $this->formatPrice($price, $cycle),
            'is_popular'    => (bool) $plan->is_popular,
            'is_current'    => $isCurrent,
            'features'      => is_array($plan->features) ? $plan->features : [],
        ];
    }

    private function buildUsage(Workspace $workspace, ?Plan $plan): array
    {
        if (!$plan) {
            return [];
        }

        $resourceCounts = [
            'contacts'        => $workspace->contacts()->count(),
            'email_accounts'  => $workspace->emailAccounts()->count(),
            'team_members'    => $workspace->members()->count(),
            'workflows'       => $workspace->workflows()->count(),
            'kb_documents'    => KbDocument::where('workspace_id', $workspace->id)->count(),
        ];

        $period = now()->format('Y-m');
        $meteredRecords = UsageRecord::where('workspace_id', $workspace->id)
            ->where('period', $period)
            ->get()
            ->keyBy('feature_key');

        $meteredKeys = ['ai_replies', 'campaigns_per_month', 'storage_mb'];

        $usage = [];
        foreach ($resourceCounts as $key => $used) {
            $limit = $plan->featureLimit($key);
            $usage[] = $this->shapeUsageRow($key, $used, $limit);
        }
        foreach ($meteredKeys as $key) {
            $limit = $plan->featureLimit($key);
            $used  = (int) ($meteredRecords->get($key)?->quantity ?? 0);
            $usage[] = $this->shapeUsageRow($key, $used, $limit);
        }

        return $usage;
    }

    private function shapeUsageRow(string $key, int $used, ?int $limit): array
    {
        $unlimited = $limit === null;
        $percent = $unlimited ? 0 : ($limit > 0 ? min(100, (int) round(($used / $limit) * 100)) : 0);

        $labelMap = [
            'contacts'           => 'Contacts',
            'email_accounts'     => 'Email accounts',
            'team_members'       => 'Team members',
            'workflows'          => 'Workflows',
            'kb_documents'       => 'KB documents',
            'ai_replies'         => 'AI replies (this month)',
            'campaigns_per_month' => 'Campaigns (this month)',
            'storage_mb'         => 'Storage (MB)',
        ];

        return [
            'key'          => $key,
            'label'        => $labelMap[$key] ?? ucwords(str_replace('_', ' ', $key)),
            'used'         => $used,
            'limit'        => $limit,
            'unlimited'    => $unlimited,
            'percent'      => $percent,
            'over_limit'   => !$unlimited && $limit > 0 && $used > $limit,
        ];
    }

    private function portalUrl(): ?string
    {
        $stripe = app(StripeService::class);
        if (!$stripe->isConfigured()) {
            return null;
        }

        $user = request()->user();
        $workspaceId = $user?->active_workspace_id;
        if (!$workspaceId) {
            return null;
        }
        $workspace = Workspace::find($workspaceId);
        if (!$workspace) {
            return null;
        }

        try {
            $session = $stripe->createCustomerPortalSession($workspace);
            return $session->url ?? null;
        } catch (\Throwable) {
            return null;
        }
    }

    private function formatPrice(float $price, string $cycle): string
    {
        $suffix = $cycle === 'yearly' ? '/yr' : '/mo';
        return '$' . rtrim(rtrim(number_format($price, 2, '.', ''), '0'), '.') . $suffix;
    }

    private function emptyMeta(): array
    {
        return ['current_page' => 1, 'per_page' => 20, 'total' => 0, 'has_more' => false];
    }
}
