<?php

declare(strict_types=1);

namespace App\Services\Billing;

use App\Events\ServiceSuspended;
use App\Events\ServiceSuspending;
use App\Events\ServiceTerminated;
use App\Events\ServiceTerminating;
use App\Events\ServiceUnsuspended;
use App\Events\ServiceUnsuspending;
use App\Models\Service;
use App\Models\User;
use App\Notifications\ServiceSuspendedNotification;
use App\Notifications\ServiceSuspensionWarningNotification;
use App\Notifications\ServiceTerminatedNotification;
use App\Notifications\ServiceTerminationWarningNotification;
use App\Services\Provisioning\ProvisioningFactory;
use Illuminate\Support\Facades\Log;
use Laravel\Cashier\Subscription;

class DunningService
{
    public function __construct(
        private ProvisioningFactory $provisioningFactory,
    ) {}

    /**
     * Suspend services for subscriptions that are past due beyond the grace period.
     * Uses per-plan and per-customer threshold overrides.
     */
    public function suspendOverdueSubscriptions(): int
    {
        $subscriptions = Subscription::query()
            ->where('stripe_status', 'past_due')
            ->with('user')
            ->get();

        $count = 0;

        foreach ($subscriptions as $subscription) {
            $effectiveDays = $this->getEffectiveSuspendDays($subscription);

            if ($effectiveDays === null) {
                // Auto-suspend disabled for this plan
                continue;
            }

            $cutoff = now()->subDays($effectiveDays);

            if ($subscription->updated_at->lte($cutoff)) {
                $this->suspendServicesForSubscription($subscription);
                $count++;
            }
        }

        if ($count > 0) {
            Log::info("Dunning: suspended services for {$count} overdue subscriptions.");
        }

        return $count;
    }

    /**
     * Terminate services for subscriptions that have been suspended too long.
     * Uses per-plan and per-customer threshold overrides.
     */
    public function terminateLongSuspendedSubscriptions(): int
    {
        $services = Service::query()
            ->where('status', 'suspended')
            ->whereNotNull('suspended_at')
            ->with(['user', 'plan'])
            ->get();

        $count = 0;

        foreach ($services as $service) {
            $effectiveDays = $this->getEffectiveTerminateDays($service);

            if ($effectiveDays === null) {
                // Auto-terminate disabled for this plan
                continue;
            }

            $cutoff = now()->subDays($effectiveDays);

            if ($service->suspended_at->lte($cutoff)) {
                $user = $service->user;

                ServiceTerminating::dispatch($user, $service);

                $service->update([
                    'status' => 'terminated',
                    'terminated_at' => now(),
                    'auto_renew' => false,
                ]);

                try {
                    $this->provisioningFactory->make($service->service_type)->terminate($service);
                } catch (\Throwable $e) {
                    Log::error("Dunning: failed to terminate service #{$service->id} on platform", [
                        'service_id' => $service->id,
                        'service_type' => $service->service_type,
                        'error' => $e->getMessage(),
                    ]);
                }

                ServiceTerminated::dispatch($user, $service);
                $user->notify(new ServiceTerminatedNotification($service));

                $count++;

                Log::info("Dunning: terminated service #{$service->id} for user #{$service->user_id}.");
            }
        }

        return $count;
    }

    /**
     * Suspend all services tied to a specific subscription.
     * Calls provisioning API for each service and fires events.
     */
    public function suspendServicesForSubscription(Subscription $subscription): void
    {
        $services = Service::query()
            ->where('subscription_id', $subscription->id)
            ->where('status', 'active')
            ->with(['user', 'plan'])
            ->get();

        foreach ($services as $service) {
            $user = $service->user;

            ServiceSuspending::dispatch($user, $service);

            $service->update([
                'status' => 'suspended',
                'suspended_at' => now(),
            ]);

            try {
                $this->provisioningFactory->make($service->service_type)->suspend($service);
            } catch (\Throwable $e) {
                Log::error("Dunning: failed to suspend service #{$service->id} on platform", [
                    'service_id' => $service->id,
                    'service_type' => $service->service_type,
                    'error' => $e->getMessage(),
                ]);
            }

            ServiceSuspended::dispatch($user, $service);
            $user->notify(new ServiceSuspendedNotification($service));
        }
    }

    /**
     * Reactivate services when a past-due subscription becomes active again.
     * Calls provisioning API for each service and fires events.
     */
    public function reactivateServicesForSubscription(Subscription $subscription): void
    {
        $services = Service::query()
            ->where('subscription_id', $subscription->id)
            ->where('status', 'suspended')
            ->with('user')
            ->get();

        foreach ($services as $service) {
            $user = $service->user;

            ServiceUnsuspending::dispatch($user, $service);

            $service->update([
                'status' => 'active',
                'suspended_at' => null,
            ]);

            try {
                $this->provisioningFactory->make($service->service_type)->unsuspend($service);
            } catch (\Throwable $e) {
                Log::error("Dunning: failed to unsuspend service #{$service->id} on platform", [
                    'service_id' => $service->id,
                    'service_type' => $service->service_type,
                    'error' => $e->getMessage(),
                ]);
            }

            ServiceUnsuspended::dispatch($user, $service);
        }
    }

    /**
     * Send suspension warning notifications to users whose services will be suspended soon.
     * Warns users whose subscriptions are past due and within the warning window.
     */
    public function sendSuspensionWarnings(): int
    {
        $warningDays = config('billing.suspension.warning_days_before_suspend');

        $subscriptions = Subscription::query()
            ->where('stripe_status', 'past_due')
            ->with('user')
            ->get();

        $count = 0;

        foreach ($subscriptions as $subscription) {
            $effectiveDays = $this->getEffectiveSuspendDays($subscription);

            if ($effectiveDays === null) {
                continue;
            }

            $daysPastDue = (int) now()->diffInDays($subscription->updated_at);
            $daysUntilSuspend = $effectiveDays - $daysPastDue;

            // Send warning if within the warning window (e.g., 1 day before suspension)
            if ($daysUntilSuspend <= $warningDays && $daysUntilSuspend > 0) {
                $services = Service::query()
                    ->where('subscription_id', $subscription->id)
                    ->where('status', 'active')
                    ->get();

                $user = $subscription->user;

                foreach ($services as $service) {
                    $user->notify(new ServiceSuspensionWarningNotification($service));
                    $count++;
                }
            }
        }

        if ($count > 0) {
            Log::info("Dunning: sent {$count} suspension warning notification(s).");
        }

        return $count;
    }

    /**
     * Send termination warning notifications to users whose services will be terminated soon.
     * Warns users whose services are suspended and within the warning window.
     */
    public function sendTerminationWarnings(): int
    {
        $warningDays = config('billing.suspension.warning_days_before_terminate');

        $services = Service::query()
            ->where('status', 'suspended')
            ->whereNotNull('suspended_at')
            ->with(['user', 'plan'])
            ->get();

        $count = 0;

        foreach ($services as $service) {
            $effectiveDays = $this->getEffectiveTerminateDays($service);

            if ($effectiveDays === null) {
                continue;
            }

            $daysSuspended = (int) now()->diffInDays($service->suspended_at);
            $daysUntilTerminate = $effectiveDays - $daysSuspended;

            // Send warning if within the warning window (e.g., 7 days before termination)
            if ($daysUntilTerminate <= $warningDays && $daysUntilTerminate > 0) {
                $service->user->notify(new ServiceTerminationWarningNotification($service));
                $count++;
            }
        }

        if ($count > 0) {
            Log::info("Dunning: sent {$count} termination warning notification(s).");
        }

        return $count;
    }

    /**
     * Get the effective number of days past due before suspension.
     * Resolution order: Customer override -> Plan setting -> Global config.
     *
     * Returns null if auto-suspend is disabled for the plan.
     */
    public function getEffectiveSuspendDays(Subscription $subscription): ?int
    {
        // Load the plan via any service linked to this subscription
        $service = Service::query()
            ->where('subscription_id', $subscription->id)
            ->with('plan')
            ->first();

        $plan = $service?->plan;

        // Check if auto-suspend is disabled at the plan level
        if ($plan && ! $plan->auto_suspend_enabled) {
            return null;
        }

        // Customer override takes priority
        $user = $subscription->user;
        if ($user && $user->override_days_to_suspend !== null) {
            return $user->override_days_to_suspend;
        }

        // Plan-level setting
        if ($plan && $plan->days_to_suspend !== null) {
            return $plan->days_to_suspend;
        }

        // Global config fallback
        return (int) config('billing.suspension.days_past_due_to_suspend');
    }

    /**
     * Get the effective number of days suspended before termination.
     * Resolution order: Customer override -> Plan setting -> Global config.
     *
     * Returns null if auto-terminate is disabled for the plan.
     */
    public function getEffectiveTerminateDays(Service $service): ?int
    {
        $plan = $service->plan;

        // Check if auto-terminate is disabled at the plan level
        if ($plan && ! $plan->auto_terminate_enabled) {
            return null;
        }

        // Customer override takes priority
        $user = $service->user;
        if ($user && $user->override_days_to_terminate !== null) {
            return $user->override_days_to_terminate;
        }

        // Plan-level setting
        if ($plan && $plan->days_to_terminate !== null) {
            return $plan->days_to_terminate;
        }

        // Global config fallback
        return (int) config('billing.suspension.days_suspended_to_terminate');
    }

    /**
     * Suspend a user's account entirely.
     */
    public function suspendUser(User $user, string $reason = 'overdue'): void
    {
        $user->update(['status' => 'suspended']);

        Log::info("Dunning: suspended user #{$user->id} for reason: {$reason}.");
    }
}
