<?php

namespace App\Services;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Requests\CommentRequest;
use App\Http\Traits\CommonTrait;
use App\Http\Traits\BoardTrait;
use App\Models\User;
use App\Models\Board;
use App\Models\BoardMeta;
use App\Models\Post;
use App\Models\Comment;
use App\Models\CommentMeta;
use App\Models\CommentHistory;
use App\Models\CommentLike;
use App\Models\CommentBlame;
use App\Models\CommentDeleted;
use App\Models\FileLib;
use App\Models\EmailLib;
use App\Models\EditorImage;
use App\Models\Exp;
use App\Models\DTO\ResponseData;
use App\Models\DTO\SearchData;
use Exception;

class CommentService
{
    use CommonTrait;
    use BoardTrait;

    public Board $boardModel;
    public BoardMeta $boardMetaModel;
    public Post $postModel;
    public Comment $commentModel;
    public CommentMeta $commentMetaModel;
    public CommentHistory $commentHistoryModel;
    public CommentLike $commentLikeModel;
    public CommentBlame $commentBlameModel;
    public CommentDeleted $commentDeletedModel;
    public User $userModel;
    public FileLib $fileLib;
    public EmailLib $emailLib;
    public EditorImage $editorImageModel;

    public function __construct()
    {
        $this->boardModel = new Board();
        $this->boardMetaModel = new BoardMeta();
        $this->postModel = new Post();
        $this->commentModel = new Comment();
        $this->commentMetaModel = new CommentMeta();
        $this->commentHistoryModel = new CommentHistory();
        $this->commentLikeModel = new CommentLike();
        $this->commentBlameModel = new CommentBlame();
        $this->commentDeletedModel = new CommentDeleted();
        $this->userModel = new User();
        $this->fileLib = new FileLib();
        $this->emailLib = new EmailLib();
        $this->editorImageModel = new EditorImage();
    }

    public function meta(int $commentID): ?CommentMeta
    {
        return $this->commentMetaModel->getAllMeta($commentID);
    }

    public function find(int $commentID): Comment
    {
        return $this->_filter($this->commentModel->get($commentID));
    }

    /**
     * 댓글 전체 수
     */
    public function total(int $postID): int
    {
        return $this->commentModel->total($postID);
    }

    /**
     * 전체 댓글 목록 조회
     */
    public function list(string $code, int $postID, SearchData $params): object
    {
        $comments = $this->commentModel->getList($code, $postID, $params->page, $params->offset, $params->perPage, $params->sort);

        if($comments->total > 0) {
            $num = listNum($comments->total, $params->page, $params->perPage);
            foreach($comments->list as $i => $row) {
                $row = $this->commentModel->newInstance(get_object_vars($row), true);
                $row->num = $num--;
                $comments->list[$i] = $this->_filter($row);
            }
        }

        return $comments;
    }

    /**
     * 회원 댓글 조회
     */
    public function userList(int $userID, SearchData $params): object
    {
        $comments = $this->commentModel->getUserList($userID, $params);

        if($comments->total > 0) {
            $num = listNum($comments->total, $params->page, $params->perPage);
            foreach($comments->list as $i => $row) {
                $row = $this->commentModel->newInstance(get_object_vars($row), true);
                $row->num = $num--;
                $row->page = $this->commentModel->findPageNumber($row->id, $params->perPage);
                $comments->list[$i] = $this->_filter($row);
            }
        }

        return $comments;
    }

    /**
     * 댓글 정보 가공
     */
    private function _filter(Comment $comment): Comment
    {
        if(!$comment->exists) {
            return $comment;
        }

        $boardMeta = $this->boardMetaModel->getAllMeta($comment->board_id);
        $comment->user->thumb = $this->profileThumbSrc($comment->user->thumb);
        $comment->viewURL = route('board.post.view', [
            $comment->board->code, $comment->post_id
        ]);

        // 익명 게시판일 경우
        if(
            $boardMeta->item('use_anonymous', 0)
            && (!$comment->user->is_admin || !$boardMeta->item('anonymous_except_admin', 0))
            && $comment->user->id
        ) {
            $comment->username = $this->createAnonymousName(
                $boardMeta->item('anonymous_name'), $comment->user->id, $comment->post_id
            );
        }

        // 댓글 설정 정보
        $commentMeta = $this->meta($comment->id);

        // 관리자에 의한 삭제
        $comment->isBlind = $commentMeta->item('is_blind', 0);

        // IP 표시 형식
        $showCommentIP = $boardMeta->item('show_comment_ip'); // 노출 방법
        if ($showCommentIP) {
            $ipDisplayStyle = ($showCommentIP == 2 || IS_ADMIN ? '1111' : config('ip_display_style')); // IP 형식
            $comment->ipAddress = $this->ipAddrMasking($comment->ip_address, $ipDisplayStyle);
        }

        // 신고 누적으로 숨김
        $comment->isBlame = false;
        if($commentBlameBlindCount = $boardMeta->item('comment_blame_blind_count', 0)) {
            $comment->isBlame = ($comment->blame >= $commentBlameBlindCount);
        }

        // 좋아요
        $comment->isLike = $this->commentLikeModel->isAlready($comment, UID, LIKE);

        // 싫어요
        $comment->isDislike = $this->commentLikeModel->isAlready($comment,UID, DISLIKE);

        // 작성일시
        $comment->createdAt = $this->dateFormat($comment->created_at);

        return $comment;
    }

    /**
     * 댓글 등록
     */
    public function register(CommentRequest $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $user = $request->user();
            $boardMeta = $request->boardMeta;
            $boardID = intval($request->post('bid'));
            $postID = intval($request->post('pid'));
            $content = $this->getContent($request);

            // 댓글 정보 저장
            $comment = $this->commentModel->register([
                'board_id' => $boardID,
                'post_id' => $postID,
                'user_id' => $user?->id,
                'parent_id' => null,
                'is_reply' => 0,
                'content' => $content,
                'sid' => $user?->sid,
                'username' => $request->post('username', $user?->name),
                'email' => $user?->email,
                'password' => $request->post('password'),
                'reply' => 0,
                'like' => 0,
                'dislike' => 0,
                'blame' => 0,
                'device_type' => DEVICE_TYPE,
                'is_secret' => ($boardMeta->item('use_comment_secret', 0) == '2' ? 1 : $request->post('is_secret', 0)),
                'is_delete' => 0,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'deleted_at' => null,
                'updated_at' => null,
                'created_at' => now()
            ]);

            // 게시판 댓글 수 갱신
            $this->boardModel->updateCommentRows($boardID);

            // 게시글 댓글 수 갱신
            $this->postModel->updateCommentRows($postID);

            // 댓글 Meta 저장
            $this->commentMetaModel->save($comment->id, [
                'is_blind' => 0,
                'exec_blind_key' => null
            ]);

            // 댓글 기록 저장
            if ($boardMeta->item('use_comment_history', 0)) {
                $this->commentHistoryModel->register($comment, '댓글 등록');
            }

            $response->comment = $comment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 댓글 수정
     */
    public function updater(CommentRequest $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $user = $request->user();
            $boardMeta = $request->boardMeta;
            $boardID = intval($request->post('bid'));
            $postID = intval($request->post('pid'));
            $commentID = intval($request->post('cid'));
            $content = $this->getContent($request);

            // 댓글 기록 저장
            if($boardMeta->item('use_comment_history', 0)) {
                $this->commentHistoryModel->updater($request->comment, '댓글 수정');
            }

            // 댓글 수정
            $comment = $this->commentModel->updater($commentID, [
                'board_id' => $boardID,
                'post_id' => $postID,
                'user_id' => $user?->id,
                'content' => $content,
                'is_secret' => $request->post('is_secret', 0),
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'updated_at' => now()
            ]);

            // 댓글 작성시 글 수정 시각 갱신
            if($boardMeta->item('update_order_on_comment', 0)) {
                $this->postModel->updater($postID, [
                    'updated_at' => now()
                ]);
            }

            $response->comment = $comment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 댓글 답글
     */
    public function reply(CommentRequest $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $user = $request->user();
            $boardMeta = $request->boardMeta;
            $boardID = intval($request->post('bid'));
            $postID = intval($request->post('pid'));
            $commentID = intval($request->post('cid'));
            $content = $this->getContent($request);

            // 답글 저장
            $newComment = $this->commentModel->reply($commentID, [
                'board_id' => $boardID,
                'post_id' => $postID,
                'user_id' => $user?->id,
                'parent_id' => null,
                'is_reply' => 0,
                'content' => $content,
                'sid' => $user?->sid,
                'username' => $request->post('username', $user?->name),
                'email' => $user?->email,
                'password' => $request->post('password'),
                'reply' => 0,
                'like' => 0,
                'dislike' => 0,
                'blame' => 0,
                'device_type' => DEVICE_TYPE,
                'is_secret' => ($boardMeta->item('use_comment_secret', 0) == '2' ? 1 : $request->post('is_secret', 0)),
                'is_delete' => 0,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'deleted_at' => null,
                'updated_at' => null,
                'created_at' => now()
            ]);

            // 부모 댓글 답변 여부 등록
            $this->commentModel->updater($commentID, [
                'is_reply' => 1
            ]);

            // 게시판 댓글 수 갱신
            $this->boardModel->updateCommentRows($boardID);

            // 게시글 댓글 수 갱신
            $this->postModel->updateCommentRows($postID);

            // 부모 댓글 수 갱신
            $this->commentModel->updateCommentRows($commentID);

            // 댓글 Meta 저장
            $this->commentMetaModel->save($newComment->id, [
                'is_blind' => 0,
                'exec_blind_key' => null
            ]);

            // 답글 등록 기록
            if($boardMeta->item('use_comment_history', 0)) {
                $this->commentHistoryModel->register($newComment, '답글 등록');
            }

            $response->comment = $newComment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 댓글 삭제
     */
    public function delete(Request $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $comment = $request->comment;

            // 댓글 존재 확인
            if (!$comment->exists) {
                throw new Exception('댓글이 존재 하지 않습니다.');
            }

            $userID = $request->user()?->id;

            // 댓글 삭제 여부 승인
            $this->commentModel->updater($comment->id, [
                'is_delete' => 1,
                'deleted_at' => now()
            ]);

            // 댓글 삭제 정보 추가
            $this->commentDeletedModel->register([
                'board_id' => $comment->board_id,
                'post_id' => $comment->post_id,
                'comment_id' => $comment->id,
                'user_id' => $userID,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'created_at' => now()
            ]);

            // 게시판 댓글 수 갱신
            $this->boardModel->updateCommentRows($comment->board_id);

            // 게시판 댓글 수 갱신
            $this->postModel->updateCommentRows($comment->post_id);

            // 부모 댓글 수 갱신
            $this->commentModel->updateCommentRows($comment->id);

            $response->comment = $comment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 댓글 신고
     */
    public function blame(Request $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $comment = $this->commentModel->findOrNew(
                $request->post('cid')
            );

            // 댓글 존재 확인
            if (!$comment->exists) {
                throw new Exception('댓글이 존재 하지 않습니다.');
            }

            $userID = $request->user()->id;

            // 이미 신고했는지 확인
            if ($this->commentBlameModel->isAlready($comment, $userID)) {
                throw new Exception('이미 신고하셨습니다.');
            }

            $this->commentBlameModel->register([
                'board_id' => $comment->board_id,
                'post_id' => $comment->post_id,
                'comment_id' => $comment->id,
                'user_id' => $userID,
                'type' => $request->post('type'),
                'reason' => $request->post('reason'),
                'status' => 0,
                'memo' => null,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'created_at' => now()
            ]);

            $this->commentModel->increaseBlame($comment->id);

            $response->comment = $comment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 댓글 좋아요
     */
    public function like(Request $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $comment = $this->commentModel->findOrNew(
                $request->post('cid')
            );

            // 댓글 존재 확인
            if (!$comment->exists) {
                throw new Exception('댓글이 존재 하지 않습니다.');
            }

            $userID = $request->user()->id;

            $saveData = [
                'board_id' => $comment->board_id,
                'post_id' => $comment->post_id,
                'comment_id' => $comment->id,
                'user_id' => $userID,
                'type' => LIKE,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'created_at' => now()
            ];

            $likeInfo = $this->commentLikeModel->info($comment, $userID);
            if ($likeInfo->exists) {
                $this->commentLikeModel->remove($comment, $userID);
                if ($likeInfo->type == LIKE) {
                    $this->commentModel->decreaseLike($comment->id);
                } else if ($likeInfo->type == DISLIKE) {
                    $this->commentLikeModel->register($saveData);
                    $this->commentModel->increaseLike($comment->id);
                    $this->commentModel->decreaseDisLike($comment->id);
                }
            } else {
                $this->commentLikeModel->register($saveData);
                $this->commentModel->increaseLike($comment->id);
            }

            $response->comment = $comment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 댓글 싫어요
     */
    public function dislike(Request $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $comment = $this->commentModel->findOrNew(
                $request->post('cid')
            );

            // 댓글 존재 확인
            if (!$comment->exists) {
                throw new Exception('댓글이 존재 하지 않습니다.');
            }

            $userID = $request->user()->id;

            $saveData = [
                'board_id' => $comment->board_id,
                'post_id' => $comment->post_id,
                'comment_id' => $comment->id,
                'user_id' => $userID,
                'type' => DISLIKE,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'created_at' => now()
            ];

            $likeInfo = $this->commentLikeModel->info($comment, $userID);
            if ($likeInfo->exists) {
                $this->commentLikeModel->remove($comment, $userID);
                if ($likeInfo->type == DISLIKE) {
                    $this->commentModel->decreaseDisLike($comment->id);
                } else if ($likeInfo->type == LIKE) {
                    $this->commentLikeModel->register($saveData);
                    $this->commentModel->increaseDisLike($comment->id);
                    $this->commentModel->decreaseLike($comment->id);
                }
            } else {
                $this->commentLikeModel->register($saveData);
                $this->commentModel->increaseDisLike($comment->id);
            }

            $response->comment = $comment;
            DB::commit();
        } catch (Exception $e) {
            $response = $response::fromException($e);
            DB::rollBack();
        }

        return $response;
    }

    /**
     * 게시글에 내 댓글이 몇개인지 확인
     */
    public function userCommentRows(Post $post, User $user): int
    {
        return $this->commentModel->where([
            ['board_id', $post->board_id],
            ['post_id', $post->id],
            ['user_id', $user->id],
        ])->count();
    }

    /**
     * 댓글 에디터 내용 처리
     */
    private function getContent(CommentRequest $request): string
    {
        // 게시글 이미지 저장
        $boardMeta = $request->boardMeta;
        $postID = $request->pid;
        $commentID = $this->commentModel->lastedKey();
        $content = trim($request->post('content'));

        if ($boardMeta->use_personal)
        {
            $addPath = ($postID . DIRECTORY_SEPARATOR . $commentID);

            if ($boardMeta->save_external_image) {
                // link
                $content = $this->fileLib->saveImageFromUrl($content, UPLOAD_PATH_COMMENT, $addPath);
            }

            // blob
            $content = $this->fileLib->saveImageFromBlob($content, UPLOAD_PATH_COMMENT, $addPath);

            // 이미지 DB 기록
            $images = $this->fileLib->getImageFromContent($content);
            if(count($images) > 0) {
                foreach($images as $img) {
                    $this->editorImageModel->register([
                        'target_type' => EDITOR_IMG_TYPE_2,
                        'target_id' => $addPath,
                        'user_id' => UID,
                        'origin_name' => $img->origin,
                        'file_name' => $img->name,
                        'file_path' => $img->path,
                        'file_url' => $img->url,
                        'file_size' => $img->size,
                        'file_type' => $img->type,
                        'width' => $img->width,
                        'height' => $img->height,
                        'ip_address' => IP_ADDRESS,
                        'user_agent' => USER_AGENT
                    ]);
                }
            }
        }
        return $content;
    }

    /**
     * 댓글 경험치 지급
     */
    public function setUserExp(Comment $comment, User $user, int $type): bool
    {
        $boardMeta = $this->boardMetaModel->getAllMeta($comment->board_id);

        if (!$boardMeta->item('use_exp', 0)) {
            return false;
        }

        $column = MAP_EXP_TYPE[$type];
        $content = MAP_EXP_CONTENT[$type];
        $value = intval($boardMeta->{$column});
        $relatedID = sprintf("%d-%d-%d", $comment->board_id, $comment->post_id, $comment->id);

        // 경험치 지급 구분
        return (new Exp)->register([
            'user_id' => $user->id,
            'content' => $content,
            'value' => $value,
            'type' => $type,
            'related_id' => $relatedID,
            'method' => request()->getMethod(),
            'created_at' => now()
        ]);
    }

    /**
     * 게시글 관련 메일 발송
     */
    public function sendEmailNotify(Comment $comment, string $sendMailFormType): bool
    {
        if(!in_array($sendMailFormType, MAP_SEND_MAIL_TYPE)) {
            return false;
        }

        $post = $comment->post;
        $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);
        $sendMailType = str_replace('_form', '', $sendMailFormType);

        // 최고 관리자에게 발송
        $admin = ($boardMeta->item($sendMailType . '_super_admin', 0) ? $this->userModel->getMaster() : null);

        // 게시글 작성자에게 발송
        $postWriter = ($boardMeta->item($sendMailType . '_post_writer', 0) ? $post->user : null);

        // 댓글 작성자에게 발송
        $commentWriter = ($boardMeta->item($sendMailType . '_comment_writer', 0) ? $comment->user : null);

        if(!$admin && !$postWriter && !$commentWriter) {
            return false;
        }

        // 1:1 문의 답변 완료 처리
        $post->updater($post->id, [
            'is_reply' => 1
        ]);

        return (new EmailLib)->send($sendMailFormType, $admin, $postWriter, $commentWriter);
    }
}
