// 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 models

import (
	"code.vikunja.io/api/pkg/files"
	"code.vikunja.io/api/pkg/utils"
	"code.vikunja.io/web"
)

// ListDuplicate holds everything needed to duplicate a list
type ListDuplicate struct {
	// The list id of the list to duplicate
	ListID int64 `json:"-" param:"listid"`
	// The target namespace ID
	NamespaceID int64 `json:"namespace_id,omitempty"`

	// The copied list
	List *List `json:",omitempty"`

	web.Rights   `json:"-"`
	web.CRUDable `json:"-"`
}

// CanCreate checks if a user has the right to duplicate a list
func (ld *ListDuplicate) CanCreate(a web.Auth) (canCreate bool, err error) {
	// List Exists + user has read access to list
	ld.List = &List{ID: ld.ListID}
	canRead, err := ld.List.CanRead(a)
	if err != nil || !canRead {
		return canRead, err
	}

	// Namespace exists + user has write access to is (-> can create new lists)
	ld.List.NamespaceID = ld.NamespaceID
	return ld.List.CanCreate(a)
}

// Create duplicates a list
// @Summary Duplicate an existing list
// @Description Copies the list, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one list to a new namespace. The user needs read access in the list and write access in the namespace of the new list.
// @tags list
// @Accept json
// @Produce json
// @Security JWTKeyAuth
// @Param listID path int true "The list ID to duplicate"
// @Param list body models.ListDuplicate true "The target namespace which should hold the copied list."
// @Success 200 {object} models.ListDuplicate "The created list."
// @Failure 400 {object} web.HTTPError "Invalid list duplicate object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list or namespace"
// @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/duplicate [put]
func (ld *ListDuplicate) Create(a web.Auth) (err error) {

	ld.List.ID = 0
	ld.List.Identifier = "" // Reset the identifier to trigger regenerating a new one
	// Set the owner to the current user
	ld.List.OwnerID = a.GetID()
	if err := CreateOrUpdateList(ld.List); err != nil {
		return err
	}

	// Duplicate kanban buckets
	// Old bucket ID as key, new id as value
	// Used to map the newly created tasks to their new buckets
	bucketMap := make(map[int64]int64)
	buckets := []*Bucket{}
	err = x.Where("list_id = ?", ld.ListID).Find(&buckets)
	if err != nil {
		return
	}
	for _, b := range buckets {
		oldID := b.ID
		b.ID = 0
		b.ListID = ld.List.ID
		if err := b.Create(a); err != nil {
			return err
		}
		bucketMap[oldID] = b.ID
	}

	// Get all tasks + all task details
	tasks, _, _, err := getTasksForLists([]*List{{ID: ld.ListID}}, &taskOptions{})
	if err != nil {
		return err
	}

	taskMap := make(map[int64]int64)
	// Create + update all tasks (includes reminders)
	oldTaskIDs := make([]int64, len(tasks))
	for _, t := range tasks {
		oldID := t.ID
		t.ID = 0
		t.ListID = ld.List.ID
		t.BucketID = bucketMap[t.BucketID]
		t.UID = ""
		err := createTask(t, a, false)
		if err != nil {
			return err
		}
		taskMap[oldID] = t.ID
		oldTaskIDs = append(oldTaskIDs, oldID)
	}

	// Save all attachments
	// We also duplicate all underlying files since they could be modified in one list which would result in
	// file changes in the other list which is not something we want.
	attachments, err := getTaskAttachmentsByTaskIDs(oldTaskIDs)
	if err != nil {
		return err
	}

	for _, attachment := range attachments {
		attachment.ID = 0
		attachment.TaskID = oldTaskIDs[attachment.TaskID]
		attachment.File = &files.File{ID: attachment.FileID}
		if err := attachment.File.LoadFileMetaByID(); err != nil {
			if files.IsErrFileDoesNotExist(err) {
				continue
			}
			return err
		}
		if err := attachment.File.LoadFileByID(); err != nil {
			return err
		}

		err := attachment.NewAttachment(attachment.File.File, attachment.File.Name, attachment.File.Size, a)
		if err != nil {
			return err
		}

		if attachment.File.File != nil {
			_ = attachment.File.File.Close()
		}
	}

	// Copy label tasks (not the labels)
	labelTasks := []*LabelTask{}
	err = x.In("task_id", oldTaskIDs).Find(&labelTasks)
	if err != nil {
		return
	}

	for _, lt := range labelTasks {
		lt.ID = 0
		lt.TaskID = taskMap[lt.TaskID]
		if _, err := x.Insert(lt); err != nil {
			return err
		}
	}

	// Assignees
	// Only copy those assignees who have access to the task
	assignees := []*TaskAssginee{}
	err = x.In("task_id", oldTaskIDs).Find(&assignees)
	if err != nil {
		return
	}
	for _, a := range assignees {
		t := &Task{
			ID:     taskMap[a.TaskID],
			ListID: ld.List.ID,
		}
		if err := t.addNewAssigneeByID(a.UserID, ld.List); err != nil {
			if IsErrUserDoesNotHaveAccessToList(err) {
				continue
			}
			return err
		}
	}

	// Comments
	comments := []*TaskComment{}
	err = x.In("task_id", oldTaskIDs).Find(&comments)
	if err != nil {
		return
	}
	for _, c := range comments {
		c.ID = 0
		c.TaskID = taskMap[c.TaskID]
		if _, err := x.Insert(c); err != nil {
			return err
		}
	}

	// Relations in that list
	// Low-Effort: Only copy those relations which are between tasks in the same list
	// because we can do that without a lot of hassle
	relations := []*TaskRelation{}
	err = x.In("task_id", oldTaskIDs).Find(&relations)
	if err != nil {
		return
	}
	for _, r := range relations {
		otherTaskID, exists := taskMap[r.OtherTaskID]
		if !exists {
			continue
		}
		r.ID = 0
		r.OtherTaskID = otherTaskID
		r.TaskID = taskMap[r.TaskID]
		if _, err := x.Insert(r); err != nil {
			return err
		}
	}

	// Background files + unsplash info
	if ld.List.BackgroundFileID != 0 {
		f := &files.File{ID: ld.List.BackgroundFileID}
		if err := f.LoadFileMetaByID(); err != nil {
			return err
		}
		if err := f.LoadFileByID(); err != nil {
			return err
		}
		defer f.File.Close()

		file, err := files.Create(f.File, f.Name, f.Size, a)
		if err != nil {
			return err
		}

		// Get unsplash info if applicable
		up, err := GetUnsplashPhotoByFileID(ld.List.BackgroundFileID)
		if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
			return err
		}
		if up != nil {
			up.ID = 0
			up.FileID = file.ID
			if err := up.Save(); err != nil {
				return err
			}
		}

		ld.List.BackgroundFileID = file.ID
		if err := CreateOrUpdateList(ld.List); err != nil {
			return err
		}
	}

	// Rights / Shares
	// To keep it simple(r) we will only copy rights which are directly used with the list, no namespace changes.
	users := []*ListUser{}
	err = x.Where("list_id = ?", ld.ListID).Find(&users)
	if err != nil {
		return
	}
	for _, u := range users {
		u.ID = 0
		u.ListID = ld.List.ID
		if _, err := x.Insert(u); err != nil {
			return err
		}
	}
	teams := []*TeamList{}
	err = x.Where("list_id = ?", ld.ListID).Find(&teams)
	if err != nil {
		return
	}
	for _, t := range teams {
		t.ID = 0
		t.ListID = ld.List.ID
		if _, err := x.Insert(t); err != nil {
			return err
		}
	}

	// Generate new link shares if any are available
	linkShares := []*LinkSharing{}
	err = x.Where("list_id = ?", ld.ListID).Find(&linkShares)
	if err != nil {
		return
	}
	for _, share := range linkShares {
		share.ID = 0
		share.ListID = ld.List.ID
		share.Hash = utils.MakeRandomString(40)
		if _, err := x.Insert(share); err != nil {
			return err
		}
	}

	return
}