package handler

import (
	"context"
	"errors"

	"git.dmitriygnatenko.ru/dima/go-common/db"
	"git.dmitriygnatenko.ru/dima/go-common/logger"
	"github.com/gofiber/fiber/v2"

	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/dto"
	customErrors "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helper/errors"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/mapper"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/i18n"
)

const (
	ArticleCacheKey        = "article"
	RecentArticlesCacheKey = "recent-articles"
	UsedTagsCacheKey       = "used-tags"
	previewArticlesCount   = 3
	articleParam           = "article"
)

type articlePage struct {
	cacheService      CacheService
	articleRepository ArticleRepository
	tagRepository     TagRepository
}

func NewArticlePageHandler(
	cacheService CacheService,
	articleRepository ArticleRepository,
	tagRepository TagRepository,
) fiber.Handler {
	h := articlePage{
		cacheService:      cacheService,
		articleRepository: articleRepository,
		tagRepository:     tagRepository,
	}

	return h.handler()
}

func (h *articlePage) handler() fiber.Handler {
	return func(fctx *fiber.Ctx) error {
		ctx := fctx.Context()
		url := fctx.Params(articleParam)
		lang := i18n.LanguageFromContext(fctx)

		// article
		renderData, err := h.article(ctx, lang, url)
		if err != nil {
			if !errors.Is(err, fiber.ErrNotFound) {
				logger.Error(ctx, customErrors.Wrap(err, "article page").Error())
			}

			return err
		}

		// recent articles
		articles, err := h.recentArticles(ctx, lang)
		if err != nil {
			logger.Error(ctx, customErrors.Wrap(err, "article page").Error())
			return err
		}

		renderData["sidebarArticles"] = articles

		// tags
		tags, err := h.tags(ctx, lang)
		if err != nil {
			logger.Error(ctx, customErrors.Wrap(err, "article page").Error())
			return err
		}

		renderData["sidebarTags"] = tags

		return fctx.Render("article", renderData, "_layout")
	}
}

func (h *articlePage) article(
	ctx context.Context,
	lang i18n.Language,
	url string,
) (fiber.Map, error) {
	articleCacheKey := ArticleCacheKey + url + string(lang)
	cacheData, found := h.cacheService.Get(articleCacheKey)
	if found {
		if articleData, ok := cacheData.(fiber.Map); ok {
			return articleData, nil
		}
	}

	articleModel, err := h.articleRepository.GetByURL(ctx, url, i18n.LanguageID(lang))
	if err != nil {
		if db.IsNotFoundError(err) {
			return nil, fiber.ErrNotFound
		}

		return nil, customErrors.Wrap(err, "can't get article by url")
	}

	if !articleModel.IsActive {
		return nil, fiber.ErrNotFound
	}

	article := mapper.ToArticle(lang, *articleModel)

	renderData := fiber.Map{
		"lang":            lang,
		"headTitle":       i18n.T(lang, "head_title"),
		"headDescription": article.MetaDescription,
		"headKeywords":    article.MetaKeywords,
		"pageTitle":       i18n.T(lang, "article_page_title", article.Title),
		"article":         article,
	}

	h.cacheService.Set(articleCacheKey, renderData, nil)

	return renderData, nil
}

func (h *articlePage) recentArticles(
	ctx context.Context,
	lang i18n.Language,
) ([]dto.ArticlePreview, error) {
	recentArticlesCacheKey := RecentArticlesCacheKey + string(lang)

	cacheData, found := h.cacheService.Get(recentArticlesCacheKey)
	if found {
		if articles, ok := cacheData.([]dto.ArticlePreview); ok {
			return articles, nil
		}
	}

	articleModels, err := h.articleRepository.GetAllPreview(ctx, i18n.LanguageID(lang))
	if err != nil {
		return nil, customErrors.Wrap(err, "can't get articles")
	}

	if len(articleModels) > previewArticlesCount {
		articleModels = articleModels[:previewArticlesCount]
	}

	articles := mapper.ToArticlesPreview(lang, articleModels)
	h.cacheService.Set(recentArticlesCacheKey, articles, nil)

	return articles, nil
}

func (h *articlePage) tags(
	ctx context.Context,
	lang i18n.Language,
) ([]dto.Tag, error) {
	usedTagsCacheKey := UsedTagsCacheKey + string(lang)
	cacheData, found := h.cacheService.Get(usedTagsCacheKey)
	if found {
		if tagsData, ok := cacheData.([]dto.Tag); ok {
			return tagsData, nil
		}
	}

	tagModels, err := h.tagRepository.GetAllUsed(ctx, i18n.LanguageID(lang))
	if err != nil {
		return nil, customErrors.Wrap(err, "can't get tags")
	}

	tags := mapper.ToTagsList(tagModels)
	h.cacheService.Set(usedTagsCacheKey, tags, nil)

	return tags, nil
}
