<?php

namespace App\Http\Controllers\Api\V1\Communication;

use App\Domain\Applicants\Models\Applicant;
use App\Domain\Communication\Enums\MessageStatus;
use App\Domain\Communication\Enums\NotificationChannel;
use App\Domain\Communication\Models\NotificationMessage;
use App\Domain\Communication\Models\NotificationPreference;
use App\Domain\Communication\Models\NotificationTemplate;
use App\Domain\Communication\Services\Notifier;
use App\Http\Controllers\Controller;
use App\Support\ApiResponse;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

/** In-app messages, the outbound log and channel preferences (§56, §57). */
class NotificationController extends Controller
{
    public function __construct(private readonly Notifier $notifier) {}

    /** The signed-in user's own in-app messages. */
    public function mine(Request $request): JsonResponse
    {
        $messages = NotificationMessage::forRecipient($request->user())
            ->where('channel', NotificationChannel::InApp->value)
            ->latest('queued_at')
            ->limit(50)
            ->get();

        return ApiResponse::success(
            $messages->map(fn (NotificationMessage $message) => $this->present($message)),
            meta: [
                'unread' => NotificationMessage::forRecipient($request->user())->unread()->count(),
            ],
        );
    }

    public function markRead(Request $request, NotificationMessage $message): JsonResponse
    {
        // Only your own messages, whoever you are.
        abort_unless(
            $message->recipient_type === $request->user()->getMorphClass()
                && $message->recipient_id === $request->user()->getKey(),
            403,
        );

        return ApiResponse::success($this->present($this->notifier->markRead($message)), 'Marked as read.');
    }

    public function markAllRead(Request $request): JsonResponse
    {
        $count = NotificationMessage::forRecipient($request->user())
            ->unread()
            ->update(['read_at' => now()]);

        return ApiResponse::success(['marked' => $count], 'All caught up.');
    }

    /** The outbound log: what the agency has told people (§56). */
    public function log(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('communication.view'), 403);

        $messages = NotificationMessage::query()
            ->where('company_id', $request->user()->company_id)
            ->when($request->filled('channel'), fn ($q) => $q->where('channel', $request->string('channel')))
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
            ->when($request->filled('search'), fn ($q) => $q->where(
                fn ($inner) => $inner
                    ->where('recipient_name', 'like', '%'.$request->string('search').'%')
                    ->orWhere('destination', 'like', '%'.$request->string('search').'%')
                    ->orWhere('subject_line', 'like', '%'.$request->string('search').'%'),
            ))
            ->latest('queued_at')
            ->latest('id')
            ->paginate($request->integer('per_page', 25));

        return ApiResponse::success(
            $messages->through(fn (NotificationMessage $message) => $this->present($message, withBody: true)),
            meta: [
                'channels' => array_map(
                    fn (NotificationChannel $channel) => ['value' => $channel->value, 'label' => $channel->label()],
                    NotificationChannel::cases(),
                ),
                'statuses' => array_map(
                    fn (MessageStatus $status) => ['value' => $status->value, 'label' => $status->label()],
                    MessageStatus::cases(),
                ),
            ],
        );
    }

    public function templates(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('communication.template_manage'), 403);

        $templates = NotificationTemplate::where('company_id', $request->user()->company_id)
            ->orderBy('code')
            ->get();

        return ApiResponse::success(
            $templates->map(fn (NotificationTemplate $template) => [
                'id' => $template->uuid,
                'code' => $template->code,
                'name' => $template->name,
                'channels' => $template->channels,
                'subject' => $template->subject,
                'body' => $template->body,
                'sms_body' => $template->sms_body,
                'is_critical' => $template->is_critical,
                'is_active' => $template->is_active,
            ]),
        );
    }

    public function updateTemplate(Request $request, NotificationTemplate $template): JsonResponse
    {
        abort_unless($request->user()->can('communication.template_manage'), 403);
        abort_unless($template->company_id === $request->user()->company_id, 403);

        $validated = $request->validate([
            'subject' => ['nullable', 'string', 'max:255'],
            'body' => ['required', 'string', 'max:5000'],
            'sms_body' => ['nullable', 'string', 'max:480'],
            'channels' => ['required', 'array', 'min:1'],
            'channels.*' => ['string', 'in:'.implode(',', NotificationChannel::values())],
            'is_active' => ['nullable', 'boolean'],
        ]);

        $template->update($validated);

        return ApiResponse::success(['id' => $template->uuid], 'Template saved.');
    }

    /** An applicant's own channel preferences (§57). */
    public function preferences(Request $request): JsonResponse
    {
        $owner = $this->owner($request);

        $set = NotificationPreference::where('owner_type', $owner->getMorphClass())
            ->where('owner_id', $owner->getKey())
            ->pluck('is_enabled', 'channel');

        return ApiResponse::success(
            array_map(
                fn (NotificationChannel $channel) => [
                    'channel' => $channel->value,
                    'label' => $channel->label(),
                    // Anything not recorded is treated as allowed.
                    'is_enabled' => (bool) ($set[$channel->value] ?? true),
                    'can_disable' => $channel !== NotificationChannel::InApp,
                ],
                NotificationChannel::cases(),
            ),
            meta: ['note' => 'Critical messages about money, visas and travel are always sent.'],
        );
    }

    public function setPreferences(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'channels' => ['required', 'array'],
            'channels.*' => ['boolean'],
        ]);

        $owner = $this->owner($request);

        // In-app cannot be turned off: it is where a critical message always
        // lands, and it costs the recipient nothing.
        $channels = collect($validated['channels'])
            ->except(NotificationChannel::InApp->value)
            ->all();

        $this->notifier->setPreferences($owner, $channels);

        return ApiResponse::success(null, 'Preferences saved.');
    }

    /** The applicant record where there is one, otherwise the user. */
    private function owner(Request $request): Model
    {
        if ($request->user()->user_type === 'applicant') {
            $applicant = Applicant::where('user_id', $request->user()->getKey())->first();

            if ($applicant !== null) {
                return $applicant;
            }
        }

        return $request->user();
    }

    /** @return array<string, mixed> */
    private function present(NotificationMessage $message, bool $withBody = false): array
    {
        return array_filter([
            'id' => $message->uuid,
            'template_code' => $message->template_code,
            'channel' => $message->channel->value,
            'channel_label' => $message->channel->label(),
            'status' => $message->status->value,
            'status_label' => $message->status->label(),
            'recipient_name' => $message->recipient_name,
            'destination' => $message->destination,
            'subject_line' => $message->subject_line,
            'body' => $withBody || $message->channel === NotificationChannel::InApp ? $message->body : null,
            'queued_at' => $message->queued_at?->toIso8601String(),
            'sent_at' => $message->sent_at?->toIso8601String(),
            'read_at' => $message->read_at?->toIso8601String(),
            'failure_reason' => $message->failure_reason,
        ], fn ($value) => $value !== null);
    }
}
