<?php

namespace App\Services;

use App\Exceptions\PaystackException;
use App\Models\Association;
use App\Models\Payment;
use App\Models\PaymentProof;
use App\Models\PlatformBankAccount;
use App\Models\PlatformSubscription;
use App\Models\SubscriptionPlan;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class SubscriptionService
{
    public function __construct(
        private readonly PaystackService $paystackService,
        private readonly NotificationService $notificationService,
    ) {}

    /**
     * Get all active subscription plans, ordered for display.
     */
    public function getAvailablePlans(): Collection
    {
        return SubscriptionPlan::active()->ordered()->get();
    }

    /**
     * Initiate a new subscription for an association.
     *
     * @return array{subscription: PlatformSubscription, payment: Payment, authorization_url?: string, bank_accounts?: Collection}
     */
    public function initiateSubscription(
        Association $association,
        SubscriptionPlan $plan,
        string $paymentMethod,
        User $user,
    ): array {
        return DB::transaction(function () use ($association, $plan, $paymentMethod, $user) {
            $reference = 'SUB-' . strtoupper(Str::random(16));

            // Cancel any existing pending subscriptions
            PlatformSubscription::where('association_id', $association->id)
                ->where('status', 'pending')
                ->update(['status' => 'cancelled']);

            // Create the subscription record
            $subscription = PlatformSubscription::create([
                'association_id' => $association->id,
                'subscription_plan_id' => $plan->id,
                'plan' => $plan->slug,
                'amount' => $plan->price,
                'billing_cycle' => $plan->billing_cycle,
                'status' => 'pending',
                'payment_method' => $paymentMethod,
                'member_limit' => $plan->member_limit,
                'storage_limit_mb' => $plan->storage_limit_mb,
                'custom_branding' => $plan->custom_branding,
                'api_access' => $plan->api_access,
                'available_modules' => $plan->allowed_modules,
                'payment_reference' => $reference,
                'created_by' => $user->id,
            ]);

            // Create corresponding payment record
            $payment = Payment::create([
                'association_id' => $association->id,
                'user_id' => $user->id,
                'payable_type' => PlatformSubscription::class,
                'payable_id' => $subscription->id,
                'amount' => $plan->price,
                'currency' => 'NGN',
                'gateway' => $paymentMethod === 'card' ? 'paystack' : 'manual',
                'reference' => $reference,
                'status' => 'pending',
                'payment_method' => $paymentMethod,
                'metadata' => [
                    'plan_id' => $plan->id,
                    'plan_name' => $plan->name,
                    'billing_cycle' => $plan->billing_cycle,
                ],
            ]);

            $result = [
                'subscription' => $subscription,
                'payment' => $payment,
            ];

            if ($paymentMethod === 'card') {
                // Initialize Paystack transaction
                $email = $user->email ?? $association->email;
                $amountInKobo = (int) ($plan->price * 100);

                $callbackUrl = url("/association/{$association->slug}/subscription/callback?reference={$reference}");

                $paystackData = $this->paystackService->initializeTransaction(
                    email: $email,
                    amountInKobo: $amountInKobo,
                    reference: $reference,
                    metadata: [
                        'subscription_id' => $subscription->id,
                        'association_id' => $association->id,
                        'plan_name' => $plan->name,
                    ],
                    callbackUrl: $callbackUrl,
                );

                $payment->update([
                    'access_code' => $paystackData['access_code'],
                ]);

                $result['authorization_url'] = $paystackData['authorization_url'];
            } else {
                // Bank transfer — return bank accounts for display
                $result['bank_accounts'] = PlatformBankAccount::active()->ordered()->get();
            }

            return $result;
        });
    }

    /**
     * Activate a subscription after successful payment.
     */
    public function activateSubscription(PlatformSubscription $subscription): void
    {
        $now = now();
        $endsAt = $this->calculateEndDate($now, $subscription->billing_cycle);

        $subscription->update([
            'status' => 'active',
            'starts_at' => $now,
            'ends_at' => $endsAt,
            'next_billing_date' => $endsAt,
            'last_payment_at' => $now,
        ]);

        // Sync limits and modules to the association
        $this->syncAssociationFromSubscription($subscription);

        // Send notification
        $this->notificationService->sendSubscriptionActivated($subscription->association, $subscription);
    }

    /**
     * Start a free trial for an association.
     */
    public function startTrial(Association $association, SubscriptionPlan $plan, User $user): PlatformSubscription
    {
        $now = now();
        $trialEndsAt = $now->copy()->addDays($plan->trial_duration_days);

        $subscription = PlatformSubscription::create([
            'association_id' => $association->id,
            'subscription_plan_id' => $plan->id,
            'plan' => $plan->slug,
            'amount' => 0,
            'billing_cycle' => $plan->billing_cycle,
            'status' => 'trial',
            'starts_at' => $now,
            'ends_at' => $trialEndsAt,
            'trial_ends_at' => $trialEndsAt,
            'member_limit' => $plan->member_limit,
            'storage_limit_mb' => $plan->storage_limit_mb,
            'custom_branding' => $plan->custom_branding,
            'api_access' => $plan->api_access,
            'available_modules' => $plan->allowed_modules,
            'created_by' => $user->id,
        ]);

        $this->syncAssociationFromSubscription($subscription);

        return $subscription;
    }

    /**
     * Handle Paystack webhook payload.
     */
    public function handlePaystackWebhook(array $payload): void
    {
        $event = $payload['event'] ?? '';
        $data = $payload['data'] ?? [];
        $reference = $data['reference'] ?? null;

        if (!$reference) {
            Log::warning('Paystack webhook: Missing reference', $payload);
            return;
        }

        $payment = Payment::where('reference', $reference)->first();

        if (!$payment) {
            Log::warning('Paystack webhook: Payment not found', ['reference' => $reference]);
            return;
        }

        if ($event === 'charge.success' && ($data['status'] ?? '') === 'success') {
            $payment->update([
                'status' => 'completed',
                'authorization_code' => $data['authorization']['authorization_code'] ?? null,
                'paid_at' => now(),
                'metadata' => array_merge($payment->metadata ?? [], [
                    'gateway_response' => $data['gateway_response'] ?? null,
                    'channel' => $data['channel'] ?? null,
                    'ip_address' => $data['ip_address'] ?? null,
                ]),
            ]);

            // Activate the linked subscription
            $subscription = $payment->payable;

            if ($subscription instanceof PlatformSubscription) {
                $this->activateSubscription($subscription);
            }

            // Send payment receipt notification
            if ($payment->user) {
                $this->notificationService->sendPaymentReceipt($payment->user, $payment);
            }

            Log::info('Paystack webhook: Payment completed', ['reference' => $reference]);
        } elseif ($event === 'charge.failed') {
            $payment->update([
                'status' => 'failed',
                'failure_reason' => $data['gateway_response'] ?? 'Payment failed',
            ]);

            if ($payment->payable instanceof PlatformSubscription) {
                $payment->payable->update(['status' => 'cancelled']);
            }

            // Send payment failed notification
            if ($payment->user) {
                $this->notificationService->sendPaymentFailed($payment->user, $payment, $data['gateway_response'] ?? 'Payment failed');
            }

            Log::info('Paystack webhook: Payment failed', ['reference' => $reference]);
        }
    }

    /**
     * Verify a Paystack transaction by reference (used on callback redirect).
     *
     * @return array{success: bool, message: string}
     */
    public function verifyPaystackPayment(string $reference): array
    {
        $payment = Payment::where('reference', $reference)->first();

        if (!$payment) {
            return ['success' => false, 'message' => 'Payment record not found.'];
        }

        // Already completed (likely via webhook)
        if ($payment->status === 'completed') {
            return ['success' => true, 'message' => 'Payment already verified and activated.'];
        }

        try {
            $data = $this->paystackService->verifyTransaction($reference);

            if (($data['status'] ?? '') === 'success') {
                $payment->update([
                    'status' => 'completed',
                    'authorization_code' => $data['authorization']['authorization_code'] ?? null,
                    'paid_at' => now(),
                    'metadata' => array_merge($payment->metadata ?? [], [
                        'gateway_response' => $data['gateway_response'] ?? null,
                        'verified_via' => 'callback',
                    ]),
                ]);

                $subscription = $payment->payable;

                if ($subscription instanceof PlatformSubscription && $subscription->status !== 'active') {
                    $this->activateSubscription($subscription);
                }

                return ['success' => true, 'message' => 'Payment verified and subscription activated.'];
            }

            return ['success' => false, 'message' => $data['gateway_response'] ?? 'Payment was not successful.'];
        } catch (PaystackException $e) {
            Log::error('Paystack verification failed', ['reference' => $reference, 'error' => $e->getMessage()]);
            return ['success' => false, 'message' => 'Unable to verify payment. Please try again or contact support.'];
        }
    }

    /**
     * Super admin verifies a manual bank transfer payment.
     */
    public function verifyManualPayment(PlatformSubscription $subscription, User $verifier): void
    {
        DB::transaction(function () use ($subscription, $verifier) {
            // Find and verify the linked payment + proof
            $payment = Payment::where('payable_type', PlatformSubscription::class)
                ->where('payable_id', $subscription->id)
                ->where('status', 'pending')
                ->first();

            if ($payment && $payment->manual_proof_id) {
                $proof = PaymentProof::find($payment->manual_proof_id);
                $proof?->verify($verifier->id);

                $payment->update([
                    'status' => 'completed',
                    'paid_at' => now(),
                ]);
            }

            $this->activateSubscription($subscription);
        });

        $this->notificationService->sendManualPaymentVerified($subscription);
    }

    /**
     * Super admin rejects a manual bank transfer payment.
     */
    public function rejectManualPayment(PlatformSubscription $subscription, string $reason): void
    {
        DB::transaction(function () use ($subscription, $reason) {
            $payment = Payment::where('payable_type', PlatformSubscription::class)
                ->where('payable_id', $subscription->id)
                ->where('status', 'pending')
                ->first();

            if ($payment && $payment->manual_proof_id) {
                $proof = PaymentProof::find($payment->manual_proof_id);
                $proof?->reject($reason);

                $payment->update([
                    'status' => 'failed',
                    'failure_reason' => $reason,
                ]);
            }

            $subscription->update(['status' => 'cancelled']);
        });

        $this->notificationService->sendManualPaymentRejected($subscription, $reason);
    }

    /**
     * Cancel an active subscription.
     */
    public function cancelSubscription(PlatformSubscription $subscription, User $canceller, string $reason): void
    {
        $subscription->update([
            'status' => 'cancelled',
            'cancelled_by' => $canceller->id,
            'cancelled_at' => now(),
            'cancellation_reason' => $reason,
            'auto_renew' => false,
        ]);

        $association = $subscription->association;
        $association->update([
            'subscription_status' => 'cancelled',
        ]);
    }

    /**
     * Find and expire overdue subscriptions. Called by scheduled command.
     */
    public function checkExpiredSubscriptions(): int
    {
        $expired = PlatformSubscription::where('status', 'active')
            ->where('ends_at', '<', now())
            ->get();

        $trialExpired = PlatformSubscription::where('status', 'trial')
            ->where('trial_ends_at', '<', now())
            ->get();

        $count = 0;

        foreach ($expired->merge($trialExpired) as $subscription) {
            $subscription->update(['status' => 'expired']);

            $subscription->association->update([
                'subscription_status' => 'expired',
                'enabled_modules' => [],
            ]);

            $this->notificationService->sendSubscriptionExpired($subscription->association, $subscription);

            $count++;

            Log::info('Subscription expired', [
                'subscription_id' => $subscription->id,
                'association_id' => $subscription->association_id,
            ]);
        }

        return $count;
    }

    /**
     * Get subscriptions expiring within a given number of days.
     */
    public function getExpiringSubscriptions(int $daysFromNow): Collection
    {
        $targetDate = now()->addDays($daysFromNow)->startOfDay();
        $targetDateEnd = $targetDate->copy()->endOfDay();

        return PlatformSubscription::where('status', 'active')
            ->whereBetween('ends_at', [$targetDate, $targetDateEnd])
            ->with('association')
            ->get();
    }

    // ── Private Helpers ──────────────────────────────────────────

    /**
     * Sync subscription limits and modules to the association record.
     */
    private function syncAssociationFromSubscription(PlatformSubscription $subscription): void
    {
        $association = $subscription->association;

        $association->update([
            'subscription_status' => $subscription->status === 'trial' ? 'trial' : 'active',
            'subscription_plan_id' => $subscription->subscription_plan_id,
            'subscription_amount' => $subscription->amount,
            'subscription_starts_at' => $subscription->starts_at,
            'subscription_ends_at' => $subscription->ends_at ?? $subscription->trial_ends_at,
            'trial_ends_at' => $subscription->trial_ends_at,
            'max_members' => $subscription->member_limit,
            'storage_limit_mb' => $subscription->storage_limit_mb,
            'enabled_modules' => $subscription->available_modules ?? [],
        ]);
    }

    /**
     * Calculate the subscription end date from a start date and billing cycle.
     */
    private function calculateEndDate(\DateTimeInterface $startDate, string $billingCycle): \DateTimeInterface
    {
        $start = \Carbon\Carbon::parse($startDate);

        return match ($billingCycle) {
            'monthly' => $start->addMonth(),
            'quarterly' => $start->addMonths(3),
            'annually' => $start->addYear(),
            default => $start->addMonth(),
        };
    }
}
