<?php

namespace App\Services;

use App\Models\Coach;
use App\Enums\NotificationTypeEnum;
use App\Models\Member;
use App\Repositories\NotificationRepository;

class NotificationService extends BaseService
{
    public function __construct(
        protected NotificationRepository $notificationRepository
    ) {}

    /**
     * Send a notification to a single member (prevents duplicates for same type+booking).
     */
    public function sendToMember(
        Member  $member,
        int     $type,
        string  $title,
        string  $message,
        ?int    $bookingId = null,
        array   $extra = []
     ): void {
        $uniqueKey = $bookingId ? 'booking_id' : (isset($extra['session_id']) ? 'session_id' : null);
        $uniqueValue = $bookingId ?? ($extra['session_id'] ?? null);

        if ($uniqueKey && $uniqueValue) {
            if ($uniqueKey === 'booking_id') {
                $exists = $this->notificationRepository->existsDuplicateForBooking(
                    $member->id,
                    $type,
                    (int) $uniqueValue
                );
            } else {
                $exists = \Illuminate\Support\Facades\DB::table('notifications')
                    ->where('notifiable_type', Member::class)
                    ->where('notifiable_id', $member->id)
                    ->where('type', $type)
                    ->where('created_at', '>=', now()->subDay())
                    ->whereRaw("JSON_EXTRACT(data, '$.{$uniqueKey}') = ?", [$uniqueValue])
                    ->exists();
            }

            if ($exists) {
                return;
            }
        }

        $data = array_merge([
            'title'      => $title,
            'message'    => $message,
            'booking_id' => $bookingId,
        ], $extra);

        $this->notificationRepository->insert([
            'type'            => $type,
            'notifiable_type' => Member::class,
            'notifiable_id'   => $member->id,
            'data'            => json_encode($data),
            'created_at'      => now(),
            'updated_at'      => now(),
        ]);
    }

    /**
     * Send same notification to multiple members (bulk insert, with duplicate check).
     */
    public function sendToMembers(
        iterable $members,
        int      $type,
        string   $title,
        string   $message,
        ?int     $bookingId = null,
        array    $extra = []
    ): void {
        $now = now();
        $rows = [];

        $notifiedIds = [];
        if ($bookingId) {
            $notifiedIds = $this->notificationRepository->notifiedMemberIdsForBooking($type, $bookingId);
        }

        $data = json_encode(array_merge([
            'title'      => $title,
            'message'    => $message,
            'booking_id' => $bookingId,
        ], $extra));

        foreach ($members as $member) {
            $memberId = $member instanceof Member ? $member->id : $member;

            if (in_array($memberId, $notifiedIds, true)) {
                continue;
            }

            $rows[] = [
                'type'            => $type,
                'notifiable_type' => Member::class,
                'notifiable_id'   => $memberId,
                'data'            => $data,
                'created_at'      => $now,
                'updated_at'      => $now,
            ];
        }

        $this->notificationRepository->insertBulk($rows);
    }

    public function sendGeneralNotification(string $title, string $message): int
    {
        $now  = now();
        $data = json_encode([
            'title'   => $title,
            'message' => $message,
        ]);

        $alreadyNotified = $this->notificationRepository->notifiedNotifiableIdsByTypeAndContent(
            NotificationTypeEnum::GENERAL->value,
            $title,
            $message,
            Member::class
        );
        $alreadyMap = array_flip(array_map('intval', $alreadyNotified));

        $inserted = 0;
        Member::active()->select('id')->orderBy('id')->chunkById(1000, function ($members) use (&$inserted, $alreadyMap, $now, $data) {
            $rows = [];
            foreach ($members as $member) {
                $memberId = (int) $member->id;
                if (isset($alreadyMap[$memberId])) {
                    continue;
                }
                $rows[] = [
                    'type'            => NotificationTypeEnum::GENERAL->value,
                    'notifiable_type' => Member::class,
                    'notifiable_id'   => $memberId,
                    'data'            => $data,
                    'created_at'      => $now,
                    'updated_at'      => $now,
                ];
            }
            if (!empty($rows)) {
                $this->notificationRepository->insertBulk($rows);
                $inserted += count($rows);
            }
        });

        return (int) $inserted;
    }




    public function sendSpecificCustomerNotification(int $memberId, string $title, string $message): void
    {
        $member = Member::findOrFail($memberId);

        if ($this->notificationRepository->existsRecentByTypeAndContent(
            $member->id,
            NotificationTypeEnum::SPECIFIC_CUSTOMER->value,
            $title,
            $message,
            Member::class
        )) {
            return;
        }

        $this->sendToMember(
            $member,
            NotificationTypeEnum::SPECIFIC_CUSTOMER->value,
            $title,
            $message
        );
    }

    public function sendSpecificCoachNotification(int $coachId, string $title, string $message): void
    {
        $coach = Coach::findOrFail($coachId);

        if ($this->notificationRepository->existsRecentByTypeAndContent(
            $coach->id,
            NotificationTypeEnum::SPECIFIC_CUSTOMER->value,
            $title,
            $message,
            Coach::class
        )) {
            return;
        }

        $data = [
            'title'      => $title,
            'message'    => $message,
            'booking_id' => null,
        ];

        $this->notificationRepository->insert([
            'type'            => NotificationTypeEnum::SPECIFIC_CUSTOMER->value,
            'notifiable_type' => Coach::class,
            'notifiable_id'   => $coach->id,
            'data'            => json_encode($data),
            'created_at'      => now(),
            'updated_at'      => now(),
        ]);
    }

    /**
     * General notification to selected recipients (members and/or coaches).
     * Performance: skip duplicates in one query per type, then bulk insert in chunks.
     */
    public function sendGeneralNotificationToRecipients(
        string $title,
        string $message,
        array $memberIds,
        array $coachIds
    ): int {
        $memberIds = array_values(array_unique(array_filter(array_map('intval', $memberIds))));
        $coachIds  = array_values(array_unique(array_filter(array_map('intval', $coachIds))));

        $now = now();
        $data = json_encode([
            'title'   => $title,
            'message' => $message,
        ]);

        $inserted = 0;

        if (!empty($memberIds)) {
            $alreadyNotifiedMembers = $this->notificationRepository->notifiedNotifiableIdsByTypeAndContent(
                NotificationTypeEnum::GENERAL->value,
                $title,
                $message,
                Member::class
            );
            $skipMembers = array_flip(array_map('intval', $alreadyNotifiedMembers));

            foreach (array_chunk($memberIds, 500) as $chunk) {
                $rows = [];
                foreach ($chunk as $id) {
                    if (isset($skipMembers[$id])) {
                        continue;
                    }
                    $rows[] = [
                        'type'            => NotificationTypeEnum::GENERAL->value,
                        'notifiable_type' => Member::class,
                        'notifiable_id'   => (int) $id,
                        'data'            => $data,
                        'created_at'      => $now,
                        'updated_at'      => $now,
                    ];
                }
                if (!empty($rows)) {
                    $this->notificationRepository->insertBulk($rows);
                    $inserted += count($rows);
                }
            }
        }

        if (!empty($coachIds)) {
            $alreadyNotifiedCoaches = $this->notificationRepository->notifiedNotifiableIdsByTypeAndContent(
                NotificationTypeEnum::GENERAL->value,
                $title,
                $message,
                Coach::class
            );
            $skipCoaches = array_flip(array_map('intval', $alreadyNotifiedCoaches));

            foreach (array_chunk($coachIds, 500) as $chunk) {
                $rows = [];
                foreach ($chunk as $id) {
                    if (isset($skipCoaches[$id])) {
                        continue;
                    }
                    $rows[] = [
                        'type'            => NotificationTypeEnum::GENERAL->value,
                        'notifiable_type' => Coach::class,
                        'notifiable_id'   => (int) $id,
                        'data'            => $data,
                        'created_at'      => $now,
                        'updated_at'      => $now,
                    ];
                }
                if (!empty($rows)) {
                    $this->notificationRepository->insertBulk($rows);
                    $inserted += count($rows);
                }
            }
        }

        return (int) $inserted;
    }

    public function sendBookingNotification(
        int $memberId,
        string $title,
        string $message,
        int $bookingId,
        ?int $type = null
    ): void {
        $member = Member::findOrFail($memberId);
        $this->sendToMember(
            $member,
            $type ?? NotificationTypeEnum::BOOKING_CREATED->value,
            $title,
            $message,
            $bookingId
        );
    }

    public function sendBookingReminder(int $memberId, string $title, string $message, int $bookingId): void
    {
        $member = Member::findOrFail($memberId);
        $this->sendToMember(
            $member,
            NotificationTypeEnum::BOOKING_REMINDER->value,
            $title,
            $message,
            $bookingId
        );
    }

    public function sendSessionReminder(int $memberId, string $title, string $message, int $sessionId): void
    {
        $member = Member::findOrFail($memberId);
        $this->sendToMember(
            $member,
            NotificationTypeEnum::SESSION_REMINDER->value,
            $title,
            $message,
            null,
            ['session_id' => $sessionId]
        );
    }

    public function sendInvitation(
        int $memberId,
        string $title,
        string $message,
        ?int $bookingId = null,
        array $extra = []
    ): void {
        $member = Member::findOrFail($memberId);
        $this->sendToMember(
            $member,
            NotificationTypeEnum::INVITATION->value,
            $title,
            $message,
            $bookingId,
            $extra
        );
    }

    public function sendTeamInvitation(
        int $memberId,
        string $title,
        string $message,
        array $extra = []
    ): void {
        $member = Member::findOrFail($memberId);
        $this->sendToMember(
            $member,
            NotificationTypeEnum::TEAM_INVITATION->value,
            $title,
            $message,
            null,
            $extra
        );
    }
}
