<?php

namespace App\Repositories\General;

use Exception;
use Throwable;
use App\Models\User;
use App\Models\Group;
use App\Enums\ImageNames;
use App\Enums\SettingKey;
use App\Enums\DeletedType;
use Illuminate\Http\Request;
use App\Enums\GroupRequester;
use App\Services\ImageService;
use App\Enums\NotificationType;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class GroupRepository
{
    protected $imageService;
    protected $notificationRepository;

    public function __construct(ImageService $imageService, NotificationRepository $notificationRepository)
    {
        $this->imageService = $imageService;
        $this->notificationRepository = $notificationRepository;
    }

    public function getAll()
    {
        $groups = Group::with([
            'user.images',
            'images',
            'category',
        ])
            ->filter(request([
                'search',
            ]))
            ->latest()
            ->paginate(config('pagination.general'))
            ->withQueryString();

        return compact('groups');
    }

    public function save(Request $request)
    {
        try {
            DB::beginTransaction();

            $group = Group::updateOrCreate(
                ['id' => $request->id],
                [
                    'user_id' => auth()->id(),
                    'category_id' => $request->category_id,
                    'name' => $request->name,
                    'description' => $request->description,
                    'type' => $request->type,
                ],
            );

            if ($request->hasFile('profile_image')) {
                $oldProfileImage = $group->images()->profile()->first();
                if ($oldProfileImage) {
                    $this->imageService->delete($oldProfileImage->id, Group::class);
                }
                $profileImage = $this->imageService->upload($request->file('profile_image'), Group::class, $group->id, ImageNames::PROFILE->value);
                $group->images()->profile()->save($profileImage);
            }

            if ($request->hasFile('cover_image')) {
                $oldCoverImage = $group->images()->cover()->first();
                if ($oldCoverImage) {
                    $this->imageService->delete($oldCoverImage->id, Group::class);
                }
                $coverImage = $this->imageService->upload($request->file('cover_image'), Group::class, $group->id, ImageNames::COVER->value);
                $group->images()->cover()->save($coverImage);
            }

            $group->members()->sync($group->user, [
                'is_seen' => true,
                'is_accepted' => true,
                'requester' => GroupRequester::GROUP_OWNER->value,
            ]);
            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group saved failed.');
        }
    }

    public function show($groupId)
    {
        $group = Group::with([
            'user.images',
            'images',
            'category',
            'acceptedMembers.images',
        ])->findOrFail($groupId);

        return compact('group');
    }

    public function showMembers($groupId)
    {
        $loginUserId = auth()->id();
        $group = Group::with(['acceptedMembers.images'])->findOrFail($groupId);

        abort_if(
            $group->isProtected() && !$group->acceptedMembers()->find($loginUserId)?->exists(),
            403,
            'Unauthorized action.'
        );
        if ($loginUserId == $group->user_id) {
            $group = Group::with(['members.images'])->findOrFail($groupId);
        }

        return compact('group');
    }

    public function showInviteUsers($groupId)
    {
        $group = Group::owned()->protected()->findOrFail($groupId);

        $pendingUsersOrMembers = $group->members()->pluck('users.id')->toArray();

        $users = User::user()
            ->whereNotIn('id', $pendingUsersOrMembers)
            ->filter(request([
                'search',
            ]))
            ->latest('created_at')
            ->paginate(config('pagination.general'))
            ->withQueryString();

        return compact('users');
    }

    public function sendInvitation(Request $request)
    {
        $group = Group::owned()->protected()->findOrFail($request->id);

        try {
            DB::beginTransaction();

            foreach ($request->user_ids as $user_id) {
                $user = User::findOrFail($user_id);
                $group->members()->save($user, [
                    'is_seen' => true,
                    'is_accepted' => false,
                    'requester' => GroupRequester::GROUP_OWNER->value,
                ]);
                $this->notificationRepository->add(
                    userId: $user->id,
                    content: "{$group->name} admin sent you an invitation.",
                    imageUrl: $group->images()->profile()->first()->url,
                    type: NotificationType::GROUP->value,
                    redirectLink: route('group.show', $group->id)
                );
            }

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group sending invitation failed.');
        }
    }

    public function acceptInvitation($groupId)
    {
        $group = Group::findOrFail($groupId);
        $loginUser = auth()->user();

        abort_unless($group->unacceptedMembersByGroupOwner()->find($loginUser->id)?->exists(), 403, 'Unauthorized action.');
        try {
            DB::beginTransaction();

            $group->members()->updateExistingPivot($loginUser->id, [
                'is_accepted' => true,
            ]);
            $this->notificationRepository->add(
                userId: $group->user_id,
                content: "{$loginUser->name} accepted your group invitation.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('user.show', $loginUser->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group accepting invitation failed.');
        }
    }

    public function ignoreInvitation($groupId)
    {
        $group = Group::findOrFail($groupId);
        $loginUserId = auth()->id();

        abort_unless($group->unacceptedMembersByGroupOwner()->find($loginUserId)?->exists(), 403, 'Unauthorized action.');
        try {
            DB::beginTransaction();

            $group->members()->detach($loginUserId);

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group ignoring invitation failed.');
        }
    }

    public function join($groupId)
    {
        $loginUser = auth()->user();

        $group = Group::public()->findOrFail($groupId);

        $isUserExist = $group->members()->find($loginUser->id)?->exists();
        abort_if($isUserExist, 409, 'Already requested.');

        try {
            DB::beginTransaction();

            $group->members()->save($loginUser, [
                'is_seen' => true,
                'is_accepted' => true,
                'requester' => GroupRequester::USER->value,
            ]);

            $this->notificationRepository->add(
                userId: $group->user_id,
                content: $loginUser->name . " joined {$group->name}.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show.members', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group joined failed.');
        }
    }

    public function userRequest($groupId)
    {
        $loginUser = auth()->user();

        $group = Group::protected()->findOrFail($groupId);

        $isUserExist = $group->members()->find($loginUser->id)?->exists();
        abort_if($isUserExist, 409, 'Already requested.');

        try {
            DB::beginTransaction();

            $group->members()->save($loginUser, [
                'is_seen' => false,
                'is_accepted' => false,
                'requester' => GroupRequester::USER->value,
            ]);

            $this->notificationRepository->add(
                userId: $group->user_id,
                content: $loginUser->name . " requested to join {$group->name}.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show.members', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group user requested failed.');
        }
    }

    public function userCancel($groupId)
    {
        $loginUser = auth()->user();

        $group = Group::protected()->findOrFail($groupId);
        $user = $group->unacceptedMembersByUser()->findOrFail($loginUser->id);

        try {
            DB::beginTransaction();

            $group->members()->detach($user->id);

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group user canceled failed.');
        }
    }

    public function userSeen($groupId, $userId)
    {
        $group = Group::owned()->protected()->findOrFail($groupId);
        $user = $group->members()->findOrFail($userId);

        $group->members()->updateExistingPivot($user->id, [
            'is_seen' => true,
        ]);
    }

    public function userAccept($groupId, $userId)
    {
        $group = Group::owned()->protected()->findOrFail($groupId);
        $user = $group->unacceptedMembersByUser()->findOrFail($userId);

        try {
            DB::beginTransaction();

            $group->members()->updateExistingPivot($user->id, [
                'is_accepted' => true,
            ]);

            $this->notificationRepository->add(
                userId: $user->id,
                content: "{$group->name} admin accepted your request.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group user accepted failed.');
        }
    }

    public function userDecline($groupId, $userId)
    {
        $group = Group::owned()->protected()->findOrFail($groupId);
        $user = $group->unacceptedMembersByUser()->findOrFail($userId);

        try {
            DB::beginTransaction();

            $group->members()->detach($user->id);

            $this->notificationRepository->add(
                userId: $user->id,
                content: "{$group->name} admin declined your request.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group user declined failed.');
        }
    }

    public function userRemove($groupId, $userId)
    {
        $group = Group::owned()->protected()->findOrFail($groupId);
        $user = $group->acceptedMembers()->findOrFail($userId);

        try {
            DB::beginTransaction();

            $group->members()->detach($user->id);

            $this->notificationRepository->add(
                userId: $user->id,
                content: "{$group->name} admin removed you from group.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group user removed failed.');
        }
    }

    public function leave($groupId)
    {
        $group = Group::findOrFail($groupId);
        $user = $group->acceptedMembers()->findOrFail(auth()->id());

        $group->members()->detach($user->id);
    }

    public function postSeen($groupId, $postId)
    {
        $group = Group::owned()->findOrFail($groupId);
        $post = $group->posts()->findOrFail($postId);

        $post->update([
            'is_seen' => true,
        ]);
    }

    public function postAccept($groupId, $postId)
    {
        $group = Group::owned()->findOrFail($groupId);
        $post = $group->unacceptedPosts()->findOrFail($postId);

        try {
            DB::beginTransaction();

            $post->update([
                'is_accepted' => true,
            ]);

            $this->notificationRepository->add(
                userId: $post->user_id,
                content: "{$group->name} admin accepted your post request.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('post.show', $post->id)
            );

            $members = $group->members;
            foreach ($members as $member) {
                if (in_array($member->id, [$post->user_id, $group->user_id])) {
                    continue;
                }
                if ((bool) SettingRepository::getValueByName(SettingKey::NOTIFICATION_GROUP_POSTS, $member->id)) {
                    $this->notificationRepository->add(
                        userId: $member->id,
                        content: "{$post->user->name} posted in {$group->name}.",
                        imageUrl: $group->images()->profile()->first()->url,
                        type: NotificationType::GROUP->value,
                        redirectLink: route('post.show', $post->id)
                    );
                }
            }

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Post accepted failed.');
        }
    }

    public function postDecline($groupId, $postId)
    {
        $group = Group::owned()->findOrFail($groupId);
        $post = $group->unacceptedPosts()->findOrFail($postId);
        $userId = $post->user_id;

        try {
            DB::beginTransaction();

            $post->update([
                'deleted_type' => DeletedType::GROUP_OWNER,
            ]);
            $post->delete();

            $this->notificationRepository->add(
                userId: $userId,
                content: "{$group->name} admin declined your post request.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Post declined failed.');
        }
    }

    public function postRemove($groupId, $postId)
    {
        $group = Group::owned()->findOrFail($groupId);
        $post = $group->acceptedPosts()->findOrFail($postId);
        $userId = $post->user_id;

        try {
            DB::beginTransaction();

            $post->update([
                'deleted_type' => DeletedType::GROUP_OWNER,
            ]);
            $post->delete();

            $this->notificationRepository->add(
                userId: $userId,
                content: "{$group->name} admin removed your post.",
                imageUrl: $group->images()->profile()->first()->url,
                type: NotificationType::GROUP->value,
                redirectLink: route('group.show', $group->id)
            );

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Post removed failed.');
        }
    }

    public function destroy(Group $group)
    {
        $loginUserId = auth()->id();

        abort_unless($group->user_id == $loginUserId, 403, 'Unauthorized action.');

        try {
            DB::beginTransaction();

            $members = $group->members()->where('user_id', '<>', $loginUserId)->get();
            foreach ($members as $member) {
                $this->notificationRepository->add(
                    userId: $member->id,
                    content: "{$group->name} admin deleted their group.",
                    imageUrl: $group->images()->profile()->first()->url,
                    type: NotificationType::GROUP->value,
                    redirectLink: ''
                );
            }

            $group->update([
                'deleted_type' => DeletedType::GROUP_OWNER,
            ]);
            $group->delete();

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();

            Log::error(__CLASS__ . '::' . __FUNCTION__ . '[line: ' . __LINE__ . ']Message: ' . $e->getMessage());

            throw new Exception('Group deleted failed.');
        }
    }
}
