<?php

namespace App\Filament\Pages;

use App\Models\Coupon;
use App\Models\SmsCampaign;
use App\Services\SmsBroadcastService;
use App\Services\SmsMessageAnalyzer;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\EmbeddedSchema;
use Filament\Schemas\Components\Form;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

/**
 * @property-read Schema $form
 */
class SendSms extends Page
{
    protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChatBubbleLeftRight;

    protected static ?string $navigationLabel = null;

    protected static ?string $title = null;

    protected static ?int $navigationSort = 2;

    protected string $view = 'filament.pages.send-sms';

    /**
     * @var array<string, mixed>|null
     */
    public ?array $data = [];

    public ?int $activeCampaignId = null;

    public bool $campaignNotificationSent = false;

    public static function getNavigationLabel(): string
    {
        return __('filament.send_sms.navigation');
    }

    public function getTitle(): string
    {
        return __('filament.send_sms.title');
    }

    public function mount(): void
    {
        $this->form->fill([
            'message' => '',
            'send_to_all' => true,
            'coupon_ids' => [],
            'excluded_coupon_ids' => [],
        ]);
    }

    public function defaultForm(Schema $schema): Schema
    {
        return $schema
            ->statePath('data')
            ->columns(1);
    }

    public function form(Schema $schema): Schema
    {
        return $schema
            ->components([
                Section::make(__('filament.send_sms.sections.message'))
                    ->schema([
                        Textarea::make('message')
                            ->label(__('filament.send_sms.fields.message'))
                            ->required()
                            ->rows(8)
                            ->maxLength(1000)
                            ->live(debounce: 300)
                            ->helperText(fn (Get $get): string => app(SmsMessageAnalyzer::class)->summary((string) ($get('message') ?? ''))),
                    ]),
                Section::make(__('filament.send_sms.sections.recipients'))
                    ->description(__('filament.send_sms.sections.recipients_description'))
                    ->schema([
                        Checkbox::make('send_to_all')
                            ->label(__('filament.send_sms.fields.send_to_all'))
                            ->helperText(__('filament.send_sms.fields.send_to_all_helper'))
                            ->live(),
                        Select::make('coupon_ids')
                            ->label(__('filament.send_sms.fields.coupon_ids'))
                            ->multiple()
                            ->searchable()
                            ->disabled(fn (Get $get): bool => (bool) $get('send_to_all'))
                            ->dehydrated(fn (Get $get): bool => ! (bool) $get('send_to_all'))
                            ->helperText(__('filament.send_sms.fields.coupon_ids_helper'))
                            ->getSearchResultsUsing(fn (string $search): array => $this->searchCoupons($search))
                            ->getOptionLabelsUsing(fn (array $values): array => $this->couponLabels($values)),
                        Select::make('excluded_coupon_ids')
                            ->label(__('filament.send_sms.fields.excluded_coupon_ids'))
                            ->multiple()
                            ->searchable()
                            ->helperText(__('filament.send_sms.fields.excluded_coupon_ids_helper'))
                            ->getSearchResultsUsing(fn (string $search): array => $this->searchCoupons($search))
                            ->getOptionLabelsUsing(fn (array $values): array => $this->couponLabels($values)),
                    ]),
            ]);
    }

    public function content(Schema $schema): Schema
    {
        return $schema
            ->components([
                Form::make([EmbeddedSchema::make('form')])
                    ->id('send-sms-form')
                    ->livewireSubmitHandler('sendCampaign')
                    ->footer([
                        Actions::make([
                            Action::make('send')
                                ->label(__('filament.send_sms.actions.send'))
                                ->icon(Heroicon::OutlinedPaperAirplane)
                                ->requiresConfirmation()
                                ->modalHeading(__('filament.send_sms.actions.send_confirm_heading'))
                                ->modalDescription(fn (): string => $this->confirmationDescription())
                                ->modalSubmitActionLabel(__('filament.send_sms.actions.send_confirm_submit'))
                                ->action('sendCampaign')
                                ->disabled(fn (): bool => $this->activeCampaignId !== null && ! $this->isCampaignComplete()),
                        ]),
                    ]),
            ]);
    }

    public function sendCampaign(): void
    {
        if ($this->activeCampaignId !== null && ! $this->isCampaignComplete()) {
            Notification::make()
                ->title(__('filament.send_sms.notifications.campaign_still_sending'))
                ->warning()
                ->send();

            return;
        }

        $data = $this->form->getState();

        $validator = Validator::make($data, [
            'message' => ['required', 'string', 'max:1000'],
            'send_to_all' => ['required', 'boolean'],
            'coupon_ids' => [
                Rule::requiredIf(fn (): bool => ! ($data['send_to_all'] ?? false)),
                'array',
            ],
            'coupon_ids.*' => ['integer', 'exists:coupons,id'],
            'excluded_coupon_ids' => ['nullable', 'array'],
            'excluded_coupon_ids.*' => ['integer', 'exists:coupons,id'],
        ]);

        $validator->validate();

        $sendToAll = (bool) $data['send_to_all'];
        $selectedIds = array_map('intval', $data['coupon_ids'] ?? []);
        $excludedIds = array_map('intval', $data['excluded_coupon_ids'] ?? []);

        $service = app(SmsBroadcastService::class);
        $willReceive = $service->resolveRecipients($sendToAll, $selectedIds, $excludedIds)
            ->filter(fn (Coupon $coupon): bool => is_valid_coupon_phone($coupon->phone))
            ->count();

        if ($willReceive === 0) {
            Notification::make()
                ->title(__('filament.send_sms.notifications.no_valid_recipients'))
                ->body(__('filament.send_sms.notifications.no_valid_recipients_body'))
                ->danger()
                ->send();

            return;
        }

        $campaign = $service->startCampaign(
            admin: auth()->user(),
            message: $data['message'],
            sendToAll: $sendToAll,
            selectedIds: $selectedIds,
            excludedIds: $excludedIds,
        );

        $this->activeCampaignId = $campaign->id;
        $this->campaignNotificationSent = false;

        if ($campaign->isComplete()) {
            $this->notifyCampaignFinished($campaign);
        } else {
            Notification::make()
                ->title(__('filament.send_sms.notifications.queued'))
                ->body(__('filament.send_sms.notifications.queued_body', ['count' => $campaign->recipient_count]))
                ->success()
                ->send();
        }
    }

    public function refreshCampaignProgress(): void
    {
        if ($this->activeCampaignId === null) {
            return;
        }

        $campaign = SmsCampaign::query()->find($this->activeCampaignId);

        if ($campaign === null) {
            $this->activeCampaignId = null;

            return;
        }

        if ($campaign->isComplete()) {
            $this->notifyCampaignFinished($campaign);
        }
    }

    protected function notifyCampaignFinished(SmsCampaign $campaign): void
    {
        if ($this->campaignNotificationSent) {
            return;
        }

        $this->campaignNotificationSent = true;
        $campaign->refresh();

        if ($campaign->failed_count > 0 && $campaign->sent_count === 0) {
            Notification::make()
                ->title(__('filament.send_sms.notifications.failed'))
                ->body(__('filament.send_sms.notifications.failed_body', ['count' => $campaign->failed_count]))
                ->danger()
                ->send();
        } elseif ($campaign->failed_count > 0) {
            Notification::make()
                ->title(__('filament.send_sms.notifications.finished_with_errors'))
                ->body(__('filament.send_sms.notifications.finished_with_errors_body', [
                    'sent' => $campaign->sent_count,
                    'failed' => $campaign->failed_count,
                    'skipped' => $campaign->skipped_count,
                ]))
                ->warning()
                ->send();
        } else {
            Notification::make()
                ->title(__('filament.send_sms.notifications.completed'))
                ->body(__('filament.send_sms.notifications.completed_body', ['count' => $campaign->sent_count]))
                ->success()
                ->send();
        }
    }

    protected function isCampaignComplete(): bool
    {
        if ($this->activeCampaignId === null) {
            return true;
        }

        return SmsCampaign::query()
            ->whereKey($this->activeCampaignId)
            ->whereNotNull('completed_at')
            ->exists();
    }

    protected function confirmationDescription(): string
    {
        $message = (string) ($this->data['message'] ?? '');
        $analyzer = app(SmsMessageAnalyzer::class);
        $willReceive = $this->getWillReceiveCount();

        return __('filament.send_sms.confirmation', [
            'count' => $willReceive,
            'summary' => $analyzer->summary($message),
        ]);
    }

    public function getWillReceiveCount(): int
    {
        $data = $this->data ?? [];

        return app(SmsBroadcastService::class)
            ->resolveRecipients(
                (bool) ($data['send_to_all'] ?? false),
                array_map('intval', $data['coupon_ids'] ?? []),
                array_map('intval', $data['excluded_coupon_ids'] ?? []),
            )
            ->filter(fn (Coupon $coupon): bool => is_valid_coupon_phone($coupon->phone))
            ->count();
    }

    public function getCampaignProgressPercent(): int
    {
        if ($this->activeCampaignId === null) {
            return 0;
        }

        $campaign = SmsCampaign::query()->find($this->activeCampaignId);

        return $campaign?->progressPercent() ?? 0;
    }

    public function getCampaignProgressLabel(): string
    {
        if ($this->activeCampaignId === null) {
            return '';
        }

        $campaign = SmsCampaign::query()->find($this->activeCampaignId);

        if ($campaign === null) {
            return '';
        }

        return __('filament.send_sms.progress.processed', [
            'processed' => $campaign->processedCount(),
            'total' => $campaign->recipient_count,
            'sent' => $campaign->sent_count,
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    protected function getViewData(): array
    {
        return [
            'campaignProgressPercent' => $this->getCampaignProgressPercent(),
            'campaignProgressLabel' => $this->getCampaignProgressLabel(),
        ];
    }

    /**
     * @return array<int, string>
     */
    protected function searchCoupons(string $search): array
    {
        $search = trim($search);

        if ($search === '') {
            return [];
        }

        return Coupon::query()
            ->where(function ($query) use ($search): void {
                $query->where('phone', 'like', "%{$search}%")
                    ->orWhere('code', 'like', "%{$search}%");

                if (ctype_digit($search)) {
                    $query->orWhere('id', (int) $search);
                }
            })
            ->orderBy('id')
            ->limit(50)
            ->get()
            ->mapWithKeys(fn (Coupon $coupon): array => [
                $coupon->id => $this->couponLabel($coupon),
            ])
            ->all();
    }

    /**
     * @param  array<int, int|string>  $values
     * @return array<int, string>
     */
    protected function couponLabels(array $values): array
    {
        if ($values === []) {
            return [];
        }

        return Coupon::query()
            ->whereIn('id', $values)
            ->get()
            ->mapWithKeys(fn (Coupon $coupon): array => [
                $coupon->id => $this->couponLabel($coupon),
            ])
            ->all();
    }

    protected function couponLabel(Coupon $coupon): string
    {
        return format_phone($coupon->phone).' · #'.$coupon->id.' · '.$coupon->code;
    }
}
