/*
	Copyright (C) 2025  hexlocation (hex@iwakura.rip) & grngxd (grng@iwakura.rip)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package routes

import (
	"bytes"
	"io"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gofrs/uuid"
	"github.com/golang-jwt/jwt/v5"
	"github.com/h2non/filetype"
	"github.com/minio/minio-go/v7"
	"stereo.cat/backend/internal/auth"
	"stereo.cat/backend/internal/auth/session"
	"stereo.cat/backend/internal/types"
)

func intoReader(buf []byte) io.Reader {
	return io.NopCloser(bytes.NewBuffer(buf))
}

func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) {
	api.POST("/upload", session.SessionMiddleware(cfg.JWTSecret), func(c *gin.Context) {
		claims := c.MustGet("claims").(jwt.MapClaims)
		user := claims["user"].(auth.User)

		uid := user.ID
		if uid == "" {
			types.ErrorInvalidParams.Throw(c, nil)
			return
		}

		file, err := c.FormFile("file")
		if err != nil {
			types.ErrorInvalidParams.Throw(c, err)
			return
		}

		if file.Size <= 0 {
			types.ErrorInvalidParams.Throw(c, nil)
			return
		}

		fileReader, err := file.Open()
		if err != nil {
			types.ErrorReaderOpen.Throw(c, err)
			return
		}

		buf, err := io.ReadAll(fileReader)
		if err != nil {
			types.ErrorReaderOpen.Throw(c, err)
			return
		}

		if !filetype.IsImage(buf) {
			types.ErrorInvalidFile.Throw(c, nil)
			return
		}

		fileType, err := filetype.Match(buf)

		if err != nil {
			types.ErrorReaderOpen.Throw(c, err)
			return
		}

		fileMeta := types.File{
			Owner:     uid,
			Name:      file.Filename,
			CreatedAt: time.Now(),
			Size:      int64(len(buf)),
			Mime:      fileType.MIME.Value,
		}

		if err := cfg.Database.Create(&fileMeta).Error; err != nil {
			types.ErrorDatabase.Throw(c, err)
			return
		}

		_, err = cfg.MinioClient.PutObject(cfg.Context, cfg.Bucket, fileMeta.ID.String(), intoReader(buf), fileMeta.Size, minio.PutObjectOptions{ContentType: fileMeta.Mime})

		if err != nil {
			types.ErrorS3.Throw(c, err)
			return
		}

		c.JSON(200, gin.H{"message": "file uploaded successfully", "id": fileMeta.ID.String()})
	})

	api.DELETE("/:id", session.SessionMiddleware(cfg.JWTSecret), func(c *gin.Context) {
		claims := c.MustGet("claims").(jwt.MapClaims)
		user := claims["user"].(auth.User)

		fileID, err := uuid.FromString(strings.TrimSpace(c.Param("id")))
		if err != nil {
			types.ErrorInvalidFile.Throw(c, err)
			return
		}

		var file *types.File

		err = cfg.Database.First(&file, fileID).Error

		if err != nil {
			types.ErrorFileNotFound.Throw(c, err)
			return
		}

		if file.Owner != user.ID {
			types.ErrorUnauthorized.Throw(c, nil)
			return
		}

		if err := cfg.MinioClient.RemoveObject(cfg.Context, cfg.Bucket, fileID.String(), minio.RemoveObjectOptions{}); err != nil {
			types.ErrorS3.Throw(c, err)
			return
		}

		if err := cfg.Database.Delete(&file).Error; err != nil {
			types.ErrorDatabase.Throw(c, err)
			return
		}

		c.JSON(200, gin.H{"message": "file deleted successfully"})
	})

	api.GET("/:id", func(c *gin.Context) {
		fileID := c.Param("id")
		fileID = strings.TrimSpace(fileID)

		var file *types.File

		id, err := uuid.FromString(fileID)
		if err != nil {
			types.ErrorInvalidFile.Throw(c, err)
			return
		}

		cfg.Database.First(&file, id)

		if file == nil {
			types.ErrorFileNotFound.Throw(c, nil)
			return
		}

		object, err := cfg.MinioClient.GetObject(cfg.Context, cfg.Bucket, fileID, minio.GetObjectOptions{})
		if err != nil {
			types.ErrorS3.Throw(c, err)
			return
		}
		if _, err := object.Stat(); err != nil {
			types.ErrorFileNotFound.Throw(c, err)
			return
		}

		c.DataFromReader(200, file.Size, file.Mime, object, nil)
	})

	api.GET("/list", session.SessionMiddleware(cfg.JWTSecret), func(c *gin.Context) {
		claims := c.MustGet("claims").(jwt.MapClaims)
		user := claims["user"].(auth.User)

		var files []types.File
		if err := cfg.Database.Where("owner = ?", user.ID).Find(&files).Error; err != nil {
			types.ErrorDatabase.Throw(c, err)
			return
		}

		c.JSON(200, files)
	})
}