<?php

namespace App\Services;

use App\Enums\PaymentStatus;
use App\Enums\SubscriptionStatus;
use App\Models\Payment;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Stripe\Checkout\Session;
use Stripe\StripeClient;
use Stripe\Webhook;

class StripeService
{
    protected StripeClient $stripe;

    public function __construct()
    {
        $this->stripe = new StripeClient(config('services.stripe.secret'));
    }

    /*
    |--------------------------------------------------------------------------
    | CHECKOUT SESSION
    |--------------------------------------------------------------------------
    */

    public function createCheckoutSession(User $user, Plan $plan, string $billing): Session
    {
        $currency = config('services.stripe.currency', 'eur');
        $price    = $billing === 'yearly' ? $plan->getYearlyPrice() : $plan->getMonthlyPrice();

        $lineItem = [
            'price_data' => [
                'currency'     => $currency,
                'product_data' => [
                    'name'        => $plan->name,
                    'description' => $billing === 'yearly' ? 'Jährliches Abonnement' : 'Monatliches Abonnement',
                ],
                'unit_amount'  => $price,
                'recurring'    => [
                    'interval' => $billing === 'yearly' ? 'year' : 'month',
                ],
            ],
            'quantity' => 1,
        ];

        // Stripe-Kunden-ID aus der User-Subscription holen (bleibt auch auf Free-Plan erhalten),
        // damit das bestehende Stripe-Kundenkonto (inkl. Guthaben) immer wiederverwendet wird.
        $existingCustomerId = Subscription::where('user_id', $user->id)
            ->whereNotNull('provider_customer_id')
            ->latest('created_at')
            ->value('provider_customer_id');

        $sharedMeta = [
            'user_id' => $user->id,
            'plan_id' => $plan->id,
            'billing' => $billing,
        ];

        $params = [
            'mode'                 => 'subscription',
            'payment_method_types' => ['card'],
            'line_items'           => [$lineItem],
            'success_url'          => route('checkout.success') . '?session_id={CHECKOUT_SESSION_ID}',
            'cancel_url'           => route('subscription.index'),
            'client_reference_id'  => $user->id,
            'metadata'             => $sharedMeta,
            'subscription_data'    => ['metadata' => $sharedMeta],
            'locale'               => 'de',
            'allow_promotion_codes'=> true,
        ];

        if ($existingCustomerId) {
            // Bestehenden Kunden übergeben → Guthaben wird automatisch verrechnet
            $params['customer'] = $existingCustomerId;
        } else {
            $params['customer_email'] = $user->email;
        }

        return $this->stripe->checkout->sessions->create($params);
    }

    /*
    |--------------------------------------------------------------------------
    | WEBHOOK DISPATCH
    |--------------------------------------------------------------------------
    */

    public function handleWebhook(string $payload, string $signature): void
    {
        $event = Webhook::constructEvent(
            $payload,
            $signature,
            config('services.stripe.webhook_secret')
        );

        Log::info('[Stripe] Webhook received', ['type' => $event->type, 'id' => $event->id]);

        match ($event->type) {
            // Primärer Event: Subscription wurde in Stripe angelegt
            'customer.subscription.created' => $this->onSubscriptionCreated($event->data->object),

            // Fallback: falls checkout.session.completed konfiguriert ist
            'checkout.session.completed'    => $this->onCheckoutCompleted($event->data->object),

            // Zahlungen (invoice.payment_succeeded absichtlich weggelassen – invoice.paid ist ausreichend)
            'invoice.paid'                  => $this->onInvoicePaid($event->data->object),

            'invoice.payment_failed'        => $this->onInvoiceFailed($event->data->object),

            // Rückerstattungen
            'charge.refunded'               => $this->onChargeRefunded($event->data->object),

            // Statusänderungen
            'customer.subscription.updated' => $this->onSubscriptionUpdated($event->data->object),
            'customer.subscription.deleted' => $this->onSubscriptionDeleted($event->data->object),

            default => null,
        };
    }

    /*
    |--------------------------------------------------------------------------
    | SUBSCRIPTION ERSTELLT (primärer Handler)
    |--------------------------------------------------------------------------
    */

    protected function onSubscriptionCreated(object $stripeSub): void
    {
        $stripeSubId    = $stripeSub->id;
        $stripeCustomer = is_string($stripeSub->customer) ? $stripeSub->customer : ($stripeSub->customer->id ?? null);

        $userId         = $stripeSub->metadata->user_id ?? null;
        $planId         = $stripeSub->metadata->plan_id ?? null;
        $billing        = $stripeSub->metadata->billing ?? 'monthly';
        $oldStripeSubId = $stripeSub->metadata->old_stripe_sub_id ?? null;

        Log::info('[Stripe] subscription.created', compact('stripeSubId', 'userId', 'planId', 'billing'));

        // Idempotenz – bereits vorhanden
        if (Subscription::where('provider_subscription_id', $stripeSubId)->where('provider', 'stripe')->exists()) {
            Log::info('[Stripe] Subscription bereits vorhanden, skip', ['sub_id' => $stripeSubId]);
            return;
        }

        $user = User::find($userId);
        $plan = Plan::find($planId);

        if (!$user || !$plan) {
            Log::error('[Stripe] User oder Plan nicht gefunden', compact('userId', 'planId'));
            return;
        }

        $price    = $billing === 'yearly' ? $plan->getYearlyPrice() : $plan->getMonthlyPrice();
        $startsAt = !empty($stripeSub->current_period_start)
            ? Carbon::createFromTimestamp($stripeSub->current_period_start)
            : now();

        $endsAt = !empty($stripeSub->current_period_end)
            ? Carbon::createFromTimestamp($stripeSub->current_period_end)
            : now()->addMonth();

        // Alte Stripe-Subscription kündigen – nur wenn sie in Stripe noch existiert (nicht bereits
        // via cancelUserSubscription o.ä. beendet) und eine andere Sub-ID ist als die neue.
        if ($oldStripeSubId && $oldStripeSubId !== $stripeSubId) {
            try {
                $old = $this->stripe->subscriptions->retrieve($oldStripeSubId);
                if (!in_array($old->status, ['canceled', 'incomplete_expired'])) {
                    $this->stripe->subscriptions->cancel($oldStripeSubId);
                    Log::info('[Stripe] Alte Sub gekündigt', ['old_sub' => $oldStripeSubId]);
                }
            } catch (\Throwable $e) {
                Log::warning('[Stripe] Alte Sub konnte nicht gecancelt werden (evtl. bereits weg)', ['old_sub' => $oldStripeSubId, 'error' => $e->getMessage()]);
            }
        }

        // Einzige Subscription des Users aktualisieren (eine Zeile pro User)
        $subscription = Subscription::updateOrCreate(
            ['user_id' => $user->id],
            [
                'plan_id'                  => $plan->id,
                'plan_name'                => $plan->name,
                'plan_slug'                => $plan->plan_key,
                'price'                    => $price,
                'original_price'           => $price,
                'interval'                 => $billing,
                'status'                   => SubscriptionStatus::Active->value,
                'starts_at'                => $startsAt,
                'ends_at'                  => $endsAt,
                'provider'                 => 'stripe',
                'provider_customer_id'     => $stripeCustomer,
                'provider_subscription_id' => $stripeSubId,
            ]
        );

        Log::info('[Stripe] Subscription angelegt/geupdated', ['sub_id' => $stripeSubId, 'plan' => $plan->name, 'user' => $user->email]);

        // Catch-up: falls invoice.paid vor subscription.created ankam → Invoice direkt verarbeiten
        $latestInvoiceId = is_string($stripeSub->latest_invoice ?? null)
            ? $stripeSub->latest_invoice
            : ($stripeSub->latest_invoice->id ?? null);

        if ($latestInvoiceId) {
            $this->syncInvoicePayment($subscription, $latestInvoiceId);
        }
    }

    /*
    |--------------------------------------------------------------------------
    | CHECKOUT SESSION COMPLETED (Fallback/Ergänzung)
    |--------------------------------------------------------------------------
    */

    protected function onCheckoutCompleted(object $session): void
    {
        if (($session->payment_status ?? '') !== 'paid') {
            return;
        }

        $newStripeSubId = is_string($session->subscription)
            ? $session->subscription
            : ($session->subscription->id ?? null);

        if (!$newStripeSubId) {
            return;
        }

        $userId  = $session->client_reference_id;
        $planId  = $session->metadata->plan_id ?? null;
        $billing = $session->metadata->billing ?? 'monthly';

        $user = User::find($userId);
        $plan = Plan::find($planId);

        if (!$user || !$plan) {
            Log::error('[Stripe] checkout.completed – User/Plan nicht gefunden', compact('userId', 'planId'));
            return;
        }

        // Race-Condition-sicherer Check + Update der einzigen User-Subscription in einer DB-Transaktion
        $subscription = DB::transaction(function () use ($user, $plan, $billing, $newStripeSubId, $session) {
            // Nochmal prüfen (mit Lock) ob subscription.created bereits fertig war
            $existing = Subscription::where('user_id', $user->id)
                ->where('provider_subscription_id', $newStripeSubId)
                ->lockForUpdate()
                ->first();

            if ($existing) {
                Log::info('[Stripe] checkout.completed – Subscription bereits vorhanden (Lock)', ['sub_id' => $newStripeSubId]);
                return $existing;
            }

            // Fallback: subscription.created hat nicht gefeuert → einzige User-Subscription updaten
            $stripeSub      = $this->stripe->subscriptions->retrieve($newStripeSubId);
            $stripeCustomer = is_string($session->customer) ? $session->customer : ($session->customer->id ?? null);
            $price          = $billing === 'yearly' ? $plan->getYearlyPrice() : $plan->getMonthlyPrice();

            $sub = Subscription::updateOrCreate(
                ['user_id' => $user->id],
                [
                    'plan_id'                  => $plan->id,
                    'plan_name'                => $plan->name,
                    'plan_slug'                => $plan->plan_key,
                    'price'                    => $price,
                    'original_price'           => $price,
                    'interval'                 => $billing,
                    'status'                   => SubscriptionStatus::Active->value,
                    'starts_at'                => !empty($stripeSub->current_period_start) ? Carbon::createFromTimestamp($stripeSub->current_period_start) : now(),
                    'ends_at'                  => !empty($stripeSub->current_period_end)   ? Carbon::createFromTimestamp($stripeSub->current_period_end)   : now()->addMonth(),
                    'provider'                 => 'stripe',
                    'provider_customer_id'     => $stripeCustomer,
                    'provider_subscription_id' => $newStripeSubId,
                ]
            );

            Log::info('[Stripe] Subscription via checkout.completed geupdated (Fallback)', ['sub_id' => $newStripeSubId]);
            return $sub;
        });

        // Payment-Catch-up: in beiden Fällen sicherstellen dass Payment existiert
        $latestInvoiceId = $this->extractInvoiceIdFromSession($session);
        if ($latestInvoiceId && $subscription) {
            $this->syncInvoicePayment($subscription, $latestInvoiceId);
        }
    }

    protected function extractInvoiceIdFromSession(object $session): ?string
    {
        $inv = $session->invoice ?? null;
        if (is_string($inv) && $inv !== '') return $inv;
        if (is_object($inv) && !empty($inv->id)) return $inv->id;
        return null;
    }

    /*
    |--------------------------------------------------------------------------
    | INVOICE PAID → Payment anlegen
    |--------------------------------------------------------------------------
    */

    protected function onInvoicePaid(object $invoice): void
    {
        $invoiceId = $invoice->id;

        // Subscription-ID aus dem Invoice-Objekt extrahieren
        // Stripe API < 2025: $invoice->subscription (String)
        // Stripe API 2025+:  $invoice->parent->subscription_details->subscription
        $sub = $invoice->subscription ?? null;
        $stripeSubId = match (true) {
            is_string($sub) && $sub !== ''     => $sub,
            is_object($sub) && !empty($sub->id) => $sub->id,
            default                             => null,
        };

        // Fallback für Stripe API 2025+ (parent.subscription_details.subscription)
        if (!$stripeSubId) {
            $stripeSubId = $invoice->parent->subscription_details->subscription
                ?? $invoice->parent->subscription_id
                ?? null;
        }

        // Fallback: erste Line-Item enthält subscription (ältere API-Pfade)
        if (!$stripeSubId && !empty($invoice->lines->data)) {
            $line        = $invoice->lines->data[0] ?? null;
            $stripeSubId = $line->subscription ?? ($line->parent->subscription_item_details->subscription ?? null);
        }

        Log::info('[Stripe] onInvoicePaid', [
            'invoice_id'     => $invoiceId,
            'sub_id_raw'     => is_object($sub) ? '(object)' : $sub,
            'sub_id'         => $stripeSubId,
            'billing_reason' => $invoice->billing_reason ?? null,
            'amount_paid'    => $invoice->amount_paid ?? null,
        ]);

        if (!$stripeSubId) {
            Log::warning('[Stripe] onInvoicePaid – keine Subscription-ID im Invoice', ['invoice_id' => $invoiceId]);
            return;
        }

        $subscription = Subscription::where('provider_subscription_id', $stripeSubId)
            ->where('provider', 'stripe')
            ->first();

        if (!$subscription) {
            Log::warning('[Stripe] onInvoicePaid – Subscription nicht in DB (Race-Condition, Catch-up via subscription.created)', [
                'sub_id'         => $stripeSubId,
                'billing_reason' => $invoice->billing_reason ?? null,
            ]);
            return;
        }

        // Lesbares billing_reason für DB (Standard: Stripe-Wert übernehmen)
        $billingReasonDb = $invoice->billing_reason ?? null;

        // Bei Verlängerungen / Upgrades (nicht Erstabrechnung) Laufzeit updaten
        if (($invoice->billing_reason ?? '') !== 'subscription_create') {
            $stripeSub = $this->stripe->subscriptions->retrieve($stripeSubId);
            $updates = ['status' => SubscriptionStatus::Active->value];
            if (!empty($stripeSub->current_period_end)) {
                $updates['ends_at'] = Carbon::createFromTimestamp($stripeSub->current_period_end);
            }
            $subscription->update($updates);

            // Für Planwechsel: "upgrade:Pro" / "downgrade:Basic" als beschreibendes Label speichern
            if (($invoice->billing_reason ?? '') === 'subscription_update') {
                $action  = $stripeSub->metadata->action  ?? null;
                $toPlan  = $stripeSub->metadata->to_plan ?? null;
                if ($action && $toPlan) {
                    $billingReasonDb = $action . ':' . $toPlan;
                }
            }
        }

        // Duplikat-Schutz via Invoice-ID (soft-check + DB-unique als harte Schranke)
        if (Payment::where('provider_payment_id', $invoiceId)->where('provider', 'stripe')->exists()) {
            Log::info('[Stripe] Invoice bereits erfasst, skip', ['invoice_id' => $invoiceId]);
            return;
        }

        try {
            Payment::create([
                'user_id'             => $subscription->user_id,
                'subscription_id'     => $subscription->id,
                'amount'              => $invoice->amount_paid,
                'currency'            => strtoupper($invoice->currency),
                'status'              => PaymentStatus::Paid->value,
                'provider'            => 'stripe',
                'provider_payment_id' => $invoiceId,
                'invoice_id'          => $invoiceId,
                'billing_reason'      => $billingReasonDb,
                'paid_at'             => now(),
            ]);
            Log::info('[Stripe] Payment angelegt', ['invoice_id' => $invoiceId, 'amount' => $invoice->amount_paid, 'reason' => $billingReasonDb]);
        } catch (\Illuminate\Database\UniqueConstraintViolationException $e) {
            Log::info('[Stripe] Payment-Duplikat abgefangen (concurrent webhook)', ['invoice_id' => $invoiceId]);
        }
    }

    /*
    |--------------------------------------------------------------------------
    | INVOICE FAILED
    |--------------------------------------------------------------------------
    */

    protected function onInvoiceFailed(object $invoice): void
    {
        $stripeSubId = is_string($invoice->subscription) ? $invoice->subscription : ($invoice->subscription->id ?? null);
        if (!$stripeSubId) return;

        $subscription = Subscription::where('provider_subscription_id', $stripeSubId)
            ->where('provider', 'stripe')
            ->first();

        if (!$subscription) return;

        $subscription->update(['status' => SubscriptionStatus::PastDue->value]);

        if (!Payment::where('provider_payment_id', $invoice->id)->where('provider', 'stripe')->exists()) {
            Payment::create([
                'user_id'             => $subscription->user_id,
                'subscription_id'     => $subscription->id,
                'amount'              => $invoice->amount_due,
                'currency'            => strtoupper($invoice->currency),
                'status'              => PaymentStatus::Failed->value,
                'provider'            => 'stripe',
                'provider_payment_id' => $invoice->id,
                'invoice_id'          => $invoice->id,
                'billing_reason'      => $invoice->billing_reason,
                'paid_at'             => null,
            ]);
        }
    }

    /*
    |--------------------------------------------------------------------------
    | INVOICE SYNC (Catch-up nach subscription.created)
    |--------------------------------------------------------------------------
    */

    protected function syncInvoicePayment(Subscription $subscription, string $invoiceId): void
    {
        // Schon erfasst?
        if (Payment::where('provider_payment_id', $invoiceId)->where('provider', 'stripe')->exists()) {
            Log::info('[Stripe] syncInvoicePayment – Invoice bereits vorhanden, skip', ['invoice_id' => $invoiceId]);
            return;
        }

        try {
            $invoice = $this->stripe->invoices->retrieve($invoiceId);

            if ($invoice->status !== 'paid') {
                Log::info('[Stripe] syncInvoicePayment – Invoice nicht paid, skip', ['invoice_id' => $invoiceId, 'status' => $invoice->status]);
                return;
            }

            try {
                Payment::create([
                    'user_id'             => $subscription->user_id,
                    'subscription_id'     => $subscription->id,
                    'amount'              => $invoice->amount_paid,
                    'currency'            => strtoupper($invoice->currency),
                    'status'              => PaymentStatus::Paid->value,
                    'provider'            => 'stripe',
                    'provider_payment_id' => $invoice->id,
                    'invoice_id'          => $invoice->id,
                    'billing_reason'      => $invoice->billing_reason,
                    'paid_at'             => now(),
                ]);
                Log::info('[Stripe] Payment via Subscription-Sync angelegt', ['invoice_id' => $invoiceId, 'amount' => $invoice->amount_paid]);
            } catch (\Illuminate\Database\UniqueConstraintViolationException $e) {
                Log::info('[Stripe] syncInvoicePayment – Duplikat abgefangen', ['invoice_id' => $invoiceId]);
            }
        } catch (\Throwable $e) {
            Log::warning('[Stripe] syncInvoicePayment fehlgeschlagen', ['invoice_id' => $invoiceId, 'error' => $e->getMessage()]);
        }
    }

    /*
    |--------------------------------------------------------------------------
    | CHARGE REFUNDED
    |--------------------------------------------------------------------------
    */

    protected function onChargeRefunded(object $charge): void
    {
        $chargeId  = $charge->id;
        $invoiceId = is_string($charge->invoice ?? null) ? $charge->invoice : ($charge->invoice->id ?? null);

        Log::info('[Stripe] charge.refunded', [
            'charge_id'       => $chargeId,
            'invoice_id'      => $invoiceId,
            'fully_refunded'  => $charge->refunded ?? false,
            'amount_refunded' => $charge->amount_refunded ?? 0,
        ]);

        // Payment via Invoice-ID finden
        $payment = $invoiceId
            ? Payment::where('provider_payment_id', $invoiceId)->where('provider', 'stripe')->first()
            : null;

        if (!$payment) {
            Log::warning('[Stripe] charge.refunded – kein Payment gefunden', ['charge_id' => $chargeId, 'invoice_id' => $invoiceId]);
            return;
        }

        // Prüfen ob dieser Refund ein Downgrade-Refund war (bereits als eigener Eintrag erfasst)
        $latestRefundId = $charge->refunds->data[0]->id ?? null;
        $isDowngradeRefund = $latestRefundId
            && Payment::where('provider_payment_id', $latestRefundId)->where('provider', 'stripe')->exists();

        if ($isDowngradeRefund) {
            // Refund-Eintrag existiert bereits – Original-Payment unverändert lassen
            Log::info('[Stripe] charge.refunded – Downgrade-Refund bereits erfasst, kein Update des Original-Payments', [
                'refund_id' => $latestRefundId,
            ]);
            return;
        }

        // Kein bekannter Refund → Original-Payment als erstattet markieren
        $payment->update(['status' => PaymentStatus::Refunded->value]);
        Log::info('[Stripe] Payment als erstattet markiert', ['payment_id' => $payment->id]);

        // Bei vollständiger Erstattung von außen (z.B. Stripe-Dashboard) Subscription kündigen
        if ($charge->refunded === true) {
            $subscription = Subscription::find($payment->subscription_id);

            if ($subscription) {
                $subscription->update(['status' => SubscriptionStatus::Canceled->value]);
                Log::info('[Stripe] Subscription wegen Vollerstattung gekündigt', ['subscription_id' => $subscription->id]);
            }
        }
    }

    /*
    |--------------------------------------------------------------------------
    | SUBSCRIPTION UPDATED / DELETED
    |--------------------------------------------------------------------------
    */

    protected function onSubscriptionUpdated(object $stripeSub): void
    {
        $subscription = Subscription::where('provider_subscription_id', $stripeSub->id)
            ->where('provider', 'stripe')
            ->first();

        if (!$subscription) return;

        $status = match ($stripeSub->status) {
            'active'   => SubscriptionStatus::Active,
            'canceled' => SubscriptionStatus::Canceled,
            'past_due' => SubscriptionStatus::PastDue,
            default    => SubscriptionStatus::Expired,
        };

        $updates = [
            'status'  => $status->value,
            'ends_at' => !empty($stripeSub->current_period_end)
                ? Carbon::createFromTimestamp($stripeSub->current_period_end)
                : $subscription->ends_at,
        ];

        // Bei Upgrade/Downgrade via API kommen Plan-Infos in den Metadata
        $planId  = $stripeSub->metadata->plan_id ?? null;
        $billing = $stripeSub->metadata->billing ?? null;

        if ($planId) {
            $plan = Plan::find($planId);
            if ($plan) {
                $price = $billing === 'yearly' ? $plan->getYearlyPrice() : $plan->getMonthlyPrice();
                $updates += [
                    'plan_id'        => $plan->id,
                    'plan_name'      => $plan->name,
                    'plan_slug'      => $plan->plan_key,
                    'price'          => $price,
                    'original_price' => $price,
                    'interval'       => $billing ?? $subscription->interval,
                ];
                Log::info('[Stripe] Subscription Plan-Update', ['sub_id' => $stripeSub->id, 'plan' => $plan->name]);
            }
        }

        $subscription->update($updates);
    }

    protected function onSubscriptionDeleted(object $stripeSub): void
    {
        $subscription = Subscription::where('provider_subscription_id', $stripeSub->id)
            ->where('provider', 'stripe')
            ->first();

        if (!$subscription) return;

        // Extern gelöschte Subscriptions (z. B. durch Stripe-Dashboard oder Zahlungsausfall)
        // auf Free-Plan zurücksetzen – customer_id bleibt für späteres Re-Abonnieren erhalten
        $freePlan = Plan::freePlan();

        $subscription->update([
            'plan_id'                  => $freePlan?->id,
            'plan_name'                => $freePlan?->name ?? 'Free',
            'plan_slug'                => $freePlan?->plan_key ?? 'free',
            'price'                    => 0,
            'original_price'           => 0,
            'interval'                 => 'monthly',
            'status'                   => SubscriptionStatus::Active->value,
            'starts_at'                => now(),
            'ends_at'                  => null,
            'provider'                 => null,
            'provider_subscription_id' => null,
        ]);

        Log::info('[Stripe] Subscription via webhook auf Free-Plan zurückgesetzt', ['sub_id' => $stripeSub->id]);
    }

    /*
    |--------------------------------------------------------------------------
    | UPGRADE / DOWNGRADE (Bestehende Stripe-Subscription updaten)
    |--------------------------------------------------------------------------
    */

    public function updateStripeSubscription(string $stripeSubId, Plan $plan, string $billing, string $userId): void
    {
        $currency = config('services.stripe.currency', 'eur');
        $price    = $billing === 'yearly' ? $plan->getYearlyPrice() : $plan->getMonthlyPrice();

        $stripeSub = $this->stripe->subscriptions->retrieve($stripeSubId);
        $itemId    = $stripeSub->items->data[0]->id ?? null;

        if (!$itemId) {
            throw new \RuntimeException('Stripe subscription hat keine Items');
        }

        // Upgrade oder Downgrade ermitteln (anhand des aktuellen DB-Preises)
        $currentDbPrice = Subscription::where('provider_subscription_id', $stripeSubId)
            ->where('provider', 'stripe')
            ->value('price') ?? 0;
        $action = $price >= $currentDbPrice ? 'upgrade' : 'downgrade';

        // subscriptions->update() erlaubt kein product_data → erst Price anlegen, dann ID nutzen
        $priceObj = $this->stripe->prices->create([
            'currency'     => $currency,
            'unit_amount'  => $price,
            'product_data' => [
                'name' => $plan->name,
            ],
            'recurring' => [
                'interval' => $billing === 'yearly' ? 'year' : 'month',
            ],
        ]);

        $this->stripe->subscriptions->update($stripeSubId, [
            'items' => [[
                'id'    => $itemId,
                'price' => $priceObj->id,
            ]],
            // create_prorations: Proration-Posten werden auf die nächste Rechnung gelegt
            // und dort korrekt mit Guthaben verrechnet. always_invoice würde sofort zwei
            // getrennte Transaktionen erzeugen (Kredit auf Kundenkonto + voller neuer Preis).
            'proration_behavior' => 'always_invoice',
            'metadata'           => [
                'user_id' => $userId,
                'plan_id' => $plan->id,
                'billing' => $billing,
                'action'  => $action,      // 'upgrade' oder 'downgrade'
                'to_plan' => $plan->name,  // Ziel-Planname für Invoice-Label
            ],
        ]);

        Log::info('[Stripe] Subscription per API geupdated', ['sub_id' => $stripeSubId, 'plan' => $plan->name]);

        // DB sofort aktualisieren für direktes UI-Feedback (Webhook kommt nach)
        Subscription::where('provider_subscription_id', $stripeSubId)
            ->where('provider', 'stripe')
            ->update([
                'plan_id'        => $plan->id,
                'plan_name'      => $plan->name,
                'plan_slug'      => $plan->plan_key,
                'price'          => $price,
                'original_price' => $price,
                'interval'       => $billing,
                'status'         => SubscriptionStatus::Active->value,
            ]);
    }

    /*
    |--------------------------------------------------------------------------
    | PRORATION REFUND (anteiliger Rückerstattung bei Wechsel auf Free)
    |--------------------------------------------------------------------------
    */

    protected function issueProrationRefund(Subscription $sub): void
    {
        $stripeSubId = $sub->provider_subscription_id;

        try {
            // Verbleibende Zeit aus der DB-Subscription berechnen (zuverlässiger als Invoice-Line-Items,
            // da Stripe nach always_invoice-Upgrades current_period_end temporär zurücksetzt).
            $now    = now();
            $endsAt = $sub->ends_at;

            if (!$endsAt || $endsAt->isPast()) {
                Log::info('[Stripe] Proration-Refund – Subscription bereits abgelaufen', ['sub_id' => $stripeSubId]);
                return;
            }

            $startsAt         = $sub->starts_at ?? $now;
            $totalSeconds     = max(1, $endsAt->timestamp - $startsAt->timestamp);
            $remainingSeconds = $endsAt->timestamp - $now->timestamp;

            if ($remainingSeconds <= 0) {
                return;
            }

            // Erstattungsbetrag auf Basis des vollen Abonnementpreises (nicht invoice.amount_paid,
            // da bei Upgrades nur der Differenzbetrag auf der letzten Invoice steht).
            $refundAmount = (int) round(($remainingSeconds / $totalSeconds) * $sub->price);

            if ($refundAmount <= 0) {
                return;
            }

            $chargeId  = null;
            $currency  = config('services.stripe.currency', 'eur');
            $customerId = $sub->provider_customer_id;

            // Strategie 1: Bezahlte Invoices der Subscription durchsuchen
            $invoices = $this->stripe->invoices->all([
                'subscription' => $stripeSubId,
                'status'       => 'paid',
                'limit'        => 5,
            ]);

            Log::info('[Stripe] Proration-Refund – Invoices gefunden', [
                'sub_id' => $stripeSubId,
                'count'  => count($invoices->data),
            ]);

            foreach ($invoices->data as $invoice) {
                $currency = strtoupper($invoice->currency);

                // Stripe API < 2025: invoice.charge ist direkt gesetzt
                $rawCharge = $invoice->charge ?? null;
                $cid       = is_string($rawCharge) ? $rawCharge : ($rawCharge->id ?? null);

                // Stripe API 2025+: charge hängt am PaymentIntent (invoice.charge ist null)
                if (!$cid) {
                    $rawPi = $invoice->payment_intent ?? null;
                    $piId  = is_string($rawPi) ? $rawPi : ($rawPi->id ?? null);
                    if ($piId) {
                        try {
                            $pi        = $this->stripe->paymentIntents->retrieve($piId);
                            $rawLatest = $pi->latest_charge ?? null;
                            $cid       = is_string($rawLatest) ? $rawLatest : ($rawLatest->id ?? null);
                            Log::info('[Stripe] Proration-Refund – PaymentIntent lookup', [
                                'pi_id'         => $piId,
                                'latest_charge' => $cid ?? 'null',
                            ]);
                        } catch (\Throwable $e) {
                            Log::warning('[Stripe] Proration-Refund – PaymentIntent-Abruf fehlgeschlagen', ['pi_id' => $piId, 'error' => $e->getMessage()]);
                        }
                    }
                }

                Log::info('[Stripe] Proration-Refund – Invoice geprüft', [
                    'invoice_id'     => $invoice->id,
                    'charge_raw'     => is_string($rawCharge) ? $rawCharge : (is_object($rawCharge) ? 'object' : 'null'),
                    'charge_found'   => $cid ?? 'null',
                ]);

                if ($cid) {
                    $chargeId = $cid;
                    break;
                }
            }

            // Strategie 2 (Fallback): Charges direkt über Customer abfragen –
            // umgeht API-Versionsunterschiede im Invoice-Objekt vollständig.
            if (!$chargeId && $customerId) {
                Log::info('[Stripe] Proration-Refund – Fallback: Charges per Customer', ['customer_id' => $customerId]);

                $charges = $this->stripe->charges->all([
                    'customer' => $customerId,
                    'limit'    => 10,
                ]);

                foreach ($charges->data as $charge) {
                    if ($charge->status === 'succeeded') {
                        $currency = strtoupper($charge->currency);
                        $chargeId = $charge->id;
                        Log::info('[Stripe] Proration-Refund – Charge via Customer gefunden', ['charge_id' => $chargeId]);
                        break;
                    }
                }
            }

            if (!$chargeId) {
                Log::warning('[Stripe] Proration-Refund – keine erstattungsfähige Charge gefunden', ['sub_id' => $stripeSubId, 'customer_id' => $customerId]);
                return;
            }

            // Bereits erstatteten Betrag berücksichtigen und auf tatsächlich erstattbaren Betrag kappen
            $charge        = $this->stripe->charges->retrieve($chargeId);
            $maxRefundable = ($charge->amount ?? 0) - ($charge->amount_refunded ?? 0);
            $refundAmount  = min($refundAmount, $maxRefundable);

            if ($refundAmount <= 0) {
                Log::info('[Stripe] Proration-Refund – Charge bereits vollständig erstattet', ['charge_id' => $chargeId]);
                return;
            }

            $refund = $this->stripe->refunds->create([
                'charge'   => $chargeId,
                'amount'   => $refundAmount,
                'reason'   => 'requested_by_customer',
                'metadata' => [
                    'reason'          => 'downgrade_to_free',
                    'subscription_id' => $stripeSubId,
                ],
            ]);

            Log::info('[Stripe] Proration-Refund erstellt', [
                'refund_id' => $refund->id,
                'amount'    => $refundAmount,
                'sub_id'    => $stripeSubId,
            ]);

            // Eigenen Payment-Eintrag anlegen damit die Erstattung in der Rechnungsübersicht sichtbar ist
            try {
                Payment::create([
                    'user_id'             => $sub->user_id,
                    'subscription_id'     => $sub->id,
                    'amount'              => $refundAmount,
                    'currency'            => $currency,
                    'status'              => PaymentStatus::Refunded->value,
                    'provider'            => 'stripe',
                    'provider_payment_id' => $refund->id,
                    'invoice_id'          => null,
                    'billing_reason'      => 'refund:downgrade_to_free',
                    'paid_at'             => now(),
                ]);
            } catch (\Illuminate\Database\UniqueConstraintViolationException) {
                // bereits vorhanden – kein Problem
            }

        } catch (\Throwable $e) {
            // Refund-Fehler nicht weiterwerfen – Kündigung soll trotzdem durchlaufen
            Log::error('[Stripe] Proration-Refund fehlgeschlagen', ['sub_id' => $stripeSubId, 'error' => $e->getMessage()]);
        }
    }

    /*
    |--------------------------------------------------------------------------
    | FREE PLAN (Stripe-Subscription kündigen)
    |--------------------------------------------------------------------------
    */

    public function cancelUserSubscription(User $user): void
    {
        $sub = Subscription::where('user_id', $user->id)
            ->where('provider', 'stripe')
            ->whereNotNull('provider_subscription_id')
            ->whereIn('status', [SubscriptionStatus::Active->value, SubscriptionStatus::PastDue->value])
            ->latest('created_at')
            ->first();

        if (!$sub) {
            Log::info('[Stripe] cancelUserSubscription – kein aktives Abo gefunden', ['user_id' => $user->id]);
            return;
        }

        // Anteiligen Restbetrag zurückerstatten bevor die Sub gekündigt wird
        $this->issueProrationRefund($sub);

        try {
            $this->stripe->subscriptions->cancel($sub->provider_subscription_id);
            Log::info('[Stripe] Subscription sofort gekündigt', ['sub_id' => $sub->provider_subscription_id]);
        } catch (\Throwable $e) {
            Log::error('[Stripe] Kündigung fehlgeschlagen', ['sub_id' => $sub->provider_subscription_id, 'error' => $e->getMessage()]);
            throw $e;
        }

        // Bestehende Subscription auf Free-Plan zurücksetzen – provider_customer_id bleibt erhalten!
        $freePlan = Plan::freePlan();

        $sub->update([
            'plan_id'                  => $freePlan?->id,
            'plan_name'                => $freePlan?->name ?? 'Free',
            'plan_slug'                => $freePlan?->plan_key ?? 'free',
            'price'                    => 0,
            'original_price'           => 0,
            'interval'                 => 'monthly',
            'status'                   => SubscriptionStatus::Active->value,
            'starts_at'                => now(),
            'ends_at'                  => null,
            'provider'                 => null,
            'provider_subscription_id' => null,
            // provider_customer_id absichtlich nicht überschreiben → bleibt für späteres Re-Abonnieren
        ]);

        Log::info('[Stripe] Subscription auf Free-Plan zurückgesetzt (customer_id behalten)', ['user_id' => $user->id]);
    }

    /*
    |--------------------------------------------------------------------------
    | BILLING PORTAL
    |--------------------------------------------------------------------------
    */

    public function createBillingPortalSession(string $stripeCustomerId): string
    {
        $session = $this->stripe->billingPortal->sessions->create([
            'customer'   => $stripeCustomerId,
            'return_url' => route('subscription.index'),
        ]);

        return $session->url;
    }
}
