<?php
namespace Misuzu\News;

use RuntimeException;
use Index\DateTime;
use Index\Data\DbTools;
use Index\Data\IDbConnection;
use Index\Routing\IRouter;
use Misuzu\Pagination;
use Misuzu\Template;
use Misuzu\Auth\AuthInfo;
use Misuzu\Comments\Comments;
use Misuzu\Comments\CommentsCategory;
use Misuzu\Comments\CommentsEx;
use Misuzu\Config\IConfig;
use Misuzu\Feeds\Feed;
use Misuzu\Feeds\FeedItem;
use Misuzu\Feeds\AtomFeedSerializer;
use Misuzu\Feeds\RssFeedSerializer;
use Misuzu\News\News;
use Misuzu\News\NewsCategoryInfo;
use Misuzu\Users\Users;
use Misuzu\Parsers\Parser;

class NewsRoutes {
    private IConfig $config;
    private AuthInfo $authInfo;
    private News $news;
    private Users $users;
    private Comments $comments;

    public function __construct(
        IRouter $router,
        IConfig $config,
        AuthInfo $authInfo,
        News $news,
        Users $users,
        Comments $comments
    ) {
        $this->config = $config;
        $this->authInfo = $authInfo;
        $this->news = $news;
        $this->users = $users;
        $this->comments = $comments;

        $router->get('/news', [$this, 'getIndex']);
        $router->get('/news.rss', [$this, 'getFeedRss']);
        $router->get('/news.atom', [$this, 'getFeedAtom']);
        $router->get('/news/:category', [$this, 'getCategory']);
        $router->get('/news/post/:id', [$this, 'getPost']);

        $router->get('/news.php', function($response, $request) {
            $postId = $request->getParam('n', FILTER_SANITIZE_NUMBER_INT)
                ?? $request->getParam('p', FILTER_SANITIZE_NUMBER_INT);

            if($postId > 0)
                $location = url('news-post', ['post' => $postId]);
            else {
                $catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
                $pageId = $request->getParam('page', FILTER_SANITIZE_NUMBER_INT);
                $location = url($catId > 0 ? 'news-category' : 'news-index', ['category' => $catId, 'page' => $pageId]);
            }

            $response->redirect($location, true);
        });

        $router->get('/news.php/rss', function($response, $request) {
            $catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
            $location = url($catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss', ['category' => $catId]);
            $response->redirect($location, true);
        });

        $router->get('/news.php/atom', function($response, $request) {
            $catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
            $location = url($catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom', ['category' => $catId]);
            $response->redirect($location, true);
        });

        $router->get('/news/index.php', function($response, $request) {
            $response->redirect(url('news-index', [
                'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT),
            ]), true);
        });

        $router->get('/news/category.php', function($response, $request) {
            $response->redirect(url('news-category', [
                'category' => $request->getParam('c', FILTER_SANITIZE_NUMBER_INT),
                'page' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
            ]), true);
        });

        $router->get('/news/post.php', function($response, $request) {
            $response->redirect(url('news-post', [
                'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
            ]), true);
        });

        $router->get('/news/feed.php/rss', function($response, $request) {
            $catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
            $response->redirect(url(
                $catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss',
                ['category' => $catId]
            ), true);
        });

        $router->get('/news/feed.php/atom', function($response, $request) {
            $catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
            $response->redirect(url(
                $catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom',
                ['category' => $catId]
            ), true);
        });
    }

    private array $userInfos = [];
    private array $userColours = [];
    private array $categoryInfos = [];

    private function getNewsPostsForView(Pagination $pagination, ?NewsCategoryInfo $categoryInfo = null): array {
        $posts = [];
        $postInfos = $this->news->getPosts(
            categoryInfo: $categoryInfo,
            onlyFeatured: $categoryInfo === null,
            pagination: $pagination
        );

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

            if(array_key_exists($userId, $this->userInfos)) {
                $userInfo = $this->userInfos[$userId];
            } else {
                try {
                    $userInfo = $this->users->getUser($userId, 'id');
                } catch(RuntimeException $ex) {
                    $userInfo = null;
                }

                $this->userInfos[$userId] = $userInfo;
            }

            if(array_key_exists($userId, $this->userColours)) {
                $userColour = $this->userColours[$userId];
            } else {
                try {
                    $userColour = $this->users->getUserColour($userInfo);
                } catch(RuntimeException $ex) {
                    $userColour = null;
                }

                $this->userColours[$userId] = $userColour;
            }

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

            $commentsCount = $postInfo->hasCommentsCategoryId()
                ? $this->comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false)
                : 0;

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

        return $posts;
    }

    private function getNewsPostsForFeed(?NewsCategoryInfo $categoryInfo = null): array {
        $posts = [];
        $postInfos = $this->news->getPosts(
            categoryInfo: $categoryInfo,
            onlyFeatured: $categoryInfo === null,
            pagination: new Pagination(10)
        );

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

            if(array_key_exists($userId, $this->userInfos)) {
                $userInfo = $this->userInfos[$userId];
            } else {
                try {
                    $userInfo = $this->users->getUser($userId, 'id');
                } catch(RuntimeException $ex) {
                    $userInfo = null;
                }

                $this->userInfos[$userId] = $userInfo;
            }

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

        return $posts;
    }

    public function getIndex() {
        $categories = $this->news->getCategories(hidden: false);

        $pagination = new Pagination($this->news->countPosts(onlyFeatured: true), 5);
        if(!$pagination->hasValidOffset())
            return 404;

        $posts = $this->getNewsPostsForView($pagination);

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

    public function getFeedRss($response) {
        return $this->getFeed($response, 'rss');
    }

    public function getFeedAtom($response) {
        return $this->getFeed($response, 'atom');
    }

    public function getCategory($response, $request, string $fileName) {
        $categoryId = pathinfo($fileName, PATHINFO_FILENAME);
        $type = pathinfo($fileName, PATHINFO_EXTENSION);

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

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

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

        $posts = $this->getNewsPostsForView($pagination, $categoryInfo);

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

    private function getCategoryFeedRss($response, $request, NewsCategoryInfo $categoryInfo) {
        return $this->getFeed($response, 'rss', $categoryInfo);
    }

    private function getCategoryFeedAtom($response, $request, NewsCategoryInfo $categoryInfo) {
        return $this->getFeed($response, 'atom', $categoryInfo);
    }

    public function getPost($response, $request, string $postId) {
        try {
            $postInfo = $this->news->getPost($postId);
        } catch(RuntimeException $ex) {
            return 404;
        }

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

        $categoryInfo = $this->news->getCategory(postInfo: $postInfo);

        if($postInfo->hasCommentsCategoryId())
            try {
                $commentsCategory = $this->comments->getCategory(categoryId: $postInfo->getCommentsCategoryId());
            } catch(RuntimeException $ex) {}

        if(!isset($commentsCategory)) {
            $commentsCategory = $this->comments->ensureCategory($postInfo->getCommentsCategoryName());
            $this->news->updatePostCommentCategory($postInfo, $commentsCategory);
        }

        $userInfo = null;
        $userColour = null;
        if($postInfo->hasUserId())
            try {
                $userInfo = $this->users->getUser($postInfo->getUserId(), 'id');
                $userColour = $this->users->getUserColour($userInfo);
            } catch(RuntimeException $ex) {}

        $comments = new CommentsEx($this->authInfo, $this->comments, $this->users);

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

    private function getFeed($response, string $feedType, ?NewsCategoryInfo $categoryInfo = null) {
        $hasCategory = $categoryInfo !== null;
        $siteName = $this->config->getString('site.name', 'Misuzu');
        $posts = $this->getNewsPostsForFeed($categoryInfo);

        $serialiser = match($feedType) {
            'rss' => new RssFeedSerializer,
            'atom' => new AtomFeedSerializer,
            default => throw new RuntimeException('Invalid $feedType specified.'),
        };

        $response->setContentType(sprintf('application/%s+xml; charset=utf-8', $feedType));
        $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-{$feedType}", ['category' => $categoryInfo->getId()]) : url("news-feed-{$feedType}")));

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

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

            $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 $serialiser->serializeFeed($feed);
    }
}
