<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Company;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\SubscriptionRequest;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * Activa/reactiva la suscripción de una empresa.
 *
 * Cierra el hueco de #163: "Aprobar/Implementar" una SubscriptionRequest solo
 * cambiaba el estado de la solicitud y NO reactivaba ninguna suscripción (había
 * que editarla a mano en Filament). Este servicio ejecuta la mutación real.
 */
class SubscriptionActivationService
{
    /**
     * Aprueba una solicitud e inmediatamente activa la suscripción de la empresa,
     * marcándola como implementada. Todo en una transacción.
     */
    public function approveAndActivate(SubscriptionRequest $request, User $approver, ?string $notes = null, ?string $amountPaid = null, ?string $paymentReference = null): Subscription
    {
        return DB::transaction(function () use ($request, $approver, $notes, $amountPaid, $paymentReference) {
            $company = $request->company ?? Company::findOrFail($request->company_id);

            if (! $request->approve($approver->id, $notes)) {
                throw new RuntimeException('La solicitud no puede aprobarse en su estado actual.');
            }

            $subscription = $this->activate($company, $request->requested_plan_id, $amountPaid, $paymentReference);

            $request->markImplemented($approver->id, [
                'activated_subscription_id' => $subscription->id,
                'notes' => $notes,
            ]);

            return $subscription;
        });
    }

    /**
     * Marca implementada una solicitud YA aprobada y activa la suscripción.
     *
     * Cubre el segundo punto de entrada de #163: la acción "Marcar Implementado"
     * (para una solicitud en estado `approved`) también debe reactivar de verdad,
     * no solo cambiar el status. Idempotente respecto a la activación: extiende
     * `ends_at` desde el futuro si ya está activa.
     */
    public function implementAndActivate(SubscriptionRequest $request, User $implementer, ?string $notes = null, ?string $amountPaid = null, ?string $paymentReference = null): Subscription
    {
        return DB::transaction(function () use ($request, $implementer, $notes, $amountPaid, $paymentReference) {
            $company = $request->company ?? Company::findOrFail($request->company_id);

            $subscription = $this->activate($company, $request->requested_plan_id, $amountPaid, $paymentReference);

            $request->markImplemented($implementer->id, [
                'activated_subscription_id' => $subscription->id,
                'notes' => $notes,
            ]);

            return $subscription;
        });
    }

    /**
     * Activa (o reactiva) la suscripción de una empresa por un ciclo de facturación.
     *
     * Reutiliza la última suscripción de la empresa (la del trial vencido, típicamente);
     * si no existe, crea una nueva. Extiende `ends_at` desde el mayor entre ahora y el
     * `ends_at` vigente para no perder tiempo restante. `save()` invalida la caché de
     * acceso vía Subscription::booted(), así que el panel/PWA se desbloquean al instante.
     */
    public function activate(Company $company, ?int $planId = null, ?string $amountPaid = null, ?string $paymentReference = null): Subscription
    {
        $subscription = $company->subscriptions()->orderByDesc('id')->first();

        if (! $subscription instanceof Subscription) {
            $subscription = new Subscription(['company_id' => $company->id]);
            $subscription->starts_at = now();
        }

        if ($planId) {
            $subscription->plan_id = $planId;
        } elseif (! $subscription->plan_id) {
            $subscription->plan_id = Plan::getTrialPlan()?->id;
        }

        // Registro manual/offline del pago (#165): informativo, no cobra nada.
        if ($amountPaid !== null && $amountPaid !== '') {
            $subscription->amount_paid = $amountPaid;
        }
        if ($paymentReference !== null && $paymentReference !== '') {
            $subscription->payment_reference = $paymentReference;
        }

        $from = ($subscription->ends_at && $subscription->ends_at->isFuture())
            ? $subscription->ends_at->copy()
            : now();

        $subscription->status = Subscription::STATUS_ACTIVE;
        $subscription->is_active = true;
        $subscription->ends_at = $this->addBillingPeriod($from, $subscription->billing_cycle);
        $subscription->grace_period_ends_at = null;
        $subscription->canceled_at = null;
        $subscription->cancellation_reason = null;
        $subscription->canceled_by_user_id = null;

        $subscription->save();

        return $subscription;
    }

    private function addBillingPeriod(Carbon $from, ?string $cycle): Carbon
    {
        return match ($cycle) {
            Subscription::BILLING_QUARTERLY => $from->copy()->addMonths(3),
            Subscription::BILLING_SEMIANNUAL => $from->copy()->addMonths(6),
            Subscription::BILLING_ANNUAL => $from->copy()->addYear(),
            default => $from->copy()->addMonth(),
        };
    }
}
