package api

import (
	"bytes"
	"errors"
	"io"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/go-sql-driver/mysql"
	"gitlab.celogeek.com/photos/api/internal/photos/models"
	"gitlab.celogeek.com/photos/api/internal/photoserrors"
	"gorm.io/gorm"
)

var CHUNK_SIZE int64 = 1 << 20

type File struct {
	Name     string   `json:"name"`
	Checksum string   `json:"checksum"`
	Chunks   []string `json:"chunks"`
}

func (s *Service) FileCreate(c *gin.Context) {
	file := &File{}
	if err := c.ShouldBindJSON(file); err != nil {
		s.Error(c, http.StatusInternalServerError, err)
		return
	}

	if len(file.Name) < 1 {
		s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreMissingName)
		return
	}

	if len(file.Checksum) != 40 {
		s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
		return
	}
	if len(file.Chunks) == 0 {
		s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreMissingChunks)
		return
	}

	for _, chunk := range file.Chunks {
		if len(chunk) != 40 {
			s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
			return
		}
	}

	r, rs, err := s.Store.Combine(file.Chunks)
	if err != nil {
		if strings.HasPrefix(err.Error(), "chunk") && strings.HasSuffix(err.Error(), "doesn't exists") {
			s.Error(c, http.StatusBadRequest, err)
		} else {
			s.Error(c, http.StatusInternalServerError, err)
		}
		return
	}

	if r != file.Checksum {
		s.Error(c, http.StatusExpectationFailed, photoserrors.ErrStoreMismatchChecksum)
		return
	}

	sess := s.CurrentSession(c)

	f := &models.File{
		Name:     file.Name,
		Checksum: file.Checksum,
		Size:     rs,
		AuthorId: &sess.AccountId,
	}

	err = s.DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(f).Error; err != nil {
			return err
		}
		for i, chunk := range file.Chunks {
			fc := &models.FileChunk{
				FileId:   f.ID,
				Part:     uint32(i + 1),
				Checksum: chunk,
			}
			if err := tx.Create(fc).Error; err != nil {
				return err
			}
		}
		return nil
	})

	if nerr, ok := err.(*mysql.MySQLError); ok {
		if nerr.Number == 1062 {
			// duplicate error
			if strings.HasSuffix(nerr.Message, "for key 'checksum'") {
				err = nil
			}
		}
	}

	if err != nil {
		s.Error(c, http.StatusInternalServerError, err)
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"status":   "success",
		"sum":      file.Checksum,
		"nbChunks": len(file.Chunks),
		"size":     rs,
	})
}

func (s *Service) FileCreateChunk(c *gin.Context) {
	if c.Request.ContentLength > CHUNK_SIZE {
		s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChunkSize)
		return
	}

	b := bytes.NewBuffer([]byte{})
	io.Copy(b, c.Request.Body)
	c.Request.Body.Close()

	sess := s.CurrentSession(c)

	chunk := s.Store.NewChunk(b.Bytes())
	if err := chunk.Save(sess); err != nil {
		if errors.Is(err, photoserrors.ErrStoreChunkAlreadyExists) {
			c.JSON(http.StatusOK, gin.H{
				"status":   "success",
				"checksum": chunk.Sum,
			})
		} else {
			s.Error(c, http.StatusBadRequest, err)
		}
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"status":   "success",
		"checksum": chunk.Sum,
	})
}

func (s *Service) FileExists(c *gin.Context) {
	checksum := c.Param("checksum")
	if len(checksum) != 40 {
		s.Error(c, http.StatusBadRequest, photoserrors.ErrStoreBadChecksum)
		return
	}

	var fileExists int64
	if err := s.DB.Model(&models.File{}).Where("checksum = ?", checksum).Count(&fileExists).Error; err != nil {
		s.Error(c, http.StatusInternalServerError, err)
		return
	}

	if fileExists > 0 {
		c.Status(http.StatusOK)
	} else {
		c.Status(http.StatusNotFound)
	}
}