<?php

namespace App\Components\Contact;

use App\Actions\Communications\AssertCanContactAction;
use App\Enum\Contact\MessageChannel;
use App\Jobs\DownloadAndStoreRecordingJob;
use App\Jobs\Synapse\AutoTranscribeRecordingJob;
use App\Jobs\Synapse\ClassifyAndScoreCallJob;
use App\Models\Claim\Claim;
use App\Models\Communication\Communication;
use App\Models\Communication\Recording;
use App\Models\Contact\ContactTemplate;
use App\Models\Contact\Hub\ContactMessage;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;
use Livewire\Component;
use Livewire\WithPagination;

class ContactHub extends Component
{
    use WithPagination;

    protected string $paginationTheme = 'bootstrap';

    public Model $model;

    public ?string $filterChannel = null;

    public ?string $filterStatus = null;

    public int $perPage = 20;

    /**
     * Message currently shown in the detail offcanvas.
     *
     * Null means that the offcanvas is closed.
     */
    public ?int $selectedMessageId = null;

    public function mount(Model $model): void
    {
        $this->model = $model;
    }

    /**
     * Return to the first activity page when the channel changes.
     */
    public function updatingFilterChannel(): void
    {
        $this->resetPage();
    }

    /**
     * Return to the first activity page when the status changes.
     */
    public function updatingFilterStatus(): void
    {
        $this->resetPage();
    }

    #[On('contactMessageSent')]
    #[On('contactMessageQueued')]
    #[On('dialler-call-started')]
    #[On('twilio-call-disconnected')]
    public function refreshFeed(): void
    {
        $this->resetPage();
    }

    public function viewMessage(int $id): void
    {
        $this->selectedMessageId = $id;
    }

    public function retranscribeRecording(int $recordingId): void
    {
        AutoTranscribeRecordingJob::dispatch($recordingId);
    }

    public function reclassifyRecording(int $recordingId): void
    {
        ClassifyAndScoreCallJob::dispatch(
            recordingId: $recordingId,
            force: true,
        );
    }

    public function refetchRecording(int $recordingId): void
    {
        $recording = Recording::query()->findOrFail($recordingId);

        $recording->update([
            'storage_disk' => null,
            'storage_path' => null,
        ]);

        DownloadAndStoreRecordingJob::dispatch($recording->id);

        $this->dispatch(
            'notify',
            message: 'Recording re-fetch has been queued.',
        );
    }

    public function render()
    {
        $check = app(AssertCanContactAction::class);

        $emailValidity = $check(
            MessageChannel::EMAIL,
            $this->model,
        );

        $smsValidity = $check(
            MessageChannel::SMS,
            $this->model,
        );

        $relatedClaim = method_exists($this->model, 'claim')
            ? $this->model->claim
            : null;

        $claim = $this->model instanceof Claim
            ? $this->model
            : $relatedClaim;

        $templates = $this->getTemplates($relatedClaim);

        $calls = $this->getCallActivities();

        $messages = $this->getMessageActivities();

        $activities = $this->paginateActivities(
            calls: $calls,
            messages: $messages,
        );

        $selectedMessage = $this->getSelectedMessage();

        return view('components.contact.contact-hub', [
            'emailValidity' => $emailValidity,
            'smsValidity' => $smsValidity,
            'activities' => $activities,
            'templates' => $templates,
            'claim' => $claim,
            'selectedMessage' => $selectedMessage,
        ]);
    }

    /**
     * Get the contact templates that apply to the current model.
     */
    private function getTemplates(?Model $relatedClaim): Collection
    {
        $morphTypes = array_unique(array_filter([
            $this->model->getMorphClass(),
            get_class($this->model),
            $relatedClaim?->getMorphClass(),
            $relatedClaim ? get_class($relatedClaim) : null,
        ]));

        return ContactTemplate::query()
            ->where('is_active', true)
            ->where('allow_manual_send', true)
            ->where(function ($query) use ($morphTypes) {
                $query
                    ->whereNull('applies_to_type')
                    ->orWhereIn('applies_to_type', $morphTypes);
            })
            ->orderBy('channel')
            ->orderBy('name')
            ->get()
            ->groupBy(
                fn (ContactTemplate $template) => $template->channel->value,
            );
    }

    /**
     * Get calls for the combined activity feed.
     */
    private function getCallActivities(): Collection
    {
        if (!$this->shouldFetchCalls()) {
            return collect();
        }

        return Communication::query()
            ->where(
                'communicable_type',
                $this->model->getMorphClass(),
            )
            ->where(
                'communicable_id',
                $this->model->getKey(),
            )
            ->when(
                $this->normalisedStatus(),
                fn ($query, string $status) => $query->where(
                    'status',
                    $status,
                ),
            )
            ->with([
                'call',
                'agent',
                'recordings.synapseTranscript',
                'recordings.callScore',
                'recordings.dataProtectionAudit',
            ])
            ->latest('started_at')
            ->get()
            ->map(
                fn (Communication $communication) => [
                    'kind' => 'call',
                    'at' => $communication->started_at
                        ?? $communication->created_at,
                    'data' => $communication,
                ],
            );
    }

    /**
     * Get emails and SMS messages for the combined activity feed.
     */
    private function getMessageActivities(): Collection
    {
        if (!$this->shouldFetchMessages()) {
            return collect();
        }

        $channel = $this->normalisedChannel();

        return ContactMessage::query()
            ->forMessageable($this->model)
            ->when(
                $channel && $channel !== 'call',
                fn ($query) => $query->where(
                    'channel',
                    $channel,
                ),
            )
            ->when(
                $this->normalisedStatus(),
                fn ($query, string $status) => $query->where(
                    'status',
                    $status,
                ),
            )
            ->with([
                'template',
                'creator',
            ])
            ->latest()
            ->get()
            ->map(
                fn (ContactMessage $message) => [
                    'kind' => 'message',
                    'at' => $message->created_at,
                    'data' => $message,
                ],
            );
    }

    /**
     * Combine calls and messages and paginate them as one activity feed.
     */
    private function paginateActivities(
        Collection $calls,
        Collection $messages,
    ): LengthAwarePaginator {
        $activities = $calls
            ->concat($messages)
            ->sortByDesc('at')
            ->values();

        $currentPage = Paginator::resolveCurrentPage();

        return new LengthAwarePaginator(
            items: $activities
                ->forPage($currentPage, $this->perPage)
                ->values(),
            total: $activities->count(),
            perPage: $this->perPage,
            currentPage: $currentPage,
            options: [
                'path' => request()->url(),
            ],
        );
    }

    /**
     * Get the currently selected message for the detail offcanvas.
     */
    private function getSelectedMessage(): ?ContactMessage
    {
        if (!$this->selectedMessageId) {
            return null;
        }

        return ContactMessage::query()
            ->with([
                'template',
                'creator',
                'events',
                'attachments',
            ])
            ->find($this->selectedMessageId);
    }

    /**
     * Determine whether calls should be included.
     */
    private function shouldFetchCalls(): bool
    {
        $channel = $this->normalisedChannel();

        return $channel === null || $channel === 'call';
    }

    /**
     * Determine whether messages should be included.
     */
    private function shouldFetchMessages(): bool
    {
        return in_array(
            $this->normalisedChannel(),
            [
                null,
                'email',
                'sms',
            ],
            true,
        );
    }

    /**
     * Convert the empty channel selection to null.
     */
    private function normalisedChannel(): ?string
    {
        return $this->filterChannel ?: null;
    }

    /**
     * Convert the empty status selection to null.
     */
    private function normalisedStatus(): ?string
    {
        return $this->filterStatus ?: null;
    }

    /**
     * Reset only this component's activity pagination.
     */
    private function resetActivityPagination(): void
    {
        $this->resetPage($this->activityPageName());
    }

    /**
     * Use a unique pagination name to avoid conflicts with other paginators.
     */
    private function activityPageName(): string
    {
        return 'contactActivityPage';
    }
}