<?php
namespace Misuzu\Http\Handlers;

use RuntimeException;
use Misuzu\DB;
use Misuzu\Pagination;
use Misuzu\Template;
use Misuzu\Comments\CommentsCategory;
use Misuzu\Comments\CommentsEx;
use Misuzu\Feeds\Feed;
use Misuzu\Feeds\FeedItem;
use Misuzu\Feeds\AtomFeedSerializer;
use Misuzu\Feeds\RssFeedSerializer;
use Misuzu\News\NewsCategoryInfo;
use Misuzu\Parsers\Parser;
use Misuzu\Users\User;

final class NewsHandler extends Handler {
    private function fetchPostInfo(array $postInfos, array $categoryInfos = []): array {
        $news = $this->context->getNews();
        $comments = $this->context->getComments();
        $posts = [];
        $userInfos = [];

        foreach($postInfos as $postInfo) {
            $userId = $postInfo->getUserId();
            $categoryId = $postInfo->getCategoryId();

            if(array_key_exists($userId, $userInfos)) {
                $userInfo = $userInfos[$userId];
            } else {
                try {
                    $userInfo = User::byId($userId);
                } catch(RuntimeException $ex) {
                    $userInfo = null;
                }

                $userInfos[$userId] = $userInfo;
            }

            if(array_key_exists($categoryId, $categoryInfos))
                $categoryInfo = $categoryInfos[$categoryId];
            else
                $categoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo);

            $commentsCount = $postInfo->hasCommentsCategoryId()
                ? $comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0;

            $posts[] = [
                'post' => $postInfo,
                'category' => $categoryInfo,
                'user' => $userInfo,
                'comments_count' => $commentsCount,
            ];
        }

        return $posts;
    }

    public function index($response, $request) {
        $news = $this->context->getNews();

        $categories = $news->getAllCategories();
        $pagination = new Pagination($news->countAllPosts(onlyFeatured: true), 5);

        if(!$pagination->hasValidOffset())
            return 404;

        $postInfos = $news->getAllPosts(onlyFeatured: true, pagination: $pagination);
        $posts = $this->fetchPostInfo($postInfos);

        $response->setContent(Template::renderRaw('news.index', [
            'news_categories' => $categories,
            'news_posts' => $posts,
            'news_pagination' => $pagination,
        ]));
    }

    public function viewCategory($response, $request, string $fileName) {
        $news = $this->context->getNews();

        $categoryId = pathinfo($fileName, PATHINFO_FILENAME);
        $type = pathinfo($fileName, PATHINFO_EXTENSION);

        try {
            $categoryInfo = $news->getCategoryById($categoryId);
        } catch(RuntimeException $ex) {
            return 404;
        }

        if($type === 'atom')
            return $this->feedCategoryAtom($response, $request, $categoryInfo);
        elseif($type === 'rss')
            return $this->feedCategoryRss($response, $request, $categoryInfo);
        elseif($type !== '')
            return 404;

        $pagination = new Pagination($news->countPostsByCategory($categoryInfo), 5);
        if(!$pagination->hasValidOffset())
            return 404;

        $postInfos = $news->getPostsByCategory($categoryInfo, pagination: $pagination);
        $posts = $this->fetchPostInfo($postInfos, [$categoryInfo->getId() => $categoryInfo]);

        $response->setContent(Template::renderRaw('news.category', [
            'news_category' => $categoryInfo,
            'news_posts' => $posts,
            'news_pagination' => $pagination,
        ]));
    }

    public function viewPost($response, $request, string $postId) {
        $news = $this->context->getNews();
        $comments = $this->context->getComments();

        try {
            $postInfo = $news->getPostById($postId);
        } catch(RuntimeException $ex) {
            return 404;
        }

        if(!$postInfo->isPublished() || $postInfo->isDeleted())
            return 404;

        $categoryInfo = $news->getCategoryByPost($postInfo);

        $comments = $this->context->getComments();

        if($postInfo->hasCommentsCategoryId()) {
            $commentsCategory = $comments->getCategoryById($postInfo->getCommentsCategoryId());
        } else {
            $commentsCategory = $comments->ensureCategory($postInfo->getCommentsCategoryName());
            $news->updatePostCommentCategory($postInfo, $commentsCategory);
        }

        $userInfo = null;
        if($postInfo->hasUserId())
            try {
                $userInfo = User::byId($postInfo->getUserId());
            } catch(RuntimeException $ex) {}

        $comments = new CommentsEx($comments);

        $response->setContent(Template::renderRaw('news.post', [
            'post_info' => $postInfo,
            'post_category_info' => $categoryInfo,
            'post_user_info' => $userInfo,
            'comments_info' => $comments->getCommentsForLayout($commentsCategory),
        ]));
    }

    private function createFeed(string $feedMode, ?NewsCategoryInfo $categoryInfo, array $posts): Feed {
        $hasCategory = $categoryInfo !== null;
        $siteName = $this->context->getConfig()->getString('site.name', 'Misuzu');

        $feed = (new Feed)
            ->setTitle($siteName . ' » ' . ($hasCategory ? $categoryInfo->getName() : 'Featured News'))
            ->setDescription($hasCategory ? $categoryInfo->getDescription() : 'A live featured news feed.')
            ->setContentUrl(url_prefix(false) . ($hasCategory ? url('news-category', ['category' => $categoryInfo->getId()]) : url('news-index')))
            ->setFeedUrl(url_prefix(false) . ($hasCategory ? url("news-category-feed-{$feedMode}", ['category' => $categoryInfo->getId()]) : url("news-feed-{$feedMode}")));

        foreach($posts as $post) {
            $postInfo = $post['post'];
            $userInfo = $post['user'];

            $userId = 0;
            $userName = 'Author';
            if($userInfo !== null) {
                $userId = $userInfo->getId();
                $userName = $userInfo->getUsername();
            }

            $postUrl = url_prefix(false) . url('news-post', ['post' => $postInfo->getId()]);
            $commentsUrl = url_prefix(false) . url('news-post-comments', ['post' => $postInfo->getId()]);
            $authorUrl = url_prefix(false) . url('user-profile', ['user' => $userId]);

            $feedItem = (new FeedItem)
                ->setTitle($postInfo->getTitle())
                ->setSummary($postInfo->getFirstParagraph())
                ->setContent(Parser::instance(Parser::MARKDOWN)->parseText($postInfo->getBody()))
                ->setCreationDate($postInfo->getCreatedTime())
                ->setUniqueId($postUrl)
                ->setContentUrl($postUrl)
                ->setCommentsUrl($commentsUrl)
                ->setAuthorName($userName)
                ->setAuthorUrl($authorUrl);

            if(!$feed->hasLastUpdate() || $feed->getLastUpdate() < $feedItem->getCreationDate())
                $feed->setLastUpdate($feedItem->getCreationDate());

            $feed->addItem($feedItem);
        }

        return $feed;
    }

    private function fetchPostInfoForFeed(array $postInfos): array {
        $news = $this->context->getNews();
        $posts = [];
        $userInfos = [];

        foreach($postInfos as $postInfo) {
            $userId = $postInfo->getUserId();

            if(array_key_exists($userId, $userInfos)) {
                $userInfo = $userInfos[$userId];
            } else {
                try {
                    $userInfo = User::byId($userId);
                } catch(RuntimeException $ex) {
                    $userInfo = null;
                }

                $userInfos[$userId] = $userInfo;
            }

            $posts[] = [
                'post' => $postInfo,
                'user' => $userInfo,
            ];
        }

        return $posts;
    }

    private function getFeaturedPostsForFeed(): array {
        return $this->fetchPostInfoForFeed(
            $this->context->getNews()->getAllPosts(
                onlyFeatured: true,
                pagination: new Pagination(10)
            )
        );
    }

    public function feedIndexAtom($response, $request) {
        $response->setContentType('application/atom+xml; charset=utf-8');
        return (new AtomFeedSerializer)->serializeFeed(
            self::createFeed('atom', null, $this->getFeaturedPostsForFeed())
        );
    }

    public function feedIndexRss($response, $request) {
        $response->setContentType('application/rss+xml; charset=utf-8');
        return (new RssFeedSerializer)->serializeFeed(
            self::createFeed('rss', null, $this->getFeaturedPostsForFeed())
        );
    }

    private function getCategoryPostsForFeed(NewsCategoryInfo $categoryInfo): array {
        return $this->fetchPostInfoForFeed(
            $this->context->getNews()->getPostsByCategory($categoryInfo, pagination: new Pagination(10))
        );
    }

    public function feedCategoryAtom($response, $request, NewsCategoryInfo $categoryInfo) {
        $response->setContentType('application/atom+xml; charset=utf-8');
        return (new AtomFeedSerializer)->serializeFeed(
            self::createFeed('atom', $categoryInfo, $this->getCategoryPostsForFeed($categoryInfo))
        );
    }

    public function feedCategoryRss($response, $request, NewsCategoryInfo $categoryInfo) {
        $response->setContentType('application/rss+xml; charset=utf-8');
        return (new RssFeedSerializer)->serializeFeed(
            self::createFeed('rss', $categoryInfo, $this->getCategoryPostsForFeed($categoryInfo))
        );
    }
}
