package question

import (
	"gitea.pena/PenaSide/common/log_mw"
	"gitea.pena/SQuiz/common/dal"
	"gitea.pena/SQuiz/common/middleware"
	"gitea.pena/SQuiz/common/model"
	"gitea.pena/SQuiz/core/internal/models"
	"github.com/gofiber/fiber/v2"
	"github.com/lib/pq"
	"unicode/utf8"
)

type Deps struct {
	DAL *dal.DAL
}

type Question struct {
	dal *dal.DAL
}

func NewQuestionController(deps Deps) *Question {
	return &Question{dal: deps.DAL}
}

// QuestionCreateReq request structure for creating Question
type QuestionCreateReq struct {
	QuizId uint64 `json:"quiz_id"` // relation to quiz

	Title       string `json:"title"`       // title of question
	Description string `json:"description"` // additional content in question such as pics, html markup or plain text
	Type        string `json:"type"`        // button/select/file/checkbox/text
	Required    bool   `json:"required"`    // set true if question must be answered for valid quiz passing
	Page        int    `json:"page"`        // set page of question
	Content     string `json:"content"`     // json serialized config of question
}

// CreateQuestion service handler for creating question for quiz
func (r *Question) CreateQuestion(ctx *fiber.Ctx) error {
	accountID, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}
	hlogger := log_mw.ExtractLogger(ctx)

	var req QuestionCreateReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}
	if utf8.RuneCountInString(req.Title) >= 512 {
		return ctx.Status(fiber.StatusUnprocessableEntity).SendString("title field should have less then 512 chars")
	}

	if req.Type != model.TypeText &&
		req.Type != model.TypeVariant &&
		req.Type != model.TypeImages &&
		req.Type != model.TypeSelect &&
		req.Type != model.TypeVarImages &&
		req.Type != model.TypeEmoji &&
		req.Type != model.TypeDate &&
		req.Type != model.TypeNumber &&
		req.Type != model.TypePage &&
		req.Type != model.TypeRating &&
		req.Type != model.TypeResult &&
		req.Type != model.TypeFile {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none")
	}

	result := model.Question{
		QuizId:      req.QuizId,
		Title:       req.Title,
		Description: req.Description,
		Type:        req.Type,
		Required:    req.Required,
		Deleted:     false,
		Page:        req.Page,
		Content:     req.Content,
	}

	questionID, err := r.dal.QuestionRepo.CreateQuestion(ctx.Context(), &result)
	if err != nil {
		if e, ok := err.(*pq.Error); ok {
			if e.Constraint == "quiz_relation" {
				return ctx.Status(fiber.StatusFailedDependency).SendString(e.Error())
			}
		}
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	hlogger.Emit(models.InfoQuestionCreate{
		CtxUserID:     accountID,
		CtxIDInt:      int64(req.QuizId),
		CtxQuestionID: int64(questionID),
	})

	return ctx.Status(fiber.StatusOK).JSON(result)
}

// GetQuestionListReq request structure for get question page
type GetQuestionListReq struct {
	Limit  uint64 `json:"limit"`   // page size
	Page   uint64 `json:"page"`    // page number
	From   int64  `json:"from"`    // start of time period
	To     int64  `json:"to"`      // end of time period
	QuizId uint64 `json:"quiz_id"` // relation to quiz

	Search   string `json:"search"`  // search string to search in files
	Type     string `json:"type"`    // type of questions. check types in model
	Deleted  bool   `json:"deleted"` // true to get only deleted questions
	Required bool   `json:"required"`
}

// GetQuestionListResp response to get page questions with count of all filtered items
type GetQuestionListResp struct {
	Count uint64           `json:"count"`
	Items []model.Question `json:"items"`
}

// GetQuestionList handler for paginated list question
func (r *Question) GetQuestionList(ctx *fiber.Ctx) error {
	var req GetQuestionListReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	if req.Type != "" &&
		req.Type != model.TypeText &&
		req.Type != model.TypeVariant &&
		req.Type != model.TypeImages &&
		req.Type != model.TypeSelect &&
		req.Type != model.TypeVarImages &&
		req.Type != model.TypeEmoji &&
		req.Type != model.TypeDate &&
		req.Type != model.TypeNumber &&
		req.Type != model.TypePage &&
		req.Type != model.TypeRating &&
		req.Type != model.TypeResult &&
		req.Type != model.TypeFile {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("inappropriate type, allowed only '', " +
			"'test','none','file', 'button','select','checkbox'")
	}

	res, cnt, err := r.dal.QuestionRepo.GetQuestionList(ctx.Context(),
		req.Limit,
		req.Page*req.Limit,
		uint64(req.From),
		uint64(req.To),
		req.QuizId,
		req.Deleted,
		req.Required,
		req.Search,
		req.Type,
		0,
	)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	return ctx.JSON(GetQuestionListResp{
		Items: res,
		Count: cnt,
	})
}

// UpdateQuestionReq struct for request to update question
type UpdateQuestionReq struct {
	Id          uint64 `json:"id"`
	Title       string `json:"title"`
	Description string `json:"desc"`
	Type        string `json:"type"`
	Required    bool   `json:"required"`
	Content     string `json:"content"`
	Page        int    `json:"page"`
}

// UpdateResp id you change question that you need only new question id
type UpdateResp struct {
	Updated uint64 `json:"updated"`
}

// UpdateQuestion handler for update question
func (r *Question) UpdateQuestion(ctx *fiber.Ctx) error {
	var req UpdateQuestionReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	if req.Id == 0 {
		return ctx.Status(fiber.StatusFailedDependency).SendString("need id of question for update")
	}

	if utf8.RuneCountInString(req.Title) >= 512 {
		return ctx.Status(fiber.StatusUnprocessableEntity).SendString("title field should have less then 512 chars")
	}

	if req.Type != model.TypeText &&
		req.Type != model.TypeVariant &&
		req.Type != model.TypeImages &&
		req.Type != model.TypeSelect &&
		req.Type != model.TypeVarImages &&
		req.Type != model.TypeEmoji &&
		req.Type != model.TypeDate &&
		req.Type != model.TypeNumber &&
		req.Type != model.TypePage &&
		req.Type != model.TypeRating &&
		req.Type != model.TypeFile &&
		req.Type != model.TypeResult &&
		req.Type != "" {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none or empty string")
	}

	question, err := r.dal.QuestionRepo.MoveToHistoryQuestion(ctx.Context(), req.Id)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}
	question.ParentIds = append(question.ParentIds, int32(question.Id))
	question.Id = req.Id
	question.Version += 1

	if req.Title != "" {
		question.Title = req.Title
	}

	if req.Description != "" {
		question.Description = req.Description
	}

	if req.Page != 0 {
		question.Page = req.Page
	}

	if req.Type != "" {
		question.Type = req.Type
	}

	if req.Required != question.Required {
		question.Required = req.Required
	}

	if req.Content != question.Content {
		question.Content = req.Content
	}

	if err := r.dal.QuestionRepo.UpdateQuestion(ctx.Context(), question); err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	return ctx.JSON(UpdateResp{
		Updated: question.Id,
	})
}

// CopyQuestionReq request struct  for copy or duplicate question
type CopyQuestionReq struct {
	Id     uint64 `json:"id"`
	QuizId uint64 `json:"quiz_id"`
}

// CopyQuestion handler for copy question
func (r *Question) CopyQuestion(ctx *fiber.Ctx) error {
	var req CopyQuestionReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	if req.Id == 0 {
		return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
	}

	question, err := r.dal.QuestionRepo.CopyQuestion(ctx.Context(), req.Id, req.QuizId)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	return ctx.JSON(UpdateResp{
		Updated: question.Id,
	})
}

// GetQuestionHistoryReq struct of get history request
type GetQuestionHistoryReq struct {
	Id    uint64 `json:"id"`
	Limit uint64 `json:"l"`
	Page  uint64 `json:"p"`
}

// GetQuestionHistory handler for history of quiz
func (r *Question) GetQuestionHistory(ctx *fiber.Ctx) error {
	var req GetQuestionHistoryReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	if req.Id == 0 {
		return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
	}

	history, err := r.dal.QuestionRepo.QuestionHistory(ctx.Context(), req.Id, req.Limit, req.Page*req.Limit)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	return ctx.Status(fiber.StatusOK).JSON(history)
}

type DeactivateResp struct {
	Deactivated uint64 `json:"deactivated"`
}

// DeleteQuestion handler for fake delete question
func (r *Question) DeleteQuestion(ctx *fiber.Ctx) error {
	accountID, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}
	hlogger := log_mw.ExtractLogger(ctx)

	var req struct {
		Id uint64 `json:"id"`
	}

	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	if req.Id == 0 {
		return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting question is required")
	}

	deleted, err := r.dal.QuestionRepo.DeleteQuestion(ctx.Context(), req.Id)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	hlogger.Emit(models.InfoQuestionDelete{
		CtxUserID:     accountID,
		CtxIDInt:      int64(deleted.QuizId),
		CtxQuestionID: int64(deleted.Id),
	})

	return ctx.JSON(DeactivateResp{
		Deactivated: deleted.Id,
	})
}
