<?php

namespace App\Services;

use App\Models\AppNotification;
use App\Models\Customer;
use App\Models\NotificationCampaign;
use App\Models\NotificationCampaignRecipient;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class CustomerNotificationService
{
    public function __construct(
        private readonly SmsService $sms,
        private readonly WhatsappService $whatsapp,
        private readonly PlatformSettings $settings,
    ) {
    }

    public function send(array $data, User $sender): NotificationCampaign
    {
        $tenant = $sender->tenant;

        if (! $tenant) {
            throw new InvalidArgumentException('A tenant user is required to send customer notifications.');
        }

        $isExternalAudience = ($data['audience_type'] ?? null) === 'numbers';
        $channels = collect($data['channels'] ?? [])
            ->intersect($isExternalAudience ? ['email', 'sms', 'whatsapp'] : ['email', 'sms', 'portal', 'whatsapp'])
            ->values()
            ->all();

        if ($isExternalAudience) {
            return $this->sendExternal($data, $sender, $tenant, $channels);
        }

        $customers = $this->resolveCustomers($sender, $data);

        return $this->sendCustomers($data, $sender, $tenant, $channels, $customers);
    }

    public function resendFailed(NotificationCampaign $campaign, User $sender): array
    {
        $tenant = $sender->tenant;

        if (! $tenant || (int) $campaign->tenant_id !== (int) $tenant->id) {
            throw new InvalidArgumentException('This notification campaign does not belong to your company.');
        }

        $campaign->loadMissing(['recipients.customer']);
        $channels = collect($campaign->channels ?? [])
            ->intersect(['email', 'sms', 'whatsapp'])
            ->values();

        $attempted = 0;
        $sent = 0;
        $failed = 0;

        foreach ($campaign->recipients as $recipient) {
            $customer = $recipient->customer;

            foreach ($channels as $channel) {
                $statusField = $channel.'_status';
                $errorField = $channel.'_error';

                if ($recipient->{$statusField} !== 'failed') {
                    continue;
                }

                $attempted++;

                $result = match ($channel) {
                    'email' => filled($recipient->email)
                        ? $this->sendEmailToAddress($tenant, $recipient->email, $recipient->customer_name, $campaign->subject, $campaign->message)
                        : ['status' => 'skipped', 'message' => 'Recipient has no email address.'],
                    'sms' => filled($recipient->phone)
                        ? $this->sms->send($recipient->phone, $campaign->message, $tenant, $customer, [
                            'notification_campaign_id' => $campaign->id,
                            'source' => 'notification_resend',
                        ])
                        : ['status' => 'skipped', 'message' => 'Recipient has no phone number.'],
                    'whatsapp' => filled($recipient->phone)
                        ? $this->whatsapp->send($recipient->phone, $campaign->message, $tenant, $customer, [
                            'notification_campaign_id' => $campaign->id,
                            'source' => 'notification_resend',
                        ])
                        : ['status' => 'skipped', 'message' => 'Recipient has no phone number.'],
                };

                $recipient->forceFill([
                    $statusField => $result['status'],
                    $errorField => $result['status'] === 'failed' ? $this->deliveryError($result) : null,
                    'sent_at' => now(),
                ])->save();

                if ($result['status'] === 'failed') {
                    $failed++;
                } elseif (in_array($result['status'], ['sent', 'logged'], true)) {
                    $sent++;
                }
            }
        }

        $this->refreshCampaignDeliveryTotals($campaign);

        return [
            'attempted' => $attempted,
            'sent' => $sent,
            'failed' => $failed,
        ];
    }

    private function sendCustomers(array $data, User $sender, Tenant $tenant, array $channels, Collection $customers): NotificationCampaign
    {
        return DB::transaction(function () use ($data, $sender, $tenant, $channels, $customers): NotificationCampaign {
            $campaign = NotificationCampaign::create([
                'tenant_id' => $tenant->id,
                'created_by' => $sender->id,
                'recipient_type' => 'customer',
                'subject' => $data['subject'],
                'message' => $data['message'],
                'channels' => $channels,
                'audience_type' => $data['audience_type'],
                'audience_filters' => $this->audienceFilters($data),
                'status' => $customers->isEmpty() ? 'no_recipients' : 'completed',
                'total_recipients' => $customers->count(),
                'sent_at' => now(),
            ]);

            $emailSent = 0;
            $smsSent = 0;
            $portalAlerts = 0;
            $whatsappSent = 0;
            $failed = 0;

            foreach ($customers as $customer) {
                $recipient = NotificationCampaignRecipient::create([
                    'notification_campaign_id' => $campaign->id,
                    'tenant_id' => $tenant->id,
                    'customer_id' => $customer->id,
                    'tenant_name' => $tenant->name,
                    'customer_name' => $customer->fullName(),
                    'email' => $customer->email,
                    'phone' => $customer->phone,
                    'sent_at' => now(),
                ]);

                if (in_array('portal', $channels, true)) {
                    if (! $customer->allowsNotificationChannel('portal')) {
                        $recipient->update(['portal_alert_status' => 'skipped']);
                    } elseif ($customer->user_id) {
                        AppNotification::withoutGlobalScopes()->create([
                            'tenant_id' => $tenant->id,
                            'user_id' => $customer->user_id,
                            'title' => $data['subject'],
                            'body' => $data['message'],
                            'type' => 'customer_message',
                            'channel' => 'database',
                        ]);

                        $recipient->update(['portal_alert_status' => 'sent']);
                        $portalAlerts++;
                    } else {
                        $recipient->update(['portal_alert_status' => 'skipped']);
                    }
                }

                if (in_array('email', $channels, true)) {
                    $emailResult = $customer->allowsNotificationChannel('email')
                        ? $this->sendEmail($tenant, $customer, $data['subject'], $data['message'])
                        : ['status' => 'skipped', 'message' => 'Member opted out of email notifications.'];
                    $recipient->update([
                        'email_status' => $emailResult['status'],
                        'email_error' => $emailResult['status'] === 'failed' ? $emailResult['message'] : null,
                    ]);

                    $this->countDelivery($emailResult, $emailSent, $failed);
                }

                if (in_array('sms', $channels, true)) {
                    $smsResult = match (true) {
                        ! $customer->allowsNotificationChannel('sms') => ['status' => 'skipped', 'message' => 'Member opted out of SMS notifications.'],
                        filled($customer->phone) => $this->sms->send($customer->phone, $data['message'], $tenant, $customer, [
                            'notification_campaign_id' => $campaign->id,
                            'source' => 'member_notification',
                        ]),
                        default => ['status' => 'skipped', 'message' => 'Customer has no phone number.'],
                    };

                    $recipient->update([
                        'sms_status' => $smsResult['status'],
                        'sms_error' => $smsResult['status'] === 'failed' ? $smsResult['message'] : null,
                    ]);

                    $this->countDelivery($smsResult, $smsSent, $failed);
                }

                if (in_array('whatsapp', $channels, true)) {
                    $whatsappResult = match (true) {
                        ! $customer->allowsNotificationChannel('whatsapp') => ['status' => 'skipped', 'message' => 'Member opted out of WhatsApp notifications.'],
                        filled($customer->phone) => $this->whatsapp->send($customer->phone, $data['message'], $tenant, $customer, [
                            'notification_campaign_id' => $campaign->id,
                            'source' => 'member_notification',
                        ]),
                        default => ['status' => 'skipped', 'message' => 'Customer has no phone number.'],
                    };

                    $recipient->update([
                        'whatsapp_status' => $whatsappResult['status'],
                        'whatsapp_error' => $whatsappResult['status'] === 'failed' ? $whatsappResult['message'] : null,
                    ]);

                    $this->countDelivery($whatsappResult, $whatsappSent, $failed);
                }
            }

            $campaign->update([
                'email_sent_count' => $emailSent,
                'sms_sent_count' => $smsSent,
                'portal_alert_count' => $portalAlerts,
                'whatsapp_sent_count' => $whatsappSent,
                'failed_count' => $failed,
                'status' => $failed > 0 ? 'completed_with_errors' : $campaign->status,
            ]);

            return $campaign;
        });
    }

    private function sendExternal(array $data, User $sender, Tenant $tenant, array $channels): NotificationCampaign
    {
        $recipients = $this->resolveExternalRecipients($data);

        return DB::transaction(function () use ($data, $sender, $tenant, $channels, $recipients): NotificationCampaign {
            $campaign = NotificationCampaign::create([
                'tenant_id' => $tenant->id,
                'created_by' => $sender->id,
                'recipient_type' => 'external',
                'subject' => $data['subject'],
                'message' => $data['message'],
                'channels' => $channels,
                'audience_type' => 'numbers',
                'audience_filters' => $this->audienceFilters($data),
                'status' => $recipients->isEmpty() ? 'no_recipients' : 'completed',
                'total_recipients' => $recipients->count(),
                'sent_at' => now(),
            ]);

            $emailSent = 0;
            $smsSent = 0;
            $whatsappSent = 0;
            $failed = 0;

            foreach ($recipients as $recipientData) {
                $recipient = NotificationCampaignRecipient::create([
                    'notification_campaign_id' => $campaign->id,
                    'tenant_id' => $tenant->id,
                    'tenant_name' => $tenant->name,
                    'customer_name' => $recipientData['name'],
                    'email' => $recipientData['email'],
                    'phone' => $recipientData['phone'],
                    'sent_at' => now(),
                ]);

                if (in_array('email', $channels, true)) {
                    $emailResult = filled($recipientData['email'])
                        ? $this->sendEmailToAddress($tenant, $recipientData['email'], $recipientData['name'], $data['subject'], $data['message'])
                        : ['status' => 'skipped', 'message' => 'External contact has no email address.'];

                    $recipient->update([
                        'email_status' => $emailResult['status'],
                        'email_error' => $emailResult['status'] === 'failed' ? $emailResult['message'] : null,
                    ]);

                    $this->countDelivery($emailResult, $emailSent, $failed);
                }

                if (in_array('sms', $channels, true)) {
                    $smsResult = filled($recipientData['phone'])
                        ? $this->sms->send($recipientData['phone'], $data['message'], $tenant, null, [
                            'notification_campaign_id' => $campaign->id,
                            'source' => 'custom_notification',
                        ])
                        : ['status' => 'skipped', 'message' => 'External contact has no phone number.'];

                    $recipient->update([
                        'sms_status' => $smsResult['status'],
                        'sms_error' => $smsResult['status'] === 'failed' ? $this->deliveryError($smsResult) : null,
                    ]);

                    $this->countDelivery($smsResult, $smsSent, $failed);
                }

                if (in_array('whatsapp', $channels, true)) {
                    $whatsappResult = filled($recipientData['phone'])
                        ? $this->whatsapp->send($recipientData['phone'], $data['message'], $tenant, null, [
                            'notification_campaign_id' => $campaign->id,
                            'source' => 'custom_notification',
                        ])
                        : ['status' => 'skipped', 'message' => 'External contact has no phone number.'];

                    $recipient->update([
                        'whatsapp_status' => $whatsappResult['status'],
                        'whatsapp_error' => $whatsappResult['status'] === 'failed' ? $this->deliveryError($whatsappResult) : null,
                    ]);

                    $this->countDelivery($whatsappResult, $whatsappSent, $failed);
                }
            }

            $campaign->update([
                'email_sent_count' => $emailSent,
                'sms_sent_count' => $smsSent,
                'whatsapp_sent_count' => $whatsappSent,
                'failed_count' => $failed,
                'status' => $failed > 0 ? 'completed_with_errors' : $campaign->status,
            ]);

            return $campaign;
        });
    }

    private function resolveCustomers(User $sender, array $data): Collection
    {
        $tenant = $sender->tenant;

        $query = Customer::withoutGlobalScopes()
            ->where('tenant_id', $tenant->id)
            ->visibleTo($sender)
            ->with('user')
            ->orderBy('first_name')
            ->orderBy('last_name');

        match ($data['audience_type']) {
            'individual' => $query->whereIn('id', $data['customer_ids'] ?? []),
            'status' => $query->where('status', $data['customer_status'] ?? 'active'),
            'kyc' => $query->where('kyc_status', $data['kyc_status'] ?? 'pending'),
            default => null,
        };

        return $query->get()->unique('id')->values();
    }

    private function resolveExternalRecipients(array $data): Collection
    {
        $raw = (string) ($data['custom_recipients'] ?? $data['phone_numbers'] ?? '');

        return collect(preg_split('/[\r\n,;]+/', $raw) ?: [])
            ->map(fn (string $value): string => trim($value))
            ->filter()
            ->map(function (string $value): array {
                $email = filter_var($value, FILTER_VALIDATE_EMAIL) ? strtolower($value) : null;
                $phone = $email ? null : $this->normalizePhone($value);

                return [
                    'name' => 'External contact',
                    'email' => $email,
                    'phone' => $phone,
                ];
            })
            ->filter(fn (array $recipient): bool => filled($recipient['email']) || filled($recipient['phone']))
            ->unique(fn (array $recipient): string => ($recipient['email'] ?: '').'|'.($recipient['phone'] ?: ''))
            ->values();
    }

    private function normalizePhone(string $phone): string
    {
        return preg_replace('/(?!^\+)[^\d]/', '', trim($phone)) ?: '';
    }

    private function audienceFilters(array $data): array
    {
        return [
            'customer_ids' => $data['audience_type'] === 'individual' ? array_values($data['customer_ids'] ?? []) : [],
            'customer_status' => $data['audience_type'] === 'status' ? ($data['customer_status'] ?? null) : null,
            'kyc_status' => $data['audience_type'] === 'kyc' ? ($data['kyc_status'] ?? null) : null,
            'custom_recipients_count' => $data['audience_type'] === 'numbers' ? $this->resolveExternalRecipients($data)->count() : 0,
        ];
    }

    private function sendEmail(Tenant $tenant, Customer $customer, string $subject, string $body): array
    {
        if (blank($customer->email)) {
            return ['status' => 'skipped', 'message' => 'Customer has no email address.'];
        }

        return $this->sendEmailToAddress($tenant, $customer->email, $customer->fullName(), $subject, $body);
    }

    private function sendEmailToAddress(Tenant $tenant, string $email, ?string $name, string $subject, string $body): array
    {
        return $this->settings->sendRawMail($tenant, $email, $name, $subject, $body);
    }

    private function deliveryError(array $result): string
    {
        $message = $result['message'] ?? 'Delivery failed.';
        $units = (int) ($result['units'] ?? 0);

        if ($units > 0 && ! str_contains($message, 'Required')) {
            return $message.' Required unit'.($units === 1 ? '' : 's').': '.$units.'.';
        }

        return $message;
    }

    private function refreshCampaignDeliveryTotals(NotificationCampaign $campaign): void
    {
        $recipients = $campaign->recipients()->get();
        $delivered = fn (string $status): bool => in_array($status, ['sent', 'logged'], true);

        $emailSent = $recipients->filter(fn (NotificationCampaignRecipient $recipient): bool => $delivered((string) $recipient->email_status))->count();
        $smsSent = $recipients->filter(fn (NotificationCampaignRecipient $recipient): bool => $delivered((string) $recipient->sms_status))->count();
        $portalAlerts = $recipients->where('portal_alert_status', 'sent')->count();
        $whatsappSent = $recipients->filter(fn (NotificationCampaignRecipient $recipient): bool => $delivered((string) $recipient->whatsapp_status))->count();
        $failed = $recipients->sum(fn (NotificationCampaignRecipient $recipient): int => collect([
            $recipient->email_status,
            $recipient->sms_status,
            $recipient->whatsapp_status,
        ])->filter(fn ($status): bool => $status === 'failed')->count());

        $campaign->forceFill([
            'email_sent_count' => $emailSent,
            'sms_sent_count' => $smsSent,
            'portal_alert_count' => $portalAlerts,
            'whatsapp_sent_count' => $whatsappSent,
            'failed_count' => $failed,
            'status' => $campaign->total_recipients === 0
                ? 'no_recipients'
                : ($failed > 0 ? 'completed_with_errors' : 'completed'),
        ])->save();
    }

    private function countDelivery(array $result, int &$sent, int &$failed): void
    {
        if ($result['status'] === 'failed') {
            $failed++;
            return;
        }

        if (in_array($result['status'], ['sent', 'logged'], true)) {
            $sent++;
        }
    }
}
