<?php

namespace App\Services;

use Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Requests\PostRequest;
use App\Http\Traits\CommonTrait;
use App\Http\Traits\BoardTrait;
use App\Models\Board;
use App\Models\BoardMeta;
use App\Models\Post;
use App\Models\PostMeta;
use App\Models\PostLike;
use App\Models\PostTag;
use App\Models\PostLink;
use App\Models\PostFile;
use App\Models\PostHistory;
use App\Models\PostDeleted;
use App\Models\User;
use App\Models\FileLib;
use App\Models\PostBlame;
use App\Models\PostBookmark;
use App\Models\PostFileDownLog;
use App\Models\PostLinkClickLog;
use App\Models\EditorImage;
use App\Models\EmailLib;
use App\Models\Exp;
use App\Models\DTO\ResponseData;
use Exception;

class PostService
{
    use CommonTrait;
    use BoardTrait;

    public Board $boardModel;
    public BoardMeta $boardMetaModel;
    public User $userModel;
    public Post $postModel;
    public PostMeta $postMetaModel;
    public PostHistory $postHistoryModel;
    public PostBookmark $postBookmarkModel;
    public PostLike $postLikeModel;
    public PostTag $postTagModel;
    public PostFile $postFileModel;
    public PostFileDownLog $postFileDownLogModel;
    public PostBlame $postBlameModel;
    public PostLink $postLinkModel;
    public PostLinkClickLog $postLinkClickLogModel;
    public PostDeleted $postDeletedModel;
    public FileLib $fileLib;
    public EditorImage $editorImageModel;

    public function __construct()
    {
        $this->boardModel = new Board();
        $this->boardMetaModel = new BoardMeta();
        $this->postModel = new Post();
        $this->postMetaModel = new PostMeta();
        $this->postHistoryModel = new PostHistory();
        $this->postBookmarkModel = new PostBookmark();
        $this->postLikeModel = new PostLike();
        $this->postTagModel = new PostTag();
        $this->postFileModel = new PostFile();
        $this->postFileDownLogModel = new PostFileDownLog();
        $this->postBlameModel = new PostBlame();
        $this->postLinkModel = new PostLink();
        $this->postLinkClickLogModel = new PostLinkClickLog();
        $this->postDeletedModel = new PostDeleted();
        $this->userModel = new User();
        $this->fileLib = new FileLib();
        $this->editorImageModel = new EditorImage();
    }

    public function meta(int $postID): ?PostMeta
    {
        return $this->postMetaModel->getAllMeta($postID);
    }

    /**
     * 게시글 정보 조회
     */
    public function find(int $postID): Post
    {
        $post = $this->postModel->get($postID);

        if(!$post->exists) {
            return $post;
        }

        $code = $post->board->code;
        $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);

        // 게시글 정보
        $post->viewURL = route('board.post.view', [$code, $postID]);
        $post->beforeViewURL = ($post->beforePostID ? route('board.post.view', [$code, $post->beforePostID]) : null);
        $post->nextViewURL = ($post->nextPostID ? route('board.post.view', [$code, $post->nextPostID]) : null);
        $post->createdAt = $post->created_at->format('Y.m.d H:i:s');
        $post->ipAddress = null;
        $post->isBookmark = false;
        $post->isLike = false;
        $post->isDislike = false;

        // 본문 URL 링크 자동 생성
        if ($boardMeta->item('use_auto_url', 0)) {
            $post->content = $this->urlAutoLink($post->content, $boardMeta->item('content_target_blank', 0)); // Link 새창 여부
        }

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

        if($post->user->exists) {
            $post->user->thumb = $this->profileThumbSrc($post->user->thumb);
            $post->user->createdAt = $post->user->created_at->format('Y.m.d');
        }

        // IP 형식 처리
        $showPostIP = $boardMeta->item('show_post_ip'); // 노출 방법
        if ($showPostIP) {
            $ipDisplayStyle = ($showPostIP == 2 || IS_ADMIN ? '1111' : config('ip_display_style')); // IP 형식
            $post->ipAddress = $this->ipAddrMasking($post->ip_address, $ipDisplayStyle);
        }

        // 즐겨찾기
        if ($boardMeta->item('use_bookmark', 0)) {
            $post->isBookmark = $this->postBookmarkModel->isAlready($post, UID);
        }

        // 좋아요
        if ($boardMeta->item('use_post_like', 0)) {
            $post->isLike = $this->postLikeModel->isAlready($post, UID, LIKE);
        }

        // 싫어요
        if ($boardMeta->item('use_post_dislike', 0)) {
            $post->isDislike = $this->postLikeModel->isAlready($post, UID, DISLIKE);
        }

        // 태그
        $post->tags = $post->postTag;

        // 첨부파일
        $post->files = $post->postFile;

        // 링크 첨부
        $post->links = $post->postLink;

        return $post;
    }

    /**
     * 게시글 등록 처리
     */
    public function register(PostRequest $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $user = $request->user();
            $boardID = intval($request->post('bid'));
            $boardMeta = $request->boardMeta;

            // 게시글 첨부 사진 저장
            $content = $this->getContent($request);
            $thumbnail = $this->getThumbnail($content,
                ($boardMeta->item('board_layout_type') == BOARD_LAYOUT_TYPE_1 ? NO_IMAGE_NORMAL_SRC : null)
            );
            $imageRows = $this->getImageRows($content);
            $videoRows = $this->getVideoRows($content);

            // 게시글 정보 저장
            $post = $this->postModel->register([
                'board_id' => $boardID,
                'board_category_id' => $request->post('category'),
                'user_id' => $user?->id,
                'thumbnail' => $thumbnail,
                'subject' => trim($request->post('subject')),
                'content' => $content,
                'sid' => $user?->sid,
                'username' => $request->post('username', $user?->name),
                'email' => $user?->email,
                'password' => $request->post('password'),
                'comment_rows' => 0,
                'comment_updated_at' => null,
                'is_reply' => 0,
                'is_personal' => $boardMeta->item('use_personal', 0),
                'is_secret' => ($boardMeta->item('use_post_secret', 0) == '2' ? 1 : $request->post('is_secret', 0)),
                'is_notice' => $request->post('is_notice', 0),
                'is_speaker' => $request->post('is_speaker', 0),
                'is_html' => $boardMeta->item('use_post_dhtml', 0),
                'use_comment' => $boardMeta->item('use_comment', 0),
                'receive_email' => 0,
                'hit' => 0,
                'like' => 0,
                'dislike' => 0,
                'blame' => 0,
                'device_type' => DEVICE_TYPE,
                'file_rows' => 0,
                'image_rows' => $imageRows,
                'video_rows' => $videoRows,
                'link_rows' => 0,
                'tag_rows' => 0,
                'is_delete' => 0,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'deleted_at' => null,
                'updated_at' => null,
                'created_at' => now()
            ]);

            // 게시글 Tag 저장
            $this->postTagModel->register($post, $request->post('tags'));

            // 게시글 Link 저장
            $this->postLinkModel->register($post, $request->post('links'));

            // 게시글 File 저장
            $this->postFileModel->register($post, $request->file('files'));

            // 게시글 기록 저장
            if ($boardMeta->use_post_history) {
                $this->postHistoryModel->register($post,'게시글 등록');
            }

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

            // 태그 캐시 삭제
            Cache::forget('tags');

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

        return $response;
    }

    /**
     * 게시글 수정 처리
     */
    public function updater(PostRequest $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);
            $thumbnail = $this->getThumbnail($content,
                ($boardMeta->item('board_layout_type') == BOARD_LAYOUT_TYPE_1 ? NO_IMAGE_NORMAL_SRC : null)
            );
            $imageRows = $this->getImageRows($content);
            $videoRows = $this->getVideoRows($content);

            // 게시글 기록 저장
            if ($boardMeta->item('use_post_history', 0)) {
                $this->postHistoryModel->updater($request->post, '게시글 수정');
            }

            // 게시글 정보 저장
            $post = $this->postModel->updater($postID, [
                'board_id' => $boardID,
                'board_category_id' => $request->post('category'),
                'user_id' => $user?->id,
                'thumbnail' => $thumbnail,
                'subject' => trim($request->post('subject')),
                'content' => $content,
                'sid' => $user?->sid,
                'username' => $request->post('username', $user?->name),
                'email' => $user?->email,
                'password' => $request->post('password'),
                'is_personal' => $boardMeta->item('use_personal', 0),
                'is_secret' => ($boardMeta->item('use_post_secret', 0) == '2' ? 1 : $request->post('is_secret', 0)),
                'is_notice' => $request->post('is_notice', 0),
                'is_speaker' => $request->post('is_speaker', 0),
                'is_html' => $boardMeta->item('use_post_dhtml', 0),
                'use_comment' => $boardMeta->item('use_comment', 0),
                'receive_email' => 0,
                'device_type' => DEVICE_TYPE,
                'file_rows' => 0,
                'image_rows' => $imageRows,
                'video_rows' => $videoRows,
                'link_rows' => 0,
                'tag_rows' => 0,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'updated_at' => now()
            ]);

            // 게시글 Tag 저장
            $this->postTagModel->updater($post, $request->post('tags'));

            // 게시글 Link 저장
            $this->postLinkModel->updater($post, $request->post('links'));

            // 게시글 File 저장
            $this->postFileModel->updater($post, $request->post('file_is_delete', []), $request->file('files'));

            // 태그 캐시 삭제
            Cache::forget('tags');

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

        return $response;
    }

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

        try {

            $post = $request->post;

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

            $user = $request->user();

            // 게시글 삭제 여부 승인
            $this->postModel->remove($post, $user);

            // 태그 캐시 삭제
            Cache::forget('tags');

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

        return $response;
    }

    /**
     * 게시글 즐겨찾기
     */
    public function bookmark(Request $request, ResponseData $response): ResponseData
    {
        DB::beginTransaction();

        try {

            $post = $this->postModel->findOrNew(
                $request->input('pid')
            );

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

            $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);
            if (!$boardMeta->item('use_bookmark', 0)) {
                throw new Exception('즐겨찾기 기능은 사용하지 않습니다.');
            }

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

            if ($this->postBookmarkModel->isAlready($post, $userID)) {
                $this->postBookmarkModel->remove($post, $userID);
            } else {
                $this->postBookmarkModel->register([
                    'board_id' => $post->board_id,
                    'post_id' => $post->id,
                    'user_id' => $userID,
                    'ip_address' => IP_ADDRESS,
                    'user_agent' => USER_AGENT,
                    'created_at' => now()
                ]);
            }

            // 즐겨찾기 수 저장
            $this->postMetaModel->save($post->id, [
                'bookmark_rows' => $this->postBookmarkModel->rows($post)
            ]);

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

        return $response;
    }

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

        try {

            $post = $this->postModel->findOrNew(
                $request->input('pid')
            );

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

            $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);
            if (!$boardMeta->item('use_blame', 0)) {
                throw new Exception('신고 기능은 사용하지 않습니다.');
            }

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

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

            $this->postBlameModel->register([
                'board_id' => $post->board_id,
                'post_id' => $post->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->postModel->increaseBlame($post->id);

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

        return $response;
    }

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

        try {

            $post = $this->postModel->findOrNew(
                $request->input('pid')
            );

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

            $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);
            if (!$boardMeta->item('use_post_like', 0)) {
                throw new Exception('추천 기능은 사용하지 않습니다.');
            }

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

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

            $likeInfo = $this->postLikeModel->info($post, $userID);
            if ($likeInfo->exists) { // 이미 추천함
                $this->postLikeModel->remove($post, $userID);
                if ($likeInfo->type == LIKE) {
                    $this->postModel->decreaseLike($post->id);
                } else if ($likeInfo->type == DISLIKE) {
                    $this->postLikeModel->register($saveData);
                    $this->postModel->increaseLike($post->id);
                    $this->postModel->decreaseDisLike($post->id);
                }
            } else { // 추천
                $this->postLikeModel->register($saveData);
                $this->postModel->increaseLike($post->id);
            }

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

        return $response;
    }

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

        try {

            $post = $this->postModel->findOrNew(
                $request->input('pid')
            );

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

            $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);
            if (!$boardMeta->item('use_post_dislike', 0)) {
                throw new Exception('비추천 기능은 사용하지 않습니다.');
            }

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

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

            $likeInfo = $this->postLikeModel->info($post, $userID);
            if ($likeInfo->exists) { // 이미 추천함
                $this->postLikeModel->remove($post, $userID);
                if ($likeInfo->type == DISLIKE) {
                    $this->postModel->decreaseDisLike($post->id);
                } else if ($likeInfo->type == LIKE) {
                    $this->postLikeModel->register($saveData);
                    $this->postModel->increaseDisLike($post->id);
                    $this->postModel->decreaseLike($post->id);
                }
            } else {
                $this->postLikeModel->register($saveData);
                $this->postModel->increaseDisLike($post->id);
            }

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

        return $response;
    }

    /**
     * 게시글 첨부파일 다운로드
     */
    public function download(Request $request): object
    {
        $ret = (object)[
            'path' => null,
            'name' => null
        ];

        $file = $this->postFileModel->info(
            $request->post('fid')
        );

        if (!$file->exists) {
            return $ret;
        }

        // 다운로드 횟수 증가
        $file->increaseDownload($file->id);

        // 다운로드 기록
        $boardMeta = $this->boardMetaModel->getAllMeta($file->board_id);
        if ($boardMeta->item('use_download_log', 0)) {
            $this->postFileDownLogModel->register([
                'post_file_id' => $file->id,
                'board_id' => $file->board_id,
                'post_id' => $file->post_id,
                'user_id' => $file->user_id,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'created_at' => now()
            ]);
        }

        $ret->path = $file->file_path;
        $ret->name = $file->origin_name;

        return $ret;
    }

    /**
     * 게시글 Link 클릭
     */
    public function linked(Request $request): string|null
    {
        $link = $this->postLinkModel->info(
            $request->post('lid')
        );

        if (!$link->exists) {
            return null;
        }

        // Link 클릭 횟수 증가
        $link->increaseHit($link->id);

        // Link 클릭 기록
        $boardMeta = $this->boardMetaModel->getAllMeta($link->board_id);
        if ($boardMeta->item('use_link_click_log', 0)) {
            $this->postLinkClickLogModel->register([
                'post_link_id' => $link->id,
                'board_id' => $link->board_id,
                'post_id' => $link->post_id,
                'user_id' => $link->user_id,
                'ip_address' => IP_ADDRESS,
                'user_agent' => USER_AGENT,
                'created_at' => now()
            ]);
        }

        return ($link->url ?? null);
    }

    /**
     * 게시글 보기 조회 수 증가
     */
    public function addHit(Post $post): void
    {
        if($this->postModel->addHit($post)) {
            $this->postModel->increaseHit($post->id);
        }
    }

    /**
     * 게시글 작성 개수
     */
    public function userPostRows(Board $board, ?User $user): int
    {
        return $this->postModel->where([
            ['board_id', $board->id],
            ['user_id', $user?->id],
            ['ip_address', IP_ADDRESS]
        ])->whereRaw('DATE(created_at) = CURDATE()')->count();
    }

    /**
     * 게시글 추천 여부
     */
    public function userPostIsLike(Post $post, User $user): int
    {
        return $this->postLikeModel->where([
            ['board_id', $post->board_id],
            ['post_id', $post->id],
            ['user_id', $user->id]
        ])->orWhere('ip_address', IP_ADDRESS)->exists();
    }

    /**
     * 첨부 사진 저장
     */
    private function getContent(PostRequest $request): string
    {
        // 게시글 이미지 저장
        $boardMeta = $request->boardMeta;
        $postID = $this->postModel->lastedKey();
        $content = trim($request->post('content'));

        if ($boardMeta->item('save_external_image', 0)) {
            // link
            $content = $this->fileLib->saveImageFromUrl($content, UPLOAD_PATH_POST, $postID);
        }

        // blob
        $content = $this->fileLib->saveImageFromBlob($content, UPLOAD_PATH_POST, $postID);

        // 이미지 DB 기록
        $images = $this->fileLib->getImageFromContent($content);

        if(count($images) > 0) {
            foreach($images as $img) {
                $this->editorImageModel->register([
                    'target_type' => EDITOR_IMG_TYPE_1,
                    'target_id' => $postID,
                    '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(Post $post, User $user, int $type): bool
    {
        if(!$post->exists) {
            return false;
        }

        $boardMeta = $this->boardMetaModel->getAllMeta($post->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", $post->board_id, $post->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(Post $post, string $sendMailFormType): bool
    {
        if (!in_array($sendMailFormType, MAP_SEND_MAIL_TYPE)) {
            return false;
        }

        $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);

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

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