<?php

namespace App\Services\Payments\Providers;

use App\Contracts\Payments\PaymentProviderContract;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Subscription as CashierSubscription;
use Stripe\Checkout\Session;
use Stripe\Customer;
use Stripe\Price;
use Stripe\Product;
use Stripe\Stripe;

class StripeProvider implements PaymentProviderContract
{
    protected array $config;

    protected ?string $apiKey;

    public function __construct(array $config = [])
    {
        $this->config = array_merge([
            'api_key' => config('services.stripe.secret'),
            'webhook_secret' => config('services.stripe.webhook_secret'),
            'currency' => config('cashier.currency', 'USD'),
            'success_url' => route('payment.success'),
            'cancel_url' => route('payment.cancel'),
        ], $config);

        $this->apiKey = $this->config['api_key'] ?? null;

        if ($this->apiKey) {
            Stripe::setApiKey($this->apiKey);
        }
    }

    public function getName(): string
    {
        return 'stripe';
    }

    public function isActive(): bool
    {
        return ! empty($this->apiKey) && $this->apiKey !== 'sk_test_placeholder';
    }

    public function createSubscription(User $user, Plan $plan, array $options = []): array
    {
        try {
            // Create or retrieve Stripe customer
            $customer = $this->getOrCreateCustomer($user);

            // Create or retrieve Stripe product and price
            $priceId = $this->getOrCreatePrice($plan);

            // Create subscription
            $subscriptionBuilder = $user->newSubscription('default', $priceId);

            if (! empty($options['trial_days'])) {
                $subscriptionBuilder->trialDays($options['trial_days']);
            }

            if (! empty($options['coupon'])) {
                $subscriptionBuilder->withCoupon($options['coupon']);
            }

            if (! empty($options['payment_method'])) {
                $subscriptionBuilder->create($options['payment_method']);
            } else {
                // Create checkout session for payment method collection
                $sessionId = $this->createCheckoutSession($user, $plan, $options);

                return [
                    'requires_action' => true,
                    'checkout_session_id' => $sessionId,
                    'type' => 'checkout_session',
                ];
            }

            $stripeSubscription = $subscriptionBuilder->create();

            return [
                'provider_subscription_id' => $stripeSubscription->stripe_id,
                'status' => $stripeSubscription->stripe_status,
                'current_period_start' => $stripeSubscription->created_at,
                'current_period_end' => $stripeSubscription->ends_at,
                'trial_ends_at' => $stripeSubscription->trial_ends_at,
                'customer_id' => $stripeSubscription->stripe_id,
            ];

        } catch (Exception $e) {
            Log::error('Stripe subscription creation failed', [
                'user_id' => $user->id,
                'plan_id' => $plan->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function cancelSubscription(Subscription $subscription, string $reason = ''): bool
    {
        try {
            $cashierSubscription = CashierSubscription::find($subscription->stripe_id);

            if (! $cashierSubscription) {
                throw new Exception('Cashier subscription not found');
            }

            // Cancel immediately or at period end
            $cashierSubscription->cancel();

            return true;

        } catch (Exception $e) {
            Log::error('Stripe subscription cancellation failed', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function updateSubscription(Subscription $subscription, Plan $newPlan): array
    {
        try {
            $cashierSubscription = CashierSubscription::find($subscription->stripe_id);

            if (! $cashierSubscription) {
                throw new Exception('Cashier subscription not found');
            }

            $newPriceId = $this->getOrCreatePrice($newPlan);

            // Swap to new plan
            $cashierSubscription->swap($newPriceId);

            return [
                'provider_subscription_id' => $cashierSubscription->stripe_id,
                'status' => $cashierSubscription->stripe_status,
                'new_price_id' => $newPriceId,
            ];

        } catch (Exception $e) {
            Log::error('Stripe subscription update failed', [
                'subscription_id' => $subscription->id,
                'new_plan_id' => $newPlan->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function pauseSubscription(Subscription $subscription): bool
    {
        try {
            $cashierSubscription = CashierSubscription::find($subscription->stripe_id);

            if (! $cashierSubscription) {
                throw new Exception('Cashier subscription not found');
            }

            // Stripe doesn't have a native pause feature, so we cancel at period end
            $cashierSubscription->cancel();

            return true;

        } catch (Exception $e) {
            Log::error('Stripe subscription pause failed', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function resumeSubscription(Subscription $subscription): bool
    {
        try {
            $cashierSubscription = CashierSubscription::find($subscription->stripe_id);

            if (! $cashierSubscription) {
                throw new Exception('Cashier subscription not found');
            }

            // Resume cancelled subscription
            $cashierSubscription->resume();

            return true;

        } catch (Exception $e) {
            Log::error('Stripe subscription resume failed', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function getSubscriptionDetails(string $providerSubscriptionId): array
    {
        try {
            $stripeSubscription = \Stripe\Subscription::retrieve($providerSubscriptionId);

            return [
                'id' => $stripeSubscription->id,
                'status' => $stripeSubscription->status,
                'current_period_start' => $stripeSubscription->current_period_start,
                'current_period_end' => $stripeSubscription->current_period_end,
                'trial_start' => $stripeSubscription->trial_start,
                'trial_end' => $stripeSubscription->trial_end,
                'customer' => $stripeSubscription->customer,
                'items' => $stripeSubscription->items->data,
                'created' => $stripeSubscription->created,
                'ended_at' => $stripeSubscription->ended_at,
                'canceled_at' => $stripeSubscription->canceled_at,
            ];

        } catch (Exception $e) {
            Log::error('Stripe subscription details retrieval failed', [
                'subscription_id' => $providerSubscriptionId,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function createCheckoutSession(User $user, Plan $plan, array $options = []): array
    {
        try {
            $customer = $this->getOrCreateCustomer($user);
            $priceId = $this->getOrCreatePrice($plan);

            $sessionData = [
                'customer' => $customer->id,
                'payment_method_types' => ['card'],
                'line_items' => [[
                    'price' => $priceId,
                    'quantity' => 1,
                ]],
                'mode' => $plan->monthly_billing ? 'subscription' : 'payment',
                'success_url' => $options['success_url'] ?? $this->config['success_url'],
                'cancel_url' => $options['cancel_url'] ?? $this->config['cancel_url'],
                'metadata' => [
                    'user_id' => $user->id,
                    'plan_id' => $plan->id,
                ],
            ];

            if ($plan->monthly_billing && ! empty($options['trial_days'])) {
                $sessionData['subscription_data'] = [
                    'trial_period_days' => $options['trial_days'],
                ];
            }

            if (! empty($options['coupon'])) {
                $sessionData['discounts'] = [['coupon' => $options['coupon']]];
            }

            $session = Session::create($sessionData);

            return [
                'checkout_session_id' => $session->id,
                'checkout_url' => $session->url,
            ];

        } catch (Exception $e) {
            Log::error('Stripe checkout session creation failed', [
                'user_id' => $user->id,
                'plan_id' => $plan->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function createCustomerPortalSession(User $user): array
    {
        try {
            $customer = $this->getOrCreateCustomer($user);

            $session = \Stripe\BillingPortal\Session::create([
                'customer' => $customer->id,
                'return_url' => route('dashboard'),
            ]);

            return [
                'portal_session_id' => $session->id,
                'portal_url' => $session->url,
            ];

        } catch (Exception $e) {
            Log::error('Stripe customer portal session creation failed', [
                'user_id' => $user->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function processWebhook(Request $request): array
    {
        try {
            $payload = $request->getContent();
            $sigHeader = $request->header('Stripe-Signature');
            $event = \Stripe\Event::constructFrom(
                json_decode($payload, true)
            );

            $result = [
                'event_type' => $event->type,
                'processed' => false,
                'data' => [],
            ];

            switch ($event->type) {
                case 'invoice.payment_succeeded':
                    $result = $this->handleInvoicePaymentSucceeded($event);
                    break;
                case 'invoice.payment_failed':
                    $result = $this->handleInvoicePaymentFailed($event);
                    break;
                case 'customer.subscription.created':
                    $result = $this->handleSubscriptionCreated($event);
                    break;
                case 'customer.subscription.updated':
                    $result = $this->handleSubscriptionUpdated($event);
                    break;
                case 'customer.subscription.deleted':
                    $result = $this->handleSubscriptionDeleted($event);
                    break;
                default:
                    Log::info('Unhandled Stripe webhook event', ['event_type' => $event->type]);
            }

            return $result;

        } catch (Exception $e) {
            Log::error('Stripe webhook processing failed', [
                'error' => $e->getMessage(),
                'payload' => $request->getContent(),
            ]);
            throw $e;
        }
    }

    public function validateWebhook(Request $request): bool
    {
        try {
            $payload = $request->getContent();
            $sigHeader = $request->header('Stripe-Signature');

            if (! $sigHeader) {
                return false;
            }

            \Stripe\Webhook::constructEvent(
                $payload,
                $sigHeader,
                $this->config['webhook_secret']
            );

            return true;

        } catch (Exception $e) {
            Log::warning('Stripe webhook validation failed', [
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    public function getConfiguration(): array
    {
        return $this->config;
    }

    public function syncSubscriptionStatus(Subscription $subscription): array
    {
        return $this->getSubscriptionDetails($subscription->provider_subscription_id);
    }

    public function getPaymentMethodDetails(string $paymentMethodId): array
    {
        try {
            $paymentMethod = \Stripe\PaymentMethod::retrieve($paymentMethodId);

            return [
                'id' => $paymentMethod->id,
                'type' => $paymentMethod->type,
                'card' => [
                    'brand' => $paymentMethod->card->brand,
                    'last4' => $paymentMethod->card->last4,
                    'exp_month' => $paymentMethod->card->exp_month,
                    'exp_year' => $paymentMethod->card->exp_year,
                ],
                'created' => $paymentMethod->created,
            ];

        } catch (Exception $e) {
            Log::error('Stripe payment method details retrieval failed', [
                'payment_method_id' => $paymentMethodId,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function processRefund(string $paymentIntentId, float $amount, string $reason = ''): array
    {
        try {
            $refund = \Stripe\Refund::create([
                'payment_intent' => $paymentIntentId,
                'amount' => (int) ($amount * 100), // Convert to cents
                'reason' => $reason ?: 'requested_by_customer',
            ]);

            return [
                'refund_id' => $refund->id,
                'amount' => $refund->amount / 100,
                'status' => $refund->status,
                'created' => $refund->created,
            ];

        } catch (Exception $e) {
            Log::error('Stripe refund processing failed', [
                'payment_intent_id' => $paymentIntentId,
                'amount' => $amount,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function getTransactionHistory(User $user, array $filters = []): array
    {
        try {
            $customer = $this->getOrCreateCustomer($user);

            $charges = \Stripe\Charge::all([
                'customer' => $customer->id,
                'limit' => $filters['limit'] ?? 100,
                'starting_after' => $filters['starting_after'] ?? null,
            ]);

            $transactions = [];

            foreach ($charges->data as $charge) {
                $transactions[] = [
                    'id' => $charge->id,
                    'amount' => $charge->amount / 100,
                    'currency' => $charge->currency,
                    'status' => $charge->status,
                    'created' => $charge->created,
                    'description' => $charge->description,
                    'payment_method' => $charge->payment_method,
                ];
            }

            return $transactions;

        } catch (Exception $e) {
            Log::error('Stripe transaction history retrieval failed', [
                'user_id' => $user->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function calculateFees(float $amount): array
    {
        // Stripe fees: 2.9% + $0.30 (US), varies by country
        $fixedFee = 0.30; // $0.30
        $percentageFee = 2.9; // 2.9%

        $percentageAmount = ($amount * $percentageFee) / 100;
        $totalFee = $fixedFee + $percentageAmount;

        return [
            'fixed_fee' => $fixedFee,
            'percentage_fee' => $percentageAmount,
            'total_fee' => $totalFee,
            'net_amount' => $amount - $totalFee,
        ];
    }

    public function getSupportedCurrencies(): array
    {
        return [
            'USD', 'EUR', 'GBP', 'CAD', 'AUD', 'CHF', 'SEK', 'NOK', 'DKK',
            'PLN', 'CZK', 'HUF', 'RON', 'BGN', 'HRK', 'RUB', 'TRY', 'MXN',
            'BRL', 'ARS', 'CLP', 'COP', 'PEN', 'UYU', 'JPY', 'SGD', 'HKD',
            'INR', 'MYR', 'THB', 'PHP', 'TWD', 'KRW', 'CNY',
        ];
    }

    public function supportsRecurring(): bool
    {
        return true;
    }

    public function supportsOneTime(): bool
    {
        return true;
    }

    // Helper methods
    protected function getOrCreateCustomer(User $user): Customer
    {
        if ($user->stripe_id) {
            return Customer::retrieve($user->stripe_id);
        }

        $customer = Customer::create([
            'email' => $user->email,
            'name' => $user->name,
            'metadata' => [
                'user_id' => $user->id,
            ],
        ]);

        $user->update(['stripe_id' => $customer->id]);

        return $customer;
    }

    protected function getOrCreatePrice(Plan $plan): string
    {
        // Check if plan already has a Stripe price ID
        if (! empty($plan->details['stripe_price_id'])) {
            return $plan->details['stripe_price_id'];
        }

        // Create product if it doesn't exist
        $productId = $this->getOrCreateProduct($plan);

        // Create price
        $priceData = [
            'product' => $productId,
            'unit_amount' => intval($plan->price * 100), // Convert to cents
            'currency' => strtolower($this->config['currency']),
        ];

        if ($plan->monthly_billing) {
            $priceData['recurring'] = [
                'interval' => 'month',
                'interval_count' => 1,
            ];
        }

        $price = Price::create($priceData);

        // Update plan with new price ID
        $planDetails = $plan->details ?? [];
        $planDetails['stripe_price_id'] = $price->id;
        $plan->update(['details' => $planDetails]);

        return $price->id;
    }

    protected function getOrCreateProduct(Plan $plan): string
    {
        // Check if plan already has a Stripe product ID
        if (! empty($plan->details['stripe_product_id'])) {
            return $plan->details['stripe_product_id'];
        }

        // Create product
        $product = Product::create([
            'name' => $plan->name,
            'description' => $plan->description,
            'metadata' => [
                'plan_id' => $plan->id,
            ],
        ]);

        // Update plan with new product ID
        $planDetails = $plan->details ?? [];
        $planDetails['stripe_product_id'] = $product->id;
        $plan->update(['details' => $planDetails]);

        return $product->id;
    }

    // Webhook handlers
    protected function handleInvoicePaymentSucceeded($event): array
    {
        $invoice = $event->data->object;

        return [
            'event_type' => $event->type,
            'processed' => true,
            'data' => [
                'invoice_id' => $invoice->id,
                'subscription_id' => $invoice->subscription,
                'amount_paid' => $invoice->amount_paid / 100,
                'status' => 'paid',
            ],
        ];
    }

    protected function handleInvoicePaymentFailed($event): array
    {
        $invoice = $event->data->object;

        return [
            'event_type' => $event->type,
            'processed' => true,
            'data' => [
                'invoice_id' => $invoice->id,
                'subscription_id' => $invoice->subscription,
                'attempt_count' => $invoice->attempt_count,
                'status' => 'payment_failed',
            ],
        ];
    }

    protected function handleSubscriptionCreated($event): array
    {
        $subscription = $event->data->object;

        return [
            'event_type' => $event->type,
            'processed' => true,
            'data' => [
                'subscription_id' => $subscription->id,
                'customer_id' => $subscription->customer,
                'status' => $subscription->status,
            ],
        ];
    }

    protected function handleSubscriptionUpdated($event): array
    {
        $subscription = $event->data->object;

        return [
            'event_type' => $event->type,
            'processed' => true,
            'data' => [
                'subscription_id' => $subscription->id,
                'status' => $subscription->status,
                'current_period_start' => $subscription->current_period_start,
                'current_period_end' => $subscription->current_period_end,
            ],
        ];
    }

    protected function handleSubscriptionDeleted($event): array
    {
        $subscription = $event->data->object;

        return [
            'event_type' => $event->type,
            'processed' => true,
            'data' => [
                'subscription_id' => $subscription->id,
                'status' => 'canceled',
                'ended_at' => $subscription->ended_at,
            ],
        ];
    }

    // Additional interface methods
    public function getSubscriptionMetadata(Subscription $subscription): array
    {
        return $subscription->provider_data['metadata'] ?? [];
    }

    public function updateSubscriptionMetadata(Subscription $subscription, array $metadata): bool
    {
        try {
            $stripeSubscription = \Stripe\Subscription::retrieve($subscription->provider_subscription_id);
            $stripeSubscription->metadata = $metadata;
            $stripeSubscription->save();

            return true;
        } catch (Exception $e) {
            Log::error('Failed to update subscription metadata', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    public function startTrial(Subscription $subscription, int $trialDays): bool
    {
        try {
            $stripeSubscription = \Stripe\Subscription::retrieve($subscription->provider_subscription_id);
            $stripeSubscription->trial_end = now()->addDays($trialDays)->timestamp;
            $stripeSubscription->save();

            return true;
        } catch (Exception $e) {
            Log::error('Failed to start trial', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    public function applyCoupon(Subscription $subscription, string $couponCode): array
    {
        try {
            $stripeSubscription = \Stripe\Subscription::retrieve($subscription->provider_subscription_id);
            $stripeSubscription->discount = null; // Remove existing discount
            $stripeSubscription->save();

            // Apply new coupon
            $stripeSubscription = \Stripe\Subscription::retrieve($subscription->provider_subscription_id);
            $coupon = \Stripe\Coupon::retrieve($couponCode);

            return [
                'coupon_id' => $coupon->id,
                'amount_off' => $coupon->amount_off ?? null,
                'percent_off' => $coupon->percent_off ?? null,
            ];

        } catch (Exception $e) {
            Log::error('Failed to apply coupon', [
                'subscription_id' => $subscription->id,
                'coupon_code' => $couponCode,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function removeCoupon(Subscription $subscription): bool
    {
        try {
            $stripeSubscription = \Stripe\Subscription::retrieve($subscription->provider_subscription_id);
            $stripeSubscription->deleteDiscount();

            return true;
        } catch (Exception $e) {
            Log::error('Failed to remove coupon', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    public function getUpcomingInvoice(Subscription $subscription): array
    {
        try {
            $invoice = \Stripe\Invoice::upcoming(['subscription' => $subscription->provider_subscription_id]);

            return [
                'amount_due' => $invoice->amount_due / 100,
                'currency' => $invoice->currency,
                'period_start' => $invoice->period_start,
                'period_end' => $invoice->period_end,
                'lines' => $invoice->lines->data,
            ];

        } catch (Exception $e) {
            Log::error('Failed to get upcoming invoice', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function retryFailedPayment(Subscription $subscription): array
    {
        try {
            $invoice = \Stripe\Invoice::retrieve(['subscription' => $subscription->provider_subscription_id]);
            $invoice->pay();

            return [
                'invoice_id' => $invoice->id,
                'status' => $invoice->status,
                'amount_paid' => $invoice->amount_paid / 100,
            ];

        } catch (Exception $e) {
            Log::error('Failed to retry payment', [
                'subscription_id' => $subscription->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    public function canModifySubscription(Subscription $subscription): bool
    {
        try {
            $stripeSubscription = \Stripe\Subscription::retrieve($subscription->provider_subscription_id);

            return in_array($stripeSubscription->status, ['active', 'trialing']);
        } catch (Exception $e) {
            return false;
        }
    }

    public function getCancellationTerms(Subscription $subscription): array
    {
        return [
            'immediate_cancellation' => true,
            'refund_policy' => 'pro_rata',
            'cancellation_effective' => 'immediately',
            'billing_cycle_proration' => true,
        ];
    }

    public function exportSubscriptionData(Subscription $subscription): array
    {
        return [
            'provider' => 'stripe',
            'provider_subscription_id' => $subscription->provider_subscription_id,
            'stripe_id' => $subscription->stripe_id,
            'status' => $subscription->stripe_status,
            'data' => $subscription->provider_data,
        ];
    }

    public function importSubscriptionData(User $user, array $subscriptionData): array
    {
        // This would be used for migrating subscriptions to Stripe
        // Implementation depends on specific requirements
        throw new Exception('Import to Stripe not implemented');
    }
}
