<?php

namespace App\Models;

use App\Services\Payments\PaymentOrchestrator;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;

class Subscription extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'type',
        'stripe_id',
        'stripe_status',
        'stripe_price',
        'quantity',
        'trial_ends_at',
        'ends_at',
        'provider',
        'provider_subscription_id',
        'unified_status',
        'cancelled_at',
        'cancellation_reason',
        'paused_at',
        'resumed_at',
        'migration_batch_id',
        'is_migrated',
        'legacy_data',
        'synced_at',
        'provider_data',
        'last_provider_sync',
        'plan_id',
        'starts_at',
        'status',
        'updated_at',
        'polar_subscription',
        'metadata',
        'migration_source',
        'migration_date',
        'migration_reason',
        'created_at',
        'activated_at',
        'checkout_id',
        'customer_id',
        'polar_checkout',
        'order_id',
        'polar_order',
        'order_created_at',
        'order_paid_at',
        'provider_checkout_id',
        'resume_reason',
        'trial_ended_at',
        'trial_converted_to',
        'subscription_id_fetched_at',
        'polar_dates',
        'customer_state_changed_at',
        'polar_subscription_data',
        'customer_metadata',
        'trial_will_end_sent_at',
        'pause_reason',
    ];

    protected $casts = [
        'is_migrated' => 'boolean',
        'legacy_data' => 'array',
        'provider_data' => 'array',
        'metadata' => 'array',
        'trial_ends_at' => 'datetime',
        'ends_at' => 'datetime',
        'cancelled_at' => 'datetime',
        'paused_at' => 'datetime',
        'resumed_at' => 'datetime',
        'synced_at' => 'datetime',
        'last_provider_sync' => 'datetime',
        'starts_at' => 'datetime',
    ];

    /**
     * Accessor for total coupon discount
     */
    protected function getTotalCouponDiscountAttribute(): float
    {
        // Use preloaded sum if available, otherwise calculate it
        if (array_key_exists('total_coupon_discount', $this->attributes)) {
            return (float) $this->attributes['total_coupon_discount'];
        }

        return $this->couponUsages()->sum('discount_amount');
    }

    protected $dates = [
        'trial_ends_at',
        'ends_at',
        'cancelled_at',
        'paused_at',
        'resumed_at',
        'synced_at',
        'last_provider_sync',
        'starts_at',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function plan()
    {
        return $this->belongsTo(Plan::class);
    }

    public function trialExtensions()
    {
        return $this->hasMany(TrialExtension::class);
    }

    public function subscriptionChanges()
    {
        return $this->hasMany(SubscriptionChange::class);
    }

    public function couponUsages()
    {
        return $this->hasMany(CouponUsage::class);
    }

    /**
     * Check if subscription is active
     */
    public function isActive(): bool
    {
        return in_array($this->status, ['active', 'trialing']) &&
               (! $this->ends_at || $this->ends_at->isFuture());
    }

    /**
     * Check if subscription is on trial
     */
    public function isOnTrial(): bool
    {
        return $this->status === 'trialing' &&
               $this->trial_ends_at &&
               $this->trial_ends_at->isFuture();
    }

    /**
     * Check if subscription is cancelled
     */
    public function isCancelled(): bool
    {
        return $this->status === 'cancelled' ||
               ($this->ends_at && $this->ends_at->isPast());
    }

    /**
     * Check if subscription supports recurring payments
     */
    public function isRecurring(): bool
    {
        return $this->plan && $this->plan->monthly_billing;
    }

    /**
     * Get the display name for the provider
     */
    public function getProviderDisplayName(): string
    {
        $displayNames = [
            'stripe' => 'Stripe',
            'lemon_squeezy' => 'Lemon Squeezy',
            'polar' => 'Polar.sh',
            'oxapay' => 'OxaPay',
            'crypto' => 'Crypto',
            'activation_key' => 'Activation Key',
        ];

        return $displayNames[$this->provider] ?? ucfirst($this->provider);
    }

    /**
     * Get provider-specific data
     */
    public function getProviderData(?string $key = null, $default = null)
    {
        if ($key) {
            return data_get($this->provider_data, $key, $default);
        }

        return $this->provider_data;
    }

    /**
     * Set provider-specific data
     */
    public function setProviderData(string $key, $value): void
    {
        $data = $this->provider_data ?? [];
        data_set($data, $key, $value);
        $this->provider_data = $data;
    }

    /**
     * Sync subscription status with provider
     */
    public function syncWithProvider(): bool
    {
        try {
            // For Polar provider, check if we need to fetch subscription ID first
            if ($this->provider === 'polar' && empty($this->provider_subscription_id) && ! empty($this->user->polar_cust_id)) {
                $this->fetchPolarSubscriptionId();
            }

            $orchestrator = app(PaymentOrchestrator::class);
            $result = $orchestrator->syncSubscriptionStatus($this);

            $this->update([
                'status' => $result['status'] ?? $this->status,
                'provider_data' => array_merge($this->provider_data ?? [], $result),
                'last_provider_sync' => now(),
            ]);

            Log::info('Subscription synced with provider', [
                'subscription_id' => $this->id,
                'provider' => $this->provider,
                'status' => $result['status'] ?? 'unknown',
            ]);

            return true;

        } catch (\Exception $e) {
            Log::error('Failed to sync subscription with provider', [
                'subscription_id' => $this->id,
                'provider' => $this->provider,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Fetch Polar subscription ID using customer ID if missing
     */
    protected function fetchPolarSubscriptionId(): void
    {
        if ($this->provider !== 'polar' || empty($this->user->polar_cust_id)) {
            return;
        }

        try {
            $polarProvider = app(\App\Services\Payments\Providers\PolarProvider::class);

            // Get active subscriptions for this customer
            $response = $polarProvider->makeAuthenticatedRequest('GET', '/subscriptions', [
                'customer_id' => $this->user->polar_cust_id,
                'status' => 'active',
                'limit' => 10,
            ]);

            if ($response->successful()) {
                $data = $response->json();
                $subscriptions = $data['items'] ?? [];

                if (! empty($subscriptions)) {
                    // Find the subscription that matches our plan or take the most recent active one
                    $matchingSubscription = null;

                    foreach ($subscriptions as $sub) {
                        // Check if this subscription matches our plan (via metadata or other criteria)
                        if (isset($sub['metadata']['plan_id']) && $sub['metadata']['plan_id'] == $this->plan_id) {
                            $matchingSubscription = $sub;
                            break;
                        }
                    }

                    // If no exact match, take the most recent active subscription
                    if (! $matchingSubscription && ! empty($subscriptions)) {
                        $matchingSubscription = $subscriptions[0];
                    }

                    if ($matchingSubscription) {
                        // Parse dates from Polar response
                        $startsAt = null;
                        $endsAt = null;
                        $cancelledAt = null;

                        // Handle current_period_start
                        if (isset($matchingSubscription['current_period_start'])) {
                            $startsAt = \Carbon\Carbon::parse($matchingSubscription['current_period_start']);
                        }

                        // Handle current_period_end (renewal date)
                        if (isset($matchingSubscription['current_period_end'])) {
                            $endsAt = \Carbon\Carbon::parse($matchingSubscription['current_period_end']);
                        }
                        // Handle ends_at (cancellation/expiry date)
                        elseif (isset($matchingSubscription['ends_at'])) {
                            $endsAt = \Carbon\Carbon::parse($matchingSubscription['ends_at']);
                        }
                        // Handle expires_at (expiry date)
                        elseif (isset($matchingSubscription['expires_at'])) {
                            $endsAt = \Carbon\Carbon::parse($matchingSubscription['expires_at']);
                        }

                        // Handle cancelled_at
                        if (isset($matchingSubscription['cancelled_at'])) {
                            $cancelledAt = \Carbon\Carbon::parse($matchingSubscription['cancelled_at']);
                        }

                        $this->update([
                            'provider_subscription_id' => $matchingSubscription['id'],
                            'status' => $matchingSubscription['status'],
                            'starts_at' => $startsAt,
                            'ends_at' => $endsAt,
                            'cancelled_at' => $cancelledAt,
                            'provider_data' => array_merge($this->provider_data ?? [], [
                                'polar_subscription' => $matchingSubscription,
                                'subscription_id_fetched_at' => now()->toISOString(),
                                'polar_dates' => [
                                    'current_period_start' => $matchingSubscription['current_period_start'] ?? null,
                                    'current_period_end' => $matchingSubscription['current_period_end'] ?? null,
                                    'ends_at' => $matchingSubscription['ends_at'] ?? null,
                                    'expires_at' => $matchingSubscription['expires_at'] ?? null,
                                    'cancelled_at' => $matchingSubscription['cancelled_at'] ?? null,
                                ],
                            ]),
                        ]);

                        Log::info('Polar subscription ID fetched and updated', [
                            'subscription_id' => $this->id,
                            'polar_subscription_id' => $matchingSubscription['id'],
                            'customer_id' => $this->user->polar_cust_id,
                            'starts_at' => $startsAt?->toISOString(),
                            'ends_at' => $endsAt?->toISOString(),
                            'cancelled_at' => $cancelledAt?->toISOString(),
                        ]);
                    }
                }
            } else {
                Log::warning('Failed to fetch Polar subscriptions for sync', [
                    'customer_id' => $this->user->polar_cust_id,
                    'status_code' => $response->status(),
                    'response' => $response->json(),
                ]);
            }

        } catch (\Exception $e) {
            Log::error('Error fetching Polar subscription ID', [
                'subscription_id' => $this->id,
                'customer_id' => $this->user->polar_cust_id ?? 'none',
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Cancel the subscription
     */
    public function cancel(string $reason = ''): bool
    {
        try {
            $orchestrator = app(PaymentOrchestrator::class);
            $result = $orchestrator->cancelSubscription($this, $reason);

            if ($result) {
                $this->update([
                    'status' => 'cancelled',
                    'cancelled_at' => now(),
                    'cancellation_reason' => $reason,
                ]);
            }

            return $result;

        } catch (\Exception $e) {
            Log::error('Failed to cancel subscription', [
                'subscription_id' => $this->id,
                'provider' => $this->provider,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Update subscription plan
     */
    public function updatePlan(Plan $newPlan): bool
    {
        try {
            $orchestrator = app(PaymentOrchestrator::class);
            $result = $orchestrator->updateSubscription($this, $newPlan);

            if ($result['success']) {
                $this->update([
                    'plan_id' => $newPlan->id,
                    'updated_at' => now(),
                ]);
            }

            return $result['success'];

        } catch (\Exception $e) {
            Log::error('Failed to update subscription plan', [
                'subscription_id' => $this->id,
                'provider' => $this->provider,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Get subscription metadata
     */
    public function getMetadata(?string $key = null, $default = null)
    {
        if ($key) {
            return data_get($this->metadata, $key, $default);
        }

        return $this->metadata;
    }

    /**
     * Set subscription metadata
     */
    public function setMetadata(string $key, $value): void
    {
        $data = $this->metadata ?? [];
        data_set($data, $key, $value);
        $this->metadata = $data;
    }

    /**
     * Scope: Active subscriptions
     */
    public function scopeActive($query)
    {
        return $query->where('status', 'active')
            ->where(function ($q) {
                $q->whereNull('ends_at')->orWhere('ends_at', '>', now());
            });
    }

    /**
     * Scope: Cancelled subscriptions
     */
    public function scopeCancelled($query)
    {
        return $query->where('status', 'cancelled')
            ->where(function ($q) {
                $q->whereNull('ends_at')->orWhere('ends_at', '<=', now());
            });
    }

    /**
     * Scope: On trial subscriptions
     */
    public function scopeOnTrial($query)
    {
        return $query->where('status', 'trialing')
            ->where('trial_ends_at', '>', now());
    }

    /**
     * Scope: By provider
     */
    public function scopeByProvider($query, string $provider)
    {
        return $query->where('provider', $provider);
    }

    /**
     * Scope: By user
     */
    public function scopeByUser($query, $userId)
    {
        return $query->where('user_id', $userId);
    }

    /**
     * Scope: With total coupon discount
     */
    public function scopeWithTotalCouponDiscount($query)
    {
        return $query->withSum('couponUsages as total_coupon_discount', 'discount_amount');
    }

    /**
     * Extend trial period
     */
    public function extendTrial(int $days, string $reason = '', string $extensionType = 'manual', ?User $grantedBy = null): TrialExtension
    {
        $originalEnd = $this->trial_ends_at;
        $newEnd = $originalEnd ? $originalEnd->copy()->addDays($days) : now()->addDays($days);

        $extension = $this->trialExtensions()->create([
            'user_id' => $this->user_id,
            'extension_days' => $days,
            'reason' => $reason,
            'extension_type' => $extensionType,
            'original_trial_ends_at' => $originalEnd,
            'new_trial_ends_at' => $newEnd,
            'granted_at' => now(),
            'granted_by_admin_id' => $grantedBy?->id,
        ]);

        // Update the subscription's trial end date
        $this->update(['trial_ends_at' => $newEnd]);

        // Record the change
        SubscriptionChange::createRecord(
            $this,
            'pause',
            "Trial extended by {$days} days",
            ['trial_ends_at' => $originalEnd?->format('Y-m-d H:i:s')],
            ['trial_ends_at' => $newEnd->format('Y-m-d H:i:s')],
            $reason
        );

        return $extension;
    }

    /**
     * Get total trial extensions granted
     */
    public function getTotalTrialExtensionsDays(): int
    {
        return $this->trialExtensions()->sum('extension_days');
    }

    /**
     * Get latest trial extension
     */
    public function getLatestTrialExtension(): ?TrialExtension
    {
        return $this->trialExtensions()->latest()->first();
    }

    /**
     * Check if trial was extended
     */
    public function hasExtendedTrial(): bool
    {
        return $this->trialExtensions()->exists();
    }

    /**
     * Apply coupon to subscription
     */
    public function applyCoupon(Coupon $coupon, float $amount): CouponUsage
    {
        if (! $coupon->isValid($this->user)) {
            throw new \Exception('Coupon is not valid for this user');
        }

        return $coupon->applyToSubscription($this, $amount);
    }

    /**
     * Get total discount from coupons
     */
    public function getTotalCouponDiscount(): float
    {
        return $this->couponUsages()->sum('discount_amount');
    }

    /**
     * Record subscription change
     */
    public function recordChange(
        string $changeType,
        string $description,
        ?array $oldValues = null,
        ?array $newValues = null,
        ?string $reason = null
    ): SubscriptionChange {
        return SubscriptionChange::createRecord(
            $this,
            $changeType,
            $description,
            $oldValues,
            $newValues,
            $reason
        );
    }

    /**
     * Get pending changes
     */
    public function getPendingChanges(): \Illuminate\Database\Eloquent\Collection
    {
        return $this->subscriptionChanges()->pending()->get();
    }

    /**
     * Process pending changes
     */
    public function processPendingChanges(): int
    {
        $pending = $this->getPendingChanges();
        $processedCount = 0;

        foreach ($pending as $change) {
            $change->markAsProcessed();
            $processedCount++;
        }

        return $processedCount;
    }

    /**
     * Calculate Monthly Recurring Revenue (MRR) for this subscription
     */
    public function calculateMRR(): float
    {
        // Only active and trialing subscriptions contribute to MRR
        if (! in_array($this->status, ['active', 'trialing'])) {
            return 0;
        }

        // Check if subscription has ended
        if ($this->ends_at && $this->ends_at->isPast()) {
            return 0;
        }

        // Get the plan's MRR calculation
        if ($this->plan) {
            return $this->plan->calculateMRR();
        }

        // Fallback: try to calculate from legacy data or provider data
        $price = $this->getLegacyPrice();
        if ($price > 0) {
            $cycleDays = $this->getLegacyBillingCycleDays();

            return ($price / $cycleDays) * 30;
        }

        return 0;
    }

    /**
     * Get price from legacy data or provider data
     */
    private function getLegacyPrice(): float
    {
        // Try to get price from plan first
        if ($this->plan && $this->plan->price) {
            return (float) $this->plan->price;
        }

        // Try provider data
        if ($this->provider_data && isset($this->provider_data['plan_details']['price'])) {
            return (float) $this->provider_data['plan_details']['price'];
        }

        // Try legacy stripe_price field
        if ($this->stripe_price) {
            // This would need additional logic to get price from Stripe
            // For now, return 0 as we can't easily get the amount
            return 0;
        }

        return 0;
    }

    /**
     * Get billing cycle days from legacy data
     */
    private function getLegacyBillingCycleDays(): int
    {
        // Try to get from plan first
        if ($this->plan && $this->plan->billing_cycle_days) {
            return (int) $this->plan->billing_cycle_days;
        }

        // Try provider data
        if ($this->provider_data && isset($this->provider_data['plan_details']['billing_cycle_days'])) {
            return (int) $this->provider_data['plan_details']['billing_cycle_days'];
        }

        // Fallback to legacy monthly_billing
        return $this->plan && $this->plan->monthly_billing ? 30 : 365;
    }
}
