// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// 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 files

import (
	"code.vikunja.io/api/pkg/config"
	"code.vikunja.io/api/pkg/timeutil"
	"code.vikunja.io/web"
	"github.com/c2h5oh/datasize"
	"github.com/spf13/afero"
	"io"
	"strconv"
	"time"
)

// File holds all information about a file
type File struct {
	ID   int64  `xorm:"int(11) autoincr not null unique pk" json:"id"`
	Name string `xorm:"text not null" json:"name"`
	Mime string `xorm:"text null" json:"mime"`
	Size uint64 `xorm:"int(11) not null" json:"size"`

	Created time.Time `xorm:"-" json:"created"`

	CreatedUnix timeutil.TimeStamp `xorm:"created" json:"-"`
	CreatedByID int64              `xorm:"int(11) not null" json:"-"`

	File afero.File `xorm:"-" json:"-"`
	// This ReadCloser is only used for migration purposes. Use with care!
	// There is currentlc no better way of doing this.
	FileContent []byte `xorm:"-" json:"-"`
}

// TableName is the table name for the files table
func (File) TableName() string {
	return "files"
}

func (f *File) getFileName() string {
	return config.FilesBasePath.GetString() + "/" + strconv.FormatInt(f.ID, 10)
}

// LoadFileByID returns a file by its ID
func (f *File) LoadFileByID() (err error) {
	f.File, err = afs.Open(f.getFileName())
	return
}

// LoadFileMetaByID loads everything about a file without loading the actual file
func (f *File) LoadFileMetaByID() (err error) {
	exists, err := x.Where("id = ?", f.ID).Get(f)
	if !exists {
		return ErrFileDoesNotExist{FileID: f.ID}
	}
	f.Created = f.CreatedUnix.ToTime()
	return
}

// Create creates a new file from an FileHeader
func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file *File, err error) {

	// Get and parse the configured file size
	var maxSize datasize.ByteSize
	err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
	if err != nil {
		return nil, err
	}
	if realsize > maxSize.Bytes() {
		return nil, ErrFileIsTooLarge{Size: realsize}
	}

	// We first insert the file into the db to get it's ID
	file = &File{
		Name:        realname,
		Size:        realsize,
		CreatedByID: a.GetID(),
	}

	_, err = x.Insert(file)
	if err != nil {
		return
	}

	// Save the file to storage with its new ID as path
	err = afs.WriteReader(file.getFileName(), f)
	return
}

// Delete removes a file from the DB and the file system
func (f *File) Delete() (err error) {
	deleted, err := x.Where("id = ?", f.ID).Delete(f)
	if err != nil {
		return err
	}
	if deleted == 0 {
		return ErrFileDoesNotExist{FileID: f.ID}
	}

	err = afs.Remove(f.getFileName())
	return
}
