<?php

namespace App\Services;

use App\Jobs\SendEmailJob;
use App\Mail\EventRegistrationMail;
use App\Mail\InvitationMail;
use App\Mail\ManualPaymentRejectedMail;
use App\Mail\ManualPaymentVerifiedMail;
use App\Mail\NoticePublishedMail;
use App\Mail\PaymentFailedMail;
use App\Mail\PaymentReceiptMail;
use App\Mail\SubscriptionActivatedMail;
use App\Mail\SubscriptionExpiredMail;
use App\Mail\SubscriptionExpiringMail;
use App\Models\Association;
use App\Models\Event;
use App\Models\Invitation;
use App\Models\NoticePost;
use App\Models\Notification;
use App\Models\Payment;
use App\Models\PlatformSubscription;
use App\Models\User;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;

class NotificationService
{
    /**
     * Core notification dispatch.
     *
     * @param array{channel?: string, related?: Model, metadata?: array, association_id?: int, mailable?: Mailable} $options
     */
    public function notify(User $user, string $type, string $title, string $body, array $options = []): Notification
    {
        $channel = $options['channel'] ?? 'database';
        $associationId = $options['association_id'] ?? $user->last_association_context ?? $user->associations()->first()?->id;

        $notification = Notification::create([
            'association_id' => $associationId,
            'user_id' => $user->id,
            'type' => $type,
            'channel' => $channel,
            'title' => $title,
            'body' => $body,
            'related_type' => isset($options['related']) ? get_class($options['related']) : null,
            'related_id' => $options['related']->id ?? null,
            'metadata' => $options['metadata'] ?? null,
        ]);

        // Send email if channel includes email
        if (in_array($channel, ['email', 'all']) && isset($options['mailable'])) {
            SendEmailJob::dispatch(
                $options['mailable'],
                $user->email,
                $notification->id,
            );
        }

        return $notification;
    }

    /**
     * Send notifications to all members of an association.
     */
    public function notifyAssociationMembers(Association $association, string $type, string $title, string $body, array $options = []): void
    {
        $options['association_id'] = $association->id;

        $association->users()->chunk(100, function ($users) use ($type, $title, $body, $options) {
            foreach ($users as $user) {
                $this->notify($user, $type, $title, $body, $options);
            }
        });
    }

    /**
     * Notify the first admin (owner) of an association.
     */
    private function getAssociationAdmin(Association $association): ?User
    {
        return $association->users()
            ->wherePivot('status', 'active')
            ->orderBy('association_user.joined_at')
            ->first();
    }

    // ──────────────────────────────────────────────
    // Subscription Notifications
    // ──────────────────────────────────────────────

    public function sendSubscriptionActivated(Association $association, PlatformSubscription $subscription): void
    {
        $admin = $this->getAssociationAdmin($association);
        if (!$admin) return;

        $plan = $subscription->subscriptionPlan;
        if (!$plan) return;

        $this->notify($admin, 'subscription.activated', 'Subscription Activated', "Your {$plan->name} plan is now active.", [
            'channel' => 'all',
            'association_id' => $association->id,
            'related' => $subscription,
            'mailable' => new SubscriptionActivatedMail($association, $plan, $subscription),
            'metadata' => ['plan_name' => $plan->name, 'ends_at' => $subscription->ends_at?->toIso8601String()],
        ]);
    }

    public function sendSubscriptionExpiring(Association $association, PlatformSubscription $subscription, int $daysRemaining): void
    {
        $admin = $this->getAssociationAdmin($association);
        if (!$admin) return;

        $plan = $subscription->subscriptionPlan;
        if (!$plan) return;

        $this->notify($admin, 'subscription.expiring', 'Subscription Expiring Soon', "Your subscription expires in {$daysRemaining} days.", [
            'channel' => 'all',
            'association_id' => $association->id,
            'related' => $subscription,
            'mailable' => new SubscriptionExpiringMail($association, $plan, $subscription, $daysRemaining),
            'metadata' => ['days_remaining' => $daysRemaining, 'plan_name' => $plan->name],
        ]);
    }

    public function sendSubscriptionExpired(Association $association, PlatformSubscription $subscription): void
    {
        $admin = $this->getAssociationAdmin($association);
        if (!$admin) return;

        $plan = $subscription->subscriptionPlan;
        if (!$plan) return;

        $this->notify($admin, 'subscription.expired', 'Subscription Expired', "Your {$plan->name} plan has expired. Advanced modules have been locked.", [
            'channel' => 'all',
            'association_id' => $association->id,
            'related' => $subscription,
            'mailable' => new SubscriptionExpiredMail($association, $plan, $subscription),
            'metadata' => ['plan_name' => $plan->name],
        ]);
    }

    public function sendManualPaymentVerified(PlatformSubscription $subscription): void
    {
        $association = $subscription->association;
        $admin = $this->getAssociationAdmin($association);
        if (!$admin) return;

        $this->notify($admin, 'payment.manual_verified', 'Payment Verified', 'Your bank transfer has been verified and subscription activated.', [
            'channel' => 'all',
            'association_id' => $association->id,
            'related' => $subscription,
            'mailable' => new ManualPaymentVerifiedMail($subscription, $association),
        ]);
    }

    public function sendManualPaymentRejected(PlatformSubscription $subscription, string $reason): void
    {
        $association = $subscription->association;
        $admin = $this->getAssociationAdmin($association);
        if (!$admin) return;

        $this->notify($admin, 'payment.manual_rejected', 'Payment Rejected', "Your bank transfer was rejected: {$reason}", [
            'channel' => 'all',
            'association_id' => $association->id,
            'related' => $subscription,
            'mailable' => new ManualPaymentRejectedMail($subscription, $association, $reason),
            'metadata' => ['reason' => $reason],
        ]);
    }

    // ──────────────────────────────────────────────
    // Payment Notifications
    // ──────────────────────────────────────────────

    public function sendPaymentReceipt(User $user, Payment $payment): void
    {
        $association = $payment->association ?? $user->associations()->first();
        if (!$association) return;

        $this->notify($user, 'payment.receipt', 'Payment Received', "Payment of ₦" . number_format($payment->amount, 2) . " confirmed.", [
            'channel' => 'all',
            'association_id' => $association->id,
            'related' => $payment,
            'mailable' => new PaymentReceiptMail($user, $payment, $association),
            'metadata' => ['amount' => $payment->amount, 'reference' => $payment->reference],
        ]);
    }

    public function sendPaymentFailed(User $user, Payment $payment, string $reason): void
    {
        $this->notify($user, 'payment.failed', 'Payment Failed', "Your payment of ₦" . number_format($payment->amount, 2) . " failed: {$reason}", [
            'channel' => 'all',
            'related' => $payment,
            'mailable' => new PaymentFailedMail($user, $payment, $reason),
            'metadata' => ['amount' => $payment->amount, 'reason' => $reason],
        ]);
    }

    // ──────────────────────────────────────────────
    // Event Notifications
    // ──────────────────────────────────────────────

    public function sendEventRegistration(User $user, Event $event): void
    {
        $association = $event->association;
        $associationName = $association?->name;

        $this->notify($user, 'event.registration', 'Event Registration Confirmed', "You're registered for: {$event->title}", [
            'channel' => 'all',
            'association_id' => $association?->id,
            'related' => $event,
            'mailable' => new EventRegistrationMail($user, $event, $associationName),
            'metadata' => ['event_title' => $event->title, 'starts_at' => $event->starts_at?->toIso8601String()],
        ]);
    }

    // ──────────────────────────────────────────────
    // Invitation Notifications
    // ──────────────────────────────────────────────

    public function sendInvitation(Invitation $invitation): void
    {
        $association = $invitation->association;
        if (!$association) return;

        $registrationUrl = config('app.frontend_url', 'http://localhost:3000')
            . '/register?token=' . $invitation->token;

        // Invitation is sent to an email (user may not exist yet)
        SendEmailJob::dispatch(
            new InvitationMail($invitation, $association, $registrationUrl),
            $invitation->email,
        );

        Log::info('Invitation email sent', [
            'email' => $invitation->email,
            'association_id' => $association->id,
        ]);
    }

    // ──────────────────────────────────────────────
    // Notice Notifications
    // ──────────────────────────────────────────────

    public function sendNoticePublished(NoticePost $notice): void
    {
        $association = $notice->association;
        if (!$association) return;

        // Only send email for urgent notices; always create database notification
        $channel = $notice->type === 'urgent' ? 'all' : 'database';

        $this->notifyAssociationMembers($association, 'notice.published', $notice->title, \Illuminate\Support\Str::limit(strip_tags($notice->content), 200), [
            'channel' => $channel,
            'related' => $notice,
            'mailable' => $notice->type === 'urgent' ? new NoticePublishedMail($notice, $association) : null,
            'metadata' => ['notice_type' => $notice->type, 'is_pinned' => $notice->is_pinned],
        ]);
    }
}
