package lessons

import (
	"sort"
	"strings"

	"git.ex.umbach.dev/LMS/libcore/models"
	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	"gorm.io/gorm"
	"lms.de/backend/modules/database"
	"lms.de/backend/modules/structs"
	"lms.de/backend/modules/utils"
)

func GetLessons(c *fiber.Ctx) error {
	// swagger:operation GET /v1/lessons lessons GetLessons
	// ---
	// summary: Get lessons.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// responses:
	//   '200':
	//     description: Lessons retrieved successfully.
	//     schema:
	//       type: array
	//       items:
	//         "$ref": "#/definitions/LessonResponse"
	//   '500':
	//     description: Failed to retrieve lessons.

	var lessons []structs.LessonResponse

	if err := database.DB.Model(&models.Lesson{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&lessons).Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(lessons)
}

func CreateLesson(c *fiber.Ctx) error {
	// swagger:operation POST /v1/lessons lessons CreateLesson
	// ---
	// summary: Create lesson.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// responses:
	//   '200':
	//     description: Lesson created successfully.
	//     schema:
	//       "$ref": "#/definitions/CreateLessonResponse"
	//   '500':
	//     description: Failed to create lesson.

	lesson := models.Lesson{
		Id:             uuid.New().String(),
		OrganizationId: c.Locals("organizationId").(string),
		State:          structs.LessonStateDraft,
		Title:          "Test",
		CreatorUserId:  c.Locals("userId").(string),
	}

	if err := database.DB.Create(&lesson).Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		structs.CreateLessonResponse{
			Id: lesson.Id,
		},
	)
}

func GetLessonContents(c *fiber.Ctx) error {
	// swagger:operation GET /v1/lessons/{lessonId}/contents lessons GetLessonContents
	// ---
	// summary: Get lesson contents.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// responses:
	//   '200':
	//     description: Lesson contents retrieved successfully.
	//     schema:
	//       type: array
	//       items:
	//         "$ref": "#/definitions/LessonContent"
	//   '500':
	//     description: Failed to retrieve lesson contents.

	var params structs.LessonIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var lessonContents []structs.GetLessonContentsResponse

	if err := database.DB.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Find(&lessonContents).Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// sort contents by position

	sort.SliceStable(lessonContents, func(i, j int) bool {
		return lessonContents[i].Position < lessonContents[j].Position
	})

	return c.JSON(lessonContents)
}

func GetLessonSettings(c *fiber.Ctx) error {
	// swagger:operation GET /v1/lessons/{lessonId}/settings lessons GetLessonSettings
	// ---
	// summary: Get lesson settings.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// responses:
	//   '200':
	//     description: Lesson settings retrieved successfully.
	//     schema:
	//       "$ref": "#/definitions/LessonSettings"
	//   '500':
	//     description: Failed to retrieve lesson settings.

	var params structs.LessonIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var lessonSettings structs.LessonSettings

	if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lessonSettings).Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(lessonSettings)
}

func UpdateLessonPreviewTitle(c *fiber.Ctx) error {
	// swagger:operation PATCH /v1/lessons/{lessonId}/preview/title lessons UpdateLessonPreviewTitle
	// ---
	// summary: Update lesson preview title.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/UpdateLessonPreviewRequest"
	// responses:
	//   '200':
	//     description: Lesson preview updated successfully.
	//     schema:
	//       "$ref": "#/definitions/UpdateLessonPreviewResponse"
	//   '500':
	//     description: Failed to update lesson preview.

	var params structs.LessonIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var body structs.UpdateLessonPreviewTitleRequest

	if err := c.BodyParser(&body); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		fiber.Map{
			"message": "Lesson preview updated successfully",
		},
	)
}

func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error {
	// swagger:operation POST /v1/lessons/{lessonId}/preview/thumbnail lessons UpdateLessonPreviewThumbnail
	// ---
	// summary: Update lesson preview thumbnail.
	// consumes:
	// - multipart/form-data
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/UpdateLessonPreviewThumbnailRequest"
	// responses:
	//   '200':
	//     description: Lesson preview updated successfully.
	//   '500':
	//     description: Failed to update lesson preview.

	var params structs.LessonIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	fileHeader, err := c.FormFile("file")

	if err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if fileHeader.Size > utils.MaxImageSize {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if !utils.IsFileTypeAllowed(fileHeader.Header.Get("Content-Type"), utils.AcceptedImageFileTypes) {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	// get current thumbnail

	lesson := models.Lesson{}

	if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lesson); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// delete current thumbnail

	if lesson.ThumbnailUrl != "" {
		utils.DeleteFile(lesson.ThumbnailUrl)
	}

	fileName := uuid.New().String() + "." + strings.Split(fileHeader.Header["Content-Type"][0], "/")[1]

	databasePath, publicPath := utils.GetFullImagePath(c.Locals("organizationId").(string), params.LessonId)

	utils.CreateFolderStructureIfNotExists(publicPath)

	if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", databasePath+fileName); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.SaveFile(fileHeader, publicPath+fileName)
}

func UpdateLessonState(c *fiber.Ctx) error {
	// swagger:operation PATCH /v1/lessons/{lessonId}/state lessons UpdateLessonState
	// ---
	// summary: Update lesson state.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/UpdateLessonStateRequest"
	// responses:
	//   '200':
	//     description: Lesson state updated successfully.
	//   '500':
	//     description: Failed to update lesson state.

	var params structs.LessonIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var body structs.UpdateLessonStateRequest

	if err := c.BodyParser(&body); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("state", body.State); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		fiber.Map{
			"message": "Lesson state updated successfully",
		},
	)
}

func AddLessonContent(c *fiber.Ctx) error {
	// swagger:operation POST /v1/lessons/{lessonId}/contents lessons AddLessonContent
	// ---
	// summary: Add lesson content.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/AddLessonContentRequest"
	// responses:
	//   '200':
	//     description: Lesson content added successfully.
	//     schema:
	//       "$ref": "#/definitions/AddLessonContentResponse"
	//   '500':
	//     description: Failed to add lesson content.

	var params structs.LessonIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var body structs.AddLessonContentRequest

	if err := c.BodyParser(&body); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	// get last position

	var lastContent models.LessonContent

	if err := database.DB.Select("position").Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Order("position desc").First(&lastContent); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// create new content

	content := models.LessonContent{
		Id:       uuid.New().String(),
		LessonId: params.LessonId,
		Page:     1,
		Position: lastContent.Position + 1,
		Type:     body.Type,
		Data:     body.Data,
	}

	if err := database.DB.Create(&content); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		structs.AddLessonContentResponse{
			Id: content.Id,
		},
	)
}

func UploadLessonContentFile(c *fiber.Ctx) error {
	// swagger:operation POST /v1/lessons/{lessonId}/contents/{contentId}/file/{type} lessons UploadLessonContentFile
	// ---
	// summary: Upload lesson content file.
	// consumes:
	// - multipart/form-data
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: contentId
	//   in: path
	//   required: true
	//   type: string
	// - name: file
	//   in: formData
	//   required: true
	//   type: file
	// responses:
	//   '200':
	//     description: Lesson content file uploaded successfully.
	//   '500':
	//     description: Failed to upload lesson content file.

	var params structs.UploadLessonContentFileParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	fileHeader, err := c.FormFile("file")

	if err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if params.Type == "image" {
		if fileHeader.Size > utils.MaxImageSize {
			return c.SendStatus(fiber.StatusBadRequest)
		}

		if !utils.IsFileTypeAllowed(fileHeader.Header.Get("Content-Type"), utils.AcceptedImageFileTypes) {
			return c.SendStatus(fiber.StatusBadRequest)
		}
	} else if params.Type == "video" {
		if fileHeader.Size > utils.MaxVideoSize {
			return c.SendStatus(fiber.StatusBadRequest)
		}

		if !utils.IsFileTypeAllowed(fileHeader.Header.Get("Content-Type"), utils.AcceptedVideoFileTypes) {
			return c.SendStatus(fiber.StatusBadRequest)
		}
	} else {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	// get current file

	content := models.LessonContent{}

	if err := database.DB.Select("type", "data").Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content); err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// delete current image

	if content.Type == utils.LessonContentTypeImage && content.Data != "" {
		utils.DeleteFile(content.Data)
	}

	fileName := uuid.New().String() + "." + strings.Split(fileHeader.Header["Content-Type"][0], "/")[1]

	databasePath, publicPath := utils.GetFullImagePath(c.Locals("organizationId").(string), params.LessonId)

	utils.CreateFolderStructureIfNotExists(publicPath)

	if err := database.DB.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("data", databasePath+fileName).Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	if err := c.SaveFile(fileHeader, publicPath+fileName); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to save file",
		})
	}

	return c.JSON(fiber.Map{
		"Data": databasePath + fileName,
	})
}

func UpdateLessonContent(c *fiber.Ctx) error {
	// swagger:operation PATCH /v1/lessons/{lessonId}/contents/{contentId} lessons UpdateLessonContent
	// ---
	// summary: Update lesson content.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: contentId
	//   in: path
	//   required: true
	//   type: string
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/UpdateLessonContentRequest"
	// responses:
	//   '200':
	//     description: Lesson content updated successfully.
	//   '500':
	//     description: Failed to update lesson content.

	var params structs.LessonIdAndContentIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var body structs.UpdateLessonContentRequest

	if err := c.BodyParser(&body); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if err := database.DB.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("data", body.Data).Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		fiber.Map{
			"message": "Lesson content updated successfully",
		},
	)
}

func UpdateLessonContentPosition(c *fiber.Ctx) error {
	// swagger:operation PATCH /v1/lessons/{lessonId}/contents/{contentId}/position lessons UpdateLessonContentPosition
	// ---
	// summary: Update lesson content position.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: contentId
	//   in: path
	//   required: true
	//   type: string
	// - name: body
	//   in: body
	//   schema:
	//     "$ref": "#/definitions/UpdateLessonContentPositionRequest"
	// responses:
	//   '200':
	//     description: Lesson content position updated successfully.
	//   '500':
	//     description: Failed to update lesson content position.

	var params structs.LessonIdAndContentIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var body structs.UpdateLessonContentPositionRequest

	if err := c.BodyParser(&body); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	// update position

	newPosition := body.Position

	// Begin a transaction to ensure consistency
	tx := database.DB.Begin()
	if tx.Error != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// Fetch the current position of the content being moved
	var currentContent models.LessonContent
	if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&currentContent).Error; err != nil {
		tx.Rollback()
		return c.SendStatus(fiber.StatusNotFound)
	}

	oldPosition := currentContent.Position

	if oldPosition == newPosition {
		// No need to update if the position hasn't changed
		return c.JSON(fiber.Map{
			"message": "Lesson content position updated successfully",
		})
	}

	if oldPosition < newPosition {
		// Items between oldPosition and newPosition need to be shifted down
		if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).
			Where("position > ? AND position <= ?", oldPosition, newPosition).
			Update("position", gorm.Expr("position - 1")).Error; err != nil {
			tx.Rollback()
			return c.SendStatus(fiber.StatusInternalServerError)
		}
	} else {
		// Items between newPosition and oldPosition need to be shifted up
		if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).
			Where("position >= ? AND position < ?", newPosition, oldPosition).
			Update("position", gorm.Expr("position + 1")).Error; err != nil {
			tx.Rollback()
			return c.SendStatus(fiber.StatusInternalServerError)
		}
	}

	// Update the position of the moved content
	if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("position", newPosition).Error; err != nil {
		tx.Rollback()
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// Commit the transaction
	if err := tx.Commit().Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		fiber.Map{
			"message": "Lesson content position updated successfully",
		},
	)
}

func DeleteLessonContent(c *fiber.Ctx) error {
	// swagger:operation DELETE /v1/lessons/{lessonId}/contents/{contentId} lessons DeleteLessonContent
	// ---
	// summary: Delete lesson content.
	// consumes:
	// - application/json
	// produces:
	// - application/json
	// parameters:
	// - name: lessonId
	//   in: path
	//   required: true
	//   type: string
	// - name: contentId
	//   in: path
	//   required: true
	//   type: string
	// responses:
	//   '200':
	//     description: Lesson content deleted successfully.
	//   '500':
	//     description: Failed to delete lesson content.

	var params structs.LessonIdAndContentIdParam

	if err := c.ParamsParser(&params); err != nil {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	// Begin a transaction to ensure consistency
	tx := database.DB.Begin()
	if tx.Error != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// Fetch the current position of the content being deleted
	var content models.LessonContent
	if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil {
		tx.Rollback()
		return c.SendStatus(fiber.StatusNotFound)
	}

	// Delete the content
	if err := tx.Delete(&content).Error; err != nil {
		tx.Rollback()
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// Shift down the positions of all contents with a higher position
	if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).
		Where("position > ?", content.Position).
		Update("position", gorm.Expr("position - 1")).Error; err != nil {
		tx.Rollback()
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// Commit the transaction
	if err := tx.Commit().Error; err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(
		fiber.Map{
			"message": "Lesson content deleted successfully",
		},
	)
}
