package quiz

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

type Deps struct {
	DAL *dal.DAL
}

type Quiz struct {
	dal *dal.DAL
}

func NewQuizController(deps Deps) *Quiz {
	return &Quiz{dal: deps.DAL}
}

type CreateQuizReq struct {
	Fingerprinting    bool `json:"fingerprinting"`     // true if you need to store device id
	Repeatable        bool `json:"repeatable"`         // make it true for allow more than one quiz checkouting
	NotePrevented     bool `json:"note_prevented"`     // note answers even if the quiz was aborted
	MailNotifications bool `json:"mail_notifications"` // set true if you want get an email with every quiz passing
	UniqueAnswers     bool `json:"unique_answers"`     // set true if we you mention only last quiz passing

	Name        string `json:"name"` // max 700 chars
	Description string `json:"description"`
	Config      string `json:"config"` // serialize json with config for page rules. fill it up only if implement one form scenario
	Status      string `json:"status"` // status of quiz as enum. see Status const. fill it up only if implement one form scenario
	Limit       uint64 `json:"limit"`  // max count of quiz passing
	DueTo       uint64 `json:"due_to"` // time when quiz is end

	QuestionCnt uint64 `json:"question_cnt"` // for creating at one request

	TimeOfPassing uint64 `json:"time_of_passing"` // amount of seconds for give all appropriate answers for quiz
	Pausable      bool   `json:"pausable"`        // true allows to pause the quiz taking

	Super   bool   `json:"super"`    // set true if you want to create group
	GroupId uint64 `json:"group_id"` // if you create quiz in group provide there the id of super quiz
}

// CreateQuiz handler for quiz creating request
func (r *Quiz) CreateQuiz(ctx *fiber.Ctx) error {
	var req CreateQuizReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}

	hlogger := log_mw.ExtractLogger(ctx)

	// check that we can store name
	if utf8.RuneCountInString(req.Name) > 700 {
		return ctx.Status(fiber.StatusUnprocessableEntity).SendString("name field should have less then 700 chars")
	}

	// status should be empty or equal one of status enum strings
	// I mean not draft, template, stop, start statuses
	if req.Status != "" &&
		req.Status != model.StatusDraft &&
		req.Status != model.StatusTemplate &&
		req.Status != model.StatusStop &&
		req.Status != model.StatusStart {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("status on creating must be only draft,template,stop,start")
	}

	// DueTo should be bigger then now
	if req.DueTo != 0 && req.DueTo <= uint64(time.Now().Unix()) {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("due to time must be lesser then now")
	}

	// you can pause quiz only if it has deadline for passing
	if req.Pausable && req.TimeOfPassing == 0 {
		return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing")
	}

	record := model.Quiz{
		AccountId:         accountId,
		Fingerprinting:    req.Fingerprinting,
		Repeatable:        req.Repeatable,
		NotePrevented:     req.NotePrevented,
		MailNotifications: req.MailNotifications,
		UniqueAnswers:     req.UniqueAnswers,
		Name:              req.Name,
		Description:       req.Description,
		Config:            req.Config,
		Status:            req.Status,
		Limit:             req.Limit,
		DueTo:             req.DueTo,
		TimeOfPassing:     req.TimeOfPassing,
		Pausable:          req.Pausable,
		QuestionsCount:    req.QuestionCnt,
		ParentIds:         []int32{},
		Super:             req.Super,
		GroupId:           req.GroupId,
	}

	quizID, err := r.dal.QuizRepo.CreateQuiz(ctx.Context(), &record)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	hlogger.Emit(models.InfoQuizCreated{
		CtxUserID: accountId,
		CtxIDInt:  int64(quizID),
	})

	return ctx.Status(fiber.StatusCreated).JSON(record)
}

// GetQuizListReq request struct for paginated quiz table
type GetQuizListReq struct {
	Limit uint64 `json:"limit"`
	Page  uint64 `json:"page"`
	From  int64  `json:"from"`
	To    int64  `json:"to"`

	Search   string `json:"search"`
	Status   string `json:"status"`
	Deleted  bool   `json:"deleted"`
	Archived bool   `json:"archived"`
	Super    bool   `json:"super"`
	GroupId  uint64 `json:"group_id"`
}

type GetQuizListResp struct {
	Count uint64       `json:"count"`
	Items []model.Quiz `json:"items"`
}

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

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}

	if req.Status != "" &&
		req.Status != model.StatusStop &&
		req.Status != model.StatusStart &&
		req.Status != model.StatusDraft &&
		req.Status != model.StatusTemplate &&
		req.Status != model.StatusTimeout &&
		req.Status != model.StatusOffLimit {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("inappropriate status, allowed only '', " +
			"'stop','start','draft', 'template','timeout','offlimit'")
	}

	res, cnt, err := r.dal.QuizRepo.GetQuizList(ctx.Context(),
		quiz.GetQuizListDeps{
			Limit:     req.Limit,
			Offset:    req.Limit * req.Page,
			From:      uint64(req.From),
			To:        uint64(req.To),
			Group:     req.GroupId,
			Deleted:   req.Deleted,
			Archived:  req.Archived,
			Super:     req.Super,
			Search:    req.Search,
			Status:    req.Status,
			AccountId: accountId,
		})
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

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

type UpdateQuizReq struct {
	Id                uint64 `json:"id"`
	Fingerprinting    bool   `json:"fp"`
	Repeatable        bool   `json:"rep"`
	NotePrevented     bool   `json:"note_prevented"`
	MailNotifications bool   `json:"mailing"`
	UniqueAnswers     bool   `json:"uniq"`
	Name              string `json:"name"`
	Description       string `json:"desc"`
	Config            string `json:"conf"`
	Status            string `json:"status"`
	Limit             uint64 `json:"limit"`
	DueTo             uint64 `json:"due_to"`
	TimeOfPassing     uint64 `json:"time_of_passing"`
	Pausable          bool   `json:"pausable"`
	QuestionCnt       uint64 `json:"question_cnt"` // for creating at one request
	Super             bool   `json:"super"`
	GroupId           uint64 `json:"group_id"`
}

type UpdateResp struct {
	Updated uint64 `json:"updated"`
}

func (r *Quiz) UpdateQuiz(ctx *fiber.Ctx) error {
	var req UpdateQuizReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}

	hlogger := log_mw.ExtractLogger(ctx)

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

	if utf8.RuneCountInString(req.Name) > 700 {
		return ctx.Status(fiber.StatusUnprocessableEntity).SendString("name field should have less then 700 chars")
	}

	// status should be empty or equal one of status enum strings
	// I mean not draft, template, stop, start statuses
	if req.Status != "" &&
		req.Status != model.StatusDraft &&
		req.Status != model.StatusTemplate &&
		req.Status != model.StatusStop &&
		req.Status != model.StatusStart {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("status on creating must be only draft,template,stop,start")
	}

	// DueTo should be bigger then now
	if req.DueTo != 0 && req.DueTo <= uint64(time.Now().Unix()) {
		return ctx.Status(fiber.StatusNotAcceptable).SendString("due to time must be lesser then now")
	}

	// you can pause quiz only if it has deadline for passing
	if req.Pausable && req.TimeOfPassing == 0 {
		return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing")
	}

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

	if req.Fingerprinting != quiz.Fingerprinting {
		quiz.Fingerprinting = req.Fingerprinting
	}

	if req.Repeatable != quiz.Repeatable {
		quiz.Repeatable = req.Repeatable
	}

	if req.MailNotifications != quiz.MailNotifications {
		quiz.MailNotifications = req.MailNotifications
	}

	if req.NotePrevented != quiz.NotePrevented {
		quiz.NotePrevented = req.NotePrevented
	}

	if req.UniqueAnswers != quiz.UniqueAnswers {
		quiz.UniqueAnswers = req.UniqueAnswers
	}

	if req.Pausable != quiz.Pausable {
		quiz.Pausable = req.Pausable
	}

	if req.Name != "" && req.Name != quiz.Name {
		quiz.Name = req.Name
	}

	if req.Description != "" && req.Description != quiz.Description {
		quiz.Description = req.Description
	}

	if req.Status != "" && req.Status != quiz.Status {
		quiz.Status = req.Status
	}

	if req.TimeOfPassing != quiz.TimeOfPassing {
		quiz.TimeOfPassing = req.TimeOfPassing
	}

	if req.DueTo != quiz.DueTo {
		quiz.DueTo = req.DueTo
	}

	if req.Limit != quiz.Limit {
		quiz.Limit = req.Limit
	}

	if req.Config != "" && req.Config != quiz.Config {
		quiz.Config = req.Config
	}

	if req.Super != quiz.Super {
		quiz.Super = req.Super
	}

	if req.GroupId != quiz.GroupId {
		quiz.GroupId = req.GroupId
	}

	quiz.QuestionsCount = req.QuestionCnt

	quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id))

	if err := r.dal.QuizRepo.UpdateQuiz(ctx.Context(), accountId, quiz); err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	if req.Status == model.StatusStart {
		hlogger.Emit(models.InfoQuizPublish{
			CtxUserID: accountId,
			CtxIDInt:  int64(quiz.Id),
		})
	}
	if req.Status == model.StatusStop {
		hlogger.Emit(models.InfoQuizStop{
			CtxUserID: accountId,
			CtxIDInt:  int64(quiz.Id),
		})
	}

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

// CopyQuizReq request struct for copy quiz
type CopyQuizReq struct {
	Id uint64 `json:"id"`
}

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

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}

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

	quiz, err := r.dal.QuizRepo.CopyQuiz(ctx.Context(), accountId, req.Id)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

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

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

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

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}
	if req.Id == 0 {
		return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
	}

	history, err := r.dal.QuizRepo.QuizHistory(ctx.Context(), quiz.QuizHistoryDeps{
		Id:        req.Id,
		Limit:     req.Limit,
		Offset:    req.Page * req.Limit,
		AccountId: accountId,
	})
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

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

// DeactivateReq request structure for archiving and deleting
type DeactivateReq struct {
	Id uint64 `json:"id"`
}

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

// DeleteQuiz handler for fake delete quiz
func (r *Quiz) DeleteQuiz(ctx *fiber.Ctx) error {
	var req DeactivateReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}
	hlogger := log_mw.ExtractLogger(ctx)

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

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

	hlogger.Emit(models.InfoQuizDelete{
		CtxUserID: accountId,
		CtxIDInt:  int64(req.Id),
	})

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

// ArchiveQuiz handler for archiving quiz
func (r *Quiz) ArchiveQuiz(ctx *fiber.Ctx) error {
	var req DeactivateReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	accountId, ok := middleware.GetAccountId(ctx)
	if !ok {
		return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
	}

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

	archived, err := r.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

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

type QuizMoveReq struct {
	Qid, AccountID string
}

func (r *Quiz) QuizMove(ctx *fiber.Ctx) error {
	var req QuizMoveReq
	if err := ctx.BodyParser(&req); err != nil {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
	}

	if req.Qid == "" || req.AccountID == "" {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid and accountID is required")
	}

	resp, err := r.dal.QuizRepo.QuizMove(ctx.Context(), req.Qid, req.AccountID)
	if err != nil {
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

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

func (r *Quiz) TemplateCopy(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 {
		Qid string `json:"qid"`
	}

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

	if req.Qid == "" {
		return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid is required")
	}

	qizID, err := r.dal.QuizRepo.TemplateCopy(ctx.Context(), accountID, req.Qid)
	if err != nil {
		fmt.Println("TEMPLERR", err)
		return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
	}

	hlogger.Emit(models.InfoQuizTemplateCopy{
		CtxUserID: accountID,
		// todo либо возвращать id копируемого квиза либо поле с qid
		// для него потому что id получаем уже в запросе sql
		//CtxID:       req.Qid,
		CtxQuizID: qizID,
	})

	return ctx.Status(fiber.StatusOK).JSON(fiber.Map{"id": qizID})
}
