package admin

import (
	"context"
	"errors"
	"strconv"

	"git.dmitriygnatenko.ru/dima/go-common/db"
	"git.dmitriygnatenko.ru/dima/go-common/logger"
	validation "github.com/go-ozzo/ozzo-validation/v4"
	"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/handler"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/i18n"
)

type addArticlePage struct {
	tm                   TransactionManager
	articleRepository    ArticleRepository
	tagRepository        TagRepository
	articleTagRepository ArticleTagRepository
	languageRepository   LanguageRepository
	cacheService         CacheService
}

func NewAddArticlePageHandler(
	tm TransactionManager,
	articleRepository ArticleRepository,
	tagRepository TagRepository,
	articleTagRepository ArticleTagRepository,
	languageRepository LanguageRepository,
	cacheService CacheService,
) fiber.Handler {
	h := addArticlePage{
		tm:                   tm,
		articleRepository:    articleRepository,
		tagRepository:        tagRepository,
		articleTagRepository: articleTagRepository,
		languageRepository:   languageRepository,
		cacheService:         cacheService,
	}

	return h.handler()
}

func (h addArticlePage) handler() fiber.Handler {
	return func(fctx *fiber.Ctx) error {
		ctx := fctx.Context()
		lang := i18n.LanguageFromContext(fctx)
		validationErrors := make(map[string]string)

		form := dto.ArticleForm{
			ActiveTags: make(map[uint64]bool),
		}

		tags, err := h.getTags(ctx)
		if err != nil {
			logger.Error(ctx, customErrors.Wrap(err, "add article page: can't get tags").Error())
			return err
		}

		languages, err := h.getLanguages(ctx)
		if err != nil {
			logger.Error(ctx, customErrors.Wrap(err, "add article page: can't get languages").Error())
			return err
		}

		if fctx.Method() == fiber.MethodPost {
			if err = fctx.BodyParser(&form); err != nil {
				logger.Info(ctx, customErrors.Wrap(err, "add article page: can't parse body").Error())
				return err
			}

			if err = h.validateArticleForm(ctx, form); err != nil {
				logger.Info(ctx, customErrors.Wrap(err, "add article page: validation").Error())
				validationErrors = mapper.ToErrorsMap(err)
			}

			tagIDs, err := h.formatTags(form.Tags)
			if err != nil {
				logger.Error(ctx, customErrors.Wrap(err, "add article page: can't format tags").Error())
				return err
			}

			for i := range tagIDs {
				form.ActiveTags[tagIDs[i]] = true
			}

			if len(validationErrors) == 0 {
				articleModel, err := mapper.ToArticleModel(form)
				if err != nil {
					logger.Error(ctx, customErrors.Wrap(err, "add article page").Error())
					return err
				}

				err = h.tm.ReadCommitted(ctx, func(ctx context.Context) error {
					articleID, txErr := h.articleRepository.Add(ctx, *articleModel)
					if txErr != nil {
						return customErrors.Wrap(txErr, "can't add article")
					}

					if len(tagIDs) > 0 {
						if txErr = h.articleTagRepository.Add(ctx, articleID, tagIDs); txErr != nil {
							return customErrors.Wrap(txErr, "can't add tags")
						}
					}

					return nil
				})

				if err != nil {
					logger.Error(ctx, customErrors.Wrap(err, "add article page").Error())
					return err
				}

				h.cacheService.Delete(handler.RecentArticlesCacheKey + form.Language)
				h.cacheService.Delete(handler.UsedTagsCacheKey + form.Language)
				h.cacheService.Delete(handler.AllPreviewArticlesCacheKey + form.Language)

				return fctx.Redirect("/admin")
			}
		}

		return fctx.Render("admin/article_edit", fiber.Map{
			"lang":       lang,
			"title":      i18n.T(lang, "admin_article_add_title"),
			"section":    "article",
			"show_apply": false,
			"form":       form,
			"errors":     validationErrors,
			"tags":       tags,
			"languages":  languages,
		}, "admin/_layout")
	}
}

func (h addArticlePage) getTags(ctx context.Context) ([]dto.Tag, error) {
	tagModels, err := h.tagRepository.GetAll(ctx)
	if err != nil {
		return nil, err
	}

	return mapper.ToTagsList(tagModels), nil
}

func (h addArticlePage) getLanguages(ctx context.Context) ([]string, error) {
	langModels, err := h.languageRepository.GetAll(ctx)
	if err != nil {
		return nil, err
	}

	res := make([]string, 0, len(langModels))
	for i := range langModels {
		res = append(res, langModels[i].URL)
	}

	return res, nil
}

func (h addArticlePage) formatTags(tags []string) ([]uint64, error) {
	ids := make([]uint64, 0, len(tags))

	for i := range tags {
		id, err := strconv.ParseUint(tags[i], 10, 64)
		if err != nil {
			return nil, err
		}

		ids = append(ids, id)
	}

	return ids, nil
}

func (h addArticlePage) validateArticleForm(ctx context.Context, req dto.ArticleForm) error {
	lang := i18n.DefaultLanguage()
	requiredMsg := i18n.T(lang, "admin_err_required")
	lengthMsg := i18n.T(lang, "admin_err_length_255")

	return validation.ValidateStructWithContext(
		ctx,
		&req,
		validation.Field(
			&req.Title,
			validation.Required.Error(requiredMsg),
			validation.Length(1, 255).Error(lengthMsg),
		),
		validation.Field(
			&req.Image,
			validation.Required.Error(requiredMsg),
			validation.Length(1, 255).Error(lengthMsg),
		),
		validation.Field(
			&req.URL,
			validation.Required.Error(requiredMsg),
			validation.Length(1, 255).Error(lengthMsg),
			validation.By(h.validateDuplicateURL(req.Language)),
		),
		validation.Field(
			&req.Text,
			validation.Required.Error(requiredMsg),
		),
		validation.Field(
			&req.MetaKeywords,
			validation.Length(1, 255).Error(lengthMsg),
		),
		validation.Field(
			&req.MetaDescription,
			validation.Length(1, 255).Error(lengthMsg),
		),
		validation.Field(
			&req.PublishTime,
			validation.Required.Error(requiredMsg),
		),
		validation.Field(
			&req.Language,
			validation.Required.Error(requiredMsg),
		),
	)
}

func (h addArticlePage) validateDuplicateURL(
	lang string,
) func(value interface{}) error {
	return func(value interface{}) error {
		url, _ := value.(string)
		ctx := context.Background()

		_, err := h.articleRepository.GetByURL(ctx, url, i18n.LanguageID(i18n.Language(lang)))
		if err != nil {
			if !db.IsNotFoundError(err) {
				logger.Error(ctx, err.Error())
			}

			return nil
		}

		return errors.New(i18n.T(i18n.DefaultLanguage(), "admin_err_article_duplicate", url))
	}
}
