// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present 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 Affero General Public Licensee 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 Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package models

import (
	"fmt"
	"net/http"
	"strings"

	"code.vikunja.io/api/pkg/config"
	"code.vikunja.io/web"
)

// Generic

// ErrGenericForbidden represents a "UsernameAlreadyExists" kind of error.
type ErrGenericForbidden struct{}

// IsErrGenericForbidden checks if an error is a ErrGenericForbidden.
func IsErrGenericForbidden(err error) bool {
	_, ok := err.(ErrGenericForbidden)
	return ok
}

func (err ErrGenericForbidden) Error() string {
	return "Forbidden"
}

// ErrorCodeGenericForbidden holds the unique world-error code of this error
const ErrorCodeGenericForbidden = 0001

// HTTPError holds the http error description
func (err ErrGenericForbidden) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrorCodeGenericForbidden, Message: "You're not allowed to do this."}
}

// ===================
// Empty things errors
// ===================

// ErrIDCannotBeZero represents a "IDCannotBeZero" kind of error. Used if an ID (of something, not defined) is 0 where it should not.
type ErrIDCannotBeZero struct{}

// IsErrIDCannotBeZero checks if an error is a ErrIDCannotBeZero.
func IsErrIDCannotBeZero(err error) bool {
	_, ok := err.(ErrIDCannotBeZero)
	return ok
}

func (err ErrIDCannotBeZero) Error() string {
	return "ID cannot be empty or 0"
}

// ErrCodeIDCannotBeZero holds the unique world-error code of this error
const ErrCodeIDCannotBeZero = 2001

// HTTPError holds the http error description
func (err ErrIDCannotBeZero) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeIDCannotBeZero, Message: "The ID cannot be empty or 0."}
}

// ErrInvalidData represents a "ErrInvalidData" kind of error. Used when a struct is invalid -> validation failed.
type ErrInvalidData struct {
	Message string
}

// IsErrInvalidData checks if an error is a ErrIDCannotBeZero.
func IsErrInvalidData(err error) bool {
	_, ok := err.(ErrInvalidData)
	return ok
}

func (err ErrInvalidData) Error() string {
	return fmt.Sprintf("Struct is invalid. %s", err.Message)
}

// ErrCodeInvalidData holds the unique world-error code of this error
const ErrCodeInvalidData = 2002

// HTTPError holds the http error description
func (err ErrInvalidData) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidData, Message: err.Message}
}

// ValidationHTTPError is the http error when a validation fails
type ValidationHTTPError struct {
	web.HTTPError
	InvalidFields []string `json:"invalid_fields"`
}

// Error implements the Error type (so we can return it as type error)
func (err ValidationHTTPError) Error() string {
	theErr := ErrInvalidData{
		Message: err.Message,
	}
	return theErr.Error()
}

func InvalidFieldError(fields []string) error {
	return ValidationHTTPError{
		HTTPError: web.HTTPError{
			HTTPCode: http.StatusPreconditionFailed,
			Code:     ErrCodeInvalidData,
			Message:  "Invalid Data",
		},
		InvalidFields: fields,
	}
}

// ===========
// Project errors
// ===========

// ErrProjectDoesNotExist represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
type ErrProjectDoesNotExist struct {
	ID int64
}

// IsErrProjectDoesNotExist checks if an error is a ErrProjectDoesNotExist.
func IsErrProjectDoesNotExist(err error) bool {
	_, ok := err.(ErrProjectDoesNotExist)
	return ok
}

func (err ErrProjectDoesNotExist) Error() string {
	return fmt.Sprintf("Project does not exist [ID: %d]", err.ID)
}

// ErrCodeProjectDoesNotExist holds the unique world-error code of this error
const ErrCodeProjectDoesNotExist = 3001

// HTTPError holds the http error description
func (err ErrProjectDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeProjectDoesNotExist, Message: "This project does not exist."}
}

// ErrNeedToHaveProjectReadAccess represents an error, where the user dont has read access to that Project
type ErrNeedToHaveProjectReadAccess struct {
	ProjectID int64
	UserID    int64
}

// IsErrNeedToHaveProjectReadAccess checks if an error is a ErrProjectDoesNotExist.
func IsErrNeedToHaveProjectReadAccess(err error) bool {
	_, ok := err.(ErrNeedToHaveProjectReadAccess)
	return ok
}

func (err ErrNeedToHaveProjectReadAccess) Error() string {
	return fmt.Sprintf("User needs to have read access to that project [ProjectID: %d, ID: %d]", err.ProjectID, err.UserID)
}

// ErrCodeNeedToHaveProjectReadAccess holds the unique world-error code of this error
const ErrCodeNeedToHaveProjectReadAccess = 3004

// HTTPError holds the http error description
func (err ErrNeedToHaveProjectReadAccess) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveProjectReadAccess, Message: "You need to have read access to this project."}
}

// ErrProjectTitleCannotBeEmpty represents a "ErrProjectTitleCannotBeEmpty" kind of error. Used if the project does not exist.
type ErrProjectTitleCannotBeEmpty struct{}

// IsErrProjectTitleCannotBeEmpty checks if an error is a ErrProjectTitleCannotBeEmpty.
func IsErrProjectTitleCannotBeEmpty(err error) bool {
	_, ok := err.(ErrProjectTitleCannotBeEmpty)
	return ok
}

func (err ErrProjectTitleCannotBeEmpty) Error() string {
	return "Project title cannot be empty."
}

// ErrCodeProjectTitleCannotBeEmpty holds the unique world-error code of this error
const ErrCodeProjectTitleCannotBeEmpty = 3005

// HTTPError holds the http error description
func (err ErrProjectTitleCannotBeEmpty) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeProjectTitleCannotBeEmpty, Message: "You must provide at least a project title."}
}

// ErrProjectShareDoesNotExist represents a "ErrProjectShareDoesNotExist" kind of error. Used if the project share does not exist.
type ErrProjectShareDoesNotExist struct {
	ID   int64
	Hash string
}

// IsErrProjectShareDoesNotExist checks if an error is a ErrProjectShareDoesNotExist.
func IsErrProjectShareDoesNotExist(err error) bool {
	_, ok := err.(ErrProjectShareDoesNotExist)
	return ok
}

func (err ErrProjectShareDoesNotExist) Error() string {
	return "Project share does not exist."
}

// ErrCodeProjectShareDoesNotExist holds the unique world-error code of this error
const ErrCodeProjectShareDoesNotExist = 3006

// HTTPError holds the http error description
func (err ErrProjectShareDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeProjectShareDoesNotExist, Message: "The project share does not exist."}
}

// ErrProjectIdentifierIsNotUnique represents a "ErrProjectIdentifierIsNotUnique" kind of error. Used if the provided project identifier is not unique.
type ErrProjectIdentifierIsNotUnique struct {
	Identifier string
}

// IsErrProjectIdentifierIsNotUnique checks if an error is a ErrProjectIdentifierIsNotUnique.
func IsErrProjectIdentifierIsNotUnique(err error) bool {
	_, ok := err.(ErrProjectIdentifierIsNotUnique)
	return ok
}

func (err ErrProjectIdentifierIsNotUnique) Error() string {
	return "Project identifier is not unique."
}

// ErrCodeProjectIdentifierIsNotUnique holds the unique world-error code of this error
const ErrCodeProjectIdentifierIsNotUnique = 3007

// HTTPError holds the http error description
func (err ErrProjectIdentifierIsNotUnique) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeProjectIdentifierIsNotUnique,
		Message:  "A project with this identifier already exists.",
	}
}

// ErrProjectIsArchived represents an error, where a project is archived
type ErrProjectIsArchived struct {
	ProjectID int64
}

// IsErrProjectIsArchived checks if an error is a project is archived error.
func IsErrProjectIsArchived(err error) bool {
	_, ok := err.(ErrProjectIsArchived)
	return ok
}

func (err ErrProjectIsArchived) Error() string {
	return fmt.Sprintf("Project is archived [ProjectID: %d]", err.ProjectID)
}

// ErrCodeProjectIsArchived holds the unique world-error code of this error
const ErrCodeProjectIsArchived = 3008

// HTTPError holds the http error description
func (err ErrProjectIsArchived) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeProjectIsArchived, Message: "This project is archived. Editing or creating new tasks is not possible."}
}

// ErrProjectCannotBelongToAPseudoParentProject represents an error where a project cannot belong to a pseudo project
type ErrProjectCannotBelongToAPseudoParentProject struct {
	ProjectID       int64
	ParentProjectID int64
}

// IsErrProjectCannotBelongToAPseudoParentProject checks if an error is a project is archived error.
func IsErrProjectCannotBelongToAPseudoParentProject(err error) bool {
	_, ok := err.(*ErrProjectCannotBelongToAPseudoParentProject)
	return ok
}

func (err *ErrProjectCannotBelongToAPseudoParentProject) Error() string {
	return fmt.Sprintf("Project cannot belong to a pseudo parent project [ProjectID: %d, ParentProjectID: %d]", err.ProjectID, err.ParentProjectID)
}

// ErrCodeProjectCannotBelongToAPseudoParentProject holds the unique world-error code of this error
const ErrCodeProjectCannotBelongToAPseudoParentProject = 3009

// HTTPError holds the http error description
func (err *ErrProjectCannotBelongToAPseudoParentProject) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeProjectCannotBelongToAPseudoParentProject,
		Message:  "This project cannot belong a dynamically generated project.",
	}
}

// ErrProjectCannotBeChildOfItself represents an error where a project cannot become a child of its own
type ErrProjectCannotBeChildOfItself struct {
	ProjectID int64
}

// IsErrProjectCannotBeChildOfItsOwn checks if an error is a project is archived error.
func IsErrProjectCannotBeChildOfItsOwn(err error) bool {
	_, ok := err.(*ErrProjectCannotBeChildOfItself)
	return ok
}

func (err *ErrProjectCannotBeChildOfItself) Error() string {
	return fmt.Sprintf("Project cannot be made a child of itself [ProjectID: %d]", err.ProjectID)
}

// ErrCodeProjectCannotBeChildOfItself holds the unique world-error code of this error
const ErrCodeProjectCannotBeChildOfItself = 3010

// HTTPError holds the http error description
func (err *ErrProjectCannotBeChildOfItself) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeProjectCannotBeChildOfItself,
		Message:  "This project cannot be a child of itself.",
	}
}

// ErrProjectCannotHaveACyclicRelationship represents an error where a project cannot have a cyclic parent relationship
type ErrProjectCannotHaveACyclicRelationship struct {
	ProjectID int64
	CycleIDs  []int64
}

// IsErrProjectCannotHaveACyclicRelationship checks if an error is a project is archived error.
func IsErrProjectCannotHaveACyclicRelationship(err error) bool {
	_, ok := err.(*ErrProjectCannotHaveACyclicRelationship)
	return ok
}

func (err *ErrProjectCannotHaveACyclicRelationship) CycleString() string {
	var cycle string
	for _, projectID := range err.CycleIDs {
		cycle += fmt.Sprintf("%d -> ", projectID)
	}
	return strings.TrimSuffix(cycle, " -> ")
}

func (err *ErrProjectCannotHaveACyclicRelationship) Error() string {
	return fmt.Sprintf("Project cannot have a cyclic relationship [ProjectID: %d]", err.ProjectID)
}

// ErrCodeProjectCannotHaveACyclicRelationship holds the unique world-error code of this error
const ErrCodeProjectCannotHaveACyclicRelationship = 3011

// HTTPError holds the http error description
func (err *ErrProjectCannotHaveACyclicRelationship) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeProjectCannotHaveACyclicRelationship,
		Message:  "This project cannot have a cyclic relationship to a parent project.",
	}
}

// ErrCannotDeleteDefaultProject represents an error where the default project is being deleted
type ErrCannotDeleteDefaultProject struct {
	ProjectID int64
}

// IsErrCannotDeleteDefaultProject checks if an error is a project is archived error.
func IsErrCannotDeleteDefaultProject(err error) bool {
	_, ok := err.(*ErrCannotDeleteDefaultProject)
	return ok
}

func (err *ErrCannotDeleteDefaultProject) Error() string {
	return fmt.Sprintf("Default project cannot be deleted [ProjectID: %d]", err.ProjectID)
}

// ErrCodeCannotDeleteDefaultProject holds the unique world-error code of this error
const ErrCodeCannotDeleteDefaultProject = 3012

// HTTPError holds the http error description
func (err *ErrCannotDeleteDefaultProject) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeCannotDeleteDefaultProject,
		Message:  "This project cannot be deleted because it is the default project of a user.",
	}
}

// ErrCannotArchiveDefaultProject represents an error where the default project is being deleted
type ErrCannotArchiveDefaultProject struct {
	ProjectID int64
}

// IsErrCannotArchiveDefaultProject checks if an error is a project is archived error.
func IsErrCannotArchiveDefaultProject(err error) bool {
	_, ok := err.(*ErrCannotArchiveDefaultProject)
	return ok
}

func (err *ErrCannotArchiveDefaultProject) Error() string {
	return fmt.Sprintf("Default project cannot be archived [ProjectID: %d]", err.ProjectID)
}

// ErrCodeCannotArchiveDefaultProject holds the unique world-error code of this error
const ErrCodeCannotArchiveDefaultProject = 3013

// HTTPError holds the http error description
func (err *ErrCannotArchiveDefaultProject) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeCannotArchiveDefaultProject,
		Message:  "This project cannot be archived because it is the default project of a user.",
	}
}

// ==============
// Task errors
// ==============

// ErrTaskCannotBeEmpty represents a "ErrTaskCannotBeEmpty" kind of error.
type ErrTaskCannotBeEmpty struct{}

// IsErrTaskCannotBeEmpty checks if an error is a ErrProjectDoesNotExist.
func IsErrTaskCannotBeEmpty(err error) bool {
	_, ok := err.(ErrTaskCannotBeEmpty)
	return ok
}

func (err ErrTaskCannotBeEmpty) Error() string {
	return "Task title cannot be empty."
}

// ErrCodeTaskCannotBeEmpty holds the unique world-error code of this error
const ErrCodeTaskCannotBeEmpty = 4001

// HTTPError holds the http error description
func (err ErrTaskCannotBeEmpty) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTaskCannotBeEmpty, Message: "You must provide at least a task title."}
}

// ErrTaskDoesNotExist represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
type ErrTaskDoesNotExist struct {
	ID int64
}

// IsErrTaskDoesNotExist checks if an error is a ErrProjectDoesNotExist.
func IsErrTaskDoesNotExist(err error) bool {
	_, ok := err.(ErrTaskDoesNotExist)
	return ok
}

func (err ErrTaskDoesNotExist) Error() string {
	return fmt.Sprintf("Project task does not exist. [ID: %d]", err.ID)
}

// ErrCodeTaskDoesNotExist holds the unique world-error code of this error
const ErrCodeTaskDoesNotExist = 4002

// HTTPError holds the http error description
func (err ErrTaskDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTaskDoesNotExist, Message: "This task does not exist"}
}

// ErrBulkTasksMustBeInSameProject represents a "ErrBulkTasksMustBeInSameProject" kind of error.
type ErrBulkTasksMustBeInSameProject struct {
	ShouldBeID int64
	IsID       int64
}

// IsErrBulkTasksMustBeInSameProject checks if an error is a ErrBulkTasksMustBeInSameProject.
func IsErrBulkTasksMustBeInSameProject(err error) bool {
	_, ok := err.(ErrBulkTasksMustBeInSameProject)
	return ok
}

func (err ErrBulkTasksMustBeInSameProject) Error() string {
	return fmt.Sprintf("All bulk editing tasks must be in the same project. [Should be: %d, is: %d]", err.ShouldBeID, err.IsID)
}

// ErrCodeBulkTasksMustBeInSameProject holds the unique world-error code of this error
const ErrCodeBulkTasksMustBeInSameProject = 4003

// HTTPError holds the http error description
func (err ErrBulkTasksMustBeInSameProject) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksMustBeInSameProject, Message: "All tasks must be in the same project."}
}

// ErrBulkTasksNeedAtLeastOne represents a "ErrBulkTasksNeedAtLeastOne" kind of error.
type ErrBulkTasksNeedAtLeastOne struct{}

// IsErrBulkTasksNeedAtLeastOne checks if an error is a ErrBulkTasksNeedAtLeastOne.
func IsErrBulkTasksNeedAtLeastOne(err error) bool {
	_, ok := err.(ErrBulkTasksNeedAtLeastOne)
	return ok
}

func (err ErrBulkTasksNeedAtLeastOne) Error() string {
	return "Need at least one task when bulk editing tasks"
}

// ErrCodeBulkTasksNeedAtLeastOne holds the unique world-error code of this error
const ErrCodeBulkTasksNeedAtLeastOne = 4004

// HTTPError holds the http error description
func (err ErrBulkTasksNeedAtLeastOne) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksNeedAtLeastOne, Message: "Need at least one tasks to do bulk editing."}
}

// ErrNoRightToSeeTask represents an error where a user does not have the right to see a task
type ErrNoRightToSeeTask struct {
	TaskID int64
	UserID int64
}

// IsErrNoRightToSeeTask checks if an error is ErrNoRightToSeeTask.
func IsErrNoRightToSeeTask(err error) bool {
	_, ok := err.(ErrNoRightToSeeTask)
	return ok
}

func (err ErrNoRightToSeeTask) Error() string {
	return fmt.Sprintf("User does not have the right to see the task [TaskID: %v, ID: %v]", err.TaskID, err.UserID)
}

// ErrCodeNoRightToSeeTask holds the unique world-error code of this error
const ErrCodeNoRightToSeeTask = 4005

// HTTPError holds the http error description
func (err ErrNoRightToSeeTask) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusForbidden,
		Code:     ErrCodeNoRightToSeeTask,
		Message:  "You don't have the right to see this task.",
	}
}

// ErrParentTaskCannotBeTheSame represents an error where the user tries to set a tasks parent as the same
type ErrParentTaskCannotBeTheSame struct {
	TaskID int64
}

// IsErrParentTaskCannotBeTheSame checks if an error is ErrParentTaskCannotBeTheSame.
func IsErrParentTaskCannotBeTheSame(err error) bool {
	_, ok := err.(ErrParentTaskCannotBeTheSame)
	return ok
}

func (err ErrParentTaskCannotBeTheSame) Error() string {
	return fmt.Sprintf("Tried to set a parents task as the same [TaskID: %v]", err.TaskID)
}

// ErrCodeParentTaskCannotBeTheSame holds the unique world-error code of this error
const ErrCodeParentTaskCannotBeTheSame = 4006

// HTTPError holds the http error description
func (err ErrParentTaskCannotBeTheSame) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusForbidden,
		Code:     ErrCodeParentTaskCannotBeTheSame,
		Message:  "You cannot set a parent task to the task itself.",
	}
}

// ErrInvalidRelationKind represents an error where the user tries to use an invalid relation kind
type ErrInvalidRelationKind struct {
	Kind RelationKind
}

// IsErrInvalidRelationKind checks if an error is ErrInvalidRelationKind.
func IsErrInvalidRelationKind(err error) bool {
	_, ok := err.(ErrInvalidRelationKind)
	return ok
}

func (err ErrInvalidRelationKind) Error() string {
	return fmt.Sprintf("Invalid task relation kind [Kind: %v]", err.Kind)
}

// ErrCodeInvalidRelationKind holds the unique world-error code of this error
const ErrCodeInvalidRelationKind = 4007

// HTTPError holds the http error description
func (err ErrInvalidRelationKind) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidRelationKind,
		Message:  "The task relation is invalid.",
	}
}

// ErrRelationAlreadyExists represents an error where the user tries to create an already existing relation
type ErrRelationAlreadyExists struct {
	Kind        RelationKind
	TaskID      int64
	OtherTaskID int64
}

// IsErrRelationAlreadyExists checks if an error is ErrRelationAlreadyExists.
func IsErrRelationAlreadyExists(err error) bool {
	_, ok := err.(ErrRelationAlreadyExists)
	return ok
}

func (err ErrRelationAlreadyExists) Error() string {
	return fmt.Sprintf("Task relation already exists [TaskID: %v, OtherTaskID: %v, Kind: %v]", err.TaskID, err.OtherTaskID, err.Kind)
}

// ErrCodeRelationAlreadyExists holds the unique world-error code of this error
const ErrCodeRelationAlreadyExists = 4008

// HTTPError holds the http error description
func (err ErrRelationAlreadyExists) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusConflict,
		Code:     ErrCodeRelationAlreadyExists,
		Message:  "The task relation already exists.",
	}
}

// ErrRelationDoesNotExist represents an error where a task relation does not exist.
type ErrRelationDoesNotExist struct {
	Kind        RelationKind
	TaskID      int64
	OtherTaskID int64
}

// IsErrRelationDoesNotExist checks if an error is ErrRelationDoesNotExist.
func IsErrRelationDoesNotExist(err error) bool {
	_, ok := err.(ErrRelationDoesNotExist)
	return ok
}

func (err ErrRelationDoesNotExist) Error() string {
	return fmt.Sprintf("Task relation does not exist [TaskID: %v, OtherTaskID: %v, Kind: %v]", err.TaskID, err.OtherTaskID, err.Kind)
}

// ErrCodeRelationDoesNotExist holds the unique world-error code of this error
const ErrCodeRelationDoesNotExist = 4009

// HTTPError holds the http error description
func (err ErrRelationDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusNotFound,
		Code:     ErrCodeRelationDoesNotExist,
		Message:  "The task relation does not exist.",
	}
}

// ErrRelationTasksCannotBeTheSame represents an error where the user tries to relate a task with itself
type ErrRelationTasksCannotBeTheSame struct {
	TaskID      int64
	OtherTaskID int64
}

// IsErrRelationTasksCannotBeTheSame checks if an error is ErrRelationTasksCannotBeTheSame.
func IsErrRelationTasksCannotBeTheSame(err error) bool {
	_, ok := err.(ErrRelationTasksCannotBeTheSame)
	return ok
}

func (err ErrRelationTasksCannotBeTheSame) Error() string {
	return fmt.Sprintf("Tried to relate a task with itself [TaskID: %v, OtherTaskID: %v]", err.TaskID, err.OtherTaskID)
}

// ErrCodeRelationTasksCannotBeTheSame holds the unique world-error code of this error
const ErrCodeRelationTasksCannotBeTheSame = 4010

// HTTPError holds the http error description
func (err ErrRelationTasksCannotBeTheSame) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeRelationTasksCannotBeTheSame,
		Message:  "You cannot relate a task with itself",
	}
}

// ErrTaskAttachmentDoesNotExist represents an error where the user tries to relate a task with itself
type ErrTaskAttachmentDoesNotExist struct {
	TaskID       int64
	AttachmentID int64
	FileID       int64
}

// IsErrTaskAttachmentDoesNotExist checks if an error is ErrTaskAttachmentDoesNotExist.
func IsErrTaskAttachmentDoesNotExist(err error) bool {
	_, ok := err.(ErrTaskAttachmentDoesNotExist)
	return ok
}

func (err ErrTaskAttachmentDoesNotExist) Error() string {
	return fmt.Sprintf("Task attachment does not exist [TaskID: %d, AttachmentID: %d, FileID: %d]", err.TaskID, err.AttachmentID, err.FileID)
}

// ErrCodeTaskAttachmentDoesNotExist holds the unique world-error code of this error
const ErrCodeTaskAttachmentDoesNotExist = 4011

// HTTPError holds the http error description
func (err ErrTaskAttachmentDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusNotFound,
		Code:     ErrCodeTaskAttachmentDoesNotExist,
		Message:  "This task attachment does not exist.",
	}
}

// ErrTaskAttachmentIsTooLarge represents an error where the user tries to relate a task with itself
type ErrTaskAttachmentIsTooLarge struct {
	Size uint64
}

// IsErrTaskAttachmentIsTooLarge checks if an error is ErrTaskAttachmentIsTooLarge.
func IsErrTaskAttachmentIsTooLarge(err error) bool {
	_, ok := err.(ErrTaskAttachmentIsTooLarge)
	return ok
}

func (err ErrTaskAttachmentIsTooLarge) Error() string {
	return fmt.Sprintf("Task attachment is too large [Size: %d]", err.Size)
}

// ErrCodeTaskAttachmentIsTooLarge holds the unique world-error code of this error
const ErrCodeTaskAttachmentIsTooLarge = 4012

// HTTPError holds the http error description
func (err ErrTaskAttachmentIsTooLarge) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeTaskAttachmentIsTooLarge,
		Message:  fmt.Sprintf("The task attachment exceeds the configured file size of %d bytes, filesize was %d", config.FilesMaxSize.GetInt64(), err.Size),
	}
}

// ErrInvalidSortParam represents an error where the provided sort param is invalid
type ErrInvalidSortParam struct {
	SortBy string
}

// IsErrInvalidSortParam checks if an error is ErrInvalidSortParam.
func IsErrInvalidSortParam(err error) bool {
	_, ok := err.(ErrInvalidSortParam)
	return ok
}

func (err ErrInvalidSortParam) Error() string {
	return fmt.Sprintf("Sort param is invalid [SortBy: %s]", err.SortBy)
}

// ErrCodeInvalidSortParam holds the unique world-error code of this error
const ErrCodeInvalidSortParam = 4013

// HTTPError holds the http error description
func (err ErrInvalidSortParam) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidSortParam,
		Message:  fmt.Sprintf("The task sort param '%s' is invalid.", err.SortBy),
	}
}

// ErrInvalidSortOrder represents an error where the provided sort order is invalid
type ErrInvalidSortOrder struct {
	OrderBy sortOrder
}

// IsErrInvalidSortOrder checks if an error is ErrInvalidSortOrder.
func IsErrInvalidSortOrder(err error) bool {
	_, ok := err.(ErrInvalidSortOrder)
	return ok
}

func (err ErrInvalidSortOrder) Error() string {
	return fmt.Sprintf("Sort order is invalid [OrderBy: %s]", err.OrderBy)
}

// ErrCodeInvalidSortOrder holds the unique world-error code of this error
const ErrCodeInvalidSortOrder = 4014

// HTTPError holds the http error description
func (err ErrInvalidSortOrder) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidSortOrder,
		Message:  fmt.Sprintf("The task sort order '%s' is invalid. Allowed is either asc or desc.", err.OrderBy),
	}
}

// ErrTaskCommentDoesNotExist represents an error where a task comment does not exist
type ErrTaskCommentDoesNotExist struct {
	ID     int64
	TaskID int64
}

// IsErrTaskCommentDoesNotExist checks if an error is ErrTaskCommentDoesNotExist.
func IsErrTaskCommentDoesNotExist(err error) bool {
	_, ok := err.(ErrTaskCommentDoesNotExist)
	return ok
}

func (err ErrTaskCommentDoesNotExist) Error() string {
	return fmt.Sprintf("Task comment does not exist [ID: %d, TaskID: %d]", err.ID, err.TaskID)
}

// ErrCodeTaskCommentDoesNotExist holds the unique world-error code of this error
const ErrCodeTaskCommentDoesNotExist = 4015

// HTTPError holds the http error description
func (err ErrTaskCommentDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusNotFound,
		Code:     ErrCodeTaskCommentDoesNotExist,
		Message:  "This task comment does not exist",
	}
}

// ErrInvalidTaskField represents an error where the provided task field is invalid
type ErrInvalidTaskField struct {
	TaskField string
}

// IsErrInvalidTaskField checks if an error is ErrInvalidTaskField.
func IsErrInvalidTaskField(err error) bool {
	_, ok := err.(ErrInvalidTaskField)
	return ok
}

func (err ErrInvalidTaskField) Error() string {
	return fmt.Sprintf("Task Field is invalid [TaskField: %s]", err.TaskField)
}

// ErrCodeInvalidTaskField holds the unique world-error code of this error
const ErrCodeInvalidTaskField = 4016

// HTTPError holds the http error description
func (err ErrInvalidTaskField) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidTaskField,
		Message:  fmt.Sprintf("The task field '%s' is invalid.", err.TaskField),
	}
}

// ErrInvalidTaskFilterComparator represents an error where the provided task field is invalid
type ErrInvalidTaskFilterComparator struct {
	Comparator taskFilterComparator
}

// IsErrInvalidTaskFilterComparator checks if an error is ErrInvalidTaskFilterComparator.
func IsErrInvalidTaskFilterComparator(err error) bool {
	_, ok := err.(ErrInvalidTaskFilterComparator)
	return ok
}

func (err ErrInvalidTaskFilterComparator) Error() string {
	return fmt.Sprintf("Task filter comparator is invalid [Comparator: %s]", err.Comparator)
}

// ErrCodeInvalidTaskFilterComparator holds the unique world-error code of this error
const ErrCodeInvalidTaskFilterComparator = 4017

// HTTPError holds the http error description
func (err ErrInvalidTaskFilterComparator) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidTaskFilterComparator,
		Message:  fmt.Sprintf("The task filter comparator '%s' is invalid.", err.Comparator),
	}
}

// ErrInvalidTaskFilterConcatinator represents an error where the provided task field is invalid
type ErrInvalidTaskFilterConcatinator struct {
	Concatinator taskFilterConcatinator
}

// IsErrInvalidTaskFilterConcatinator checks if an error is ErrInvalidTaskFilterConcatinator.
func IsErrInvalidTaskFilterConcatinator(err error) bool {
	_, ok := err.(ErrInvalidTaskFilterConcatinator)
	return ok
}

func (err ErrInvalidTaskFilterConcatinator) Error() string {
	return fmt.Sprintf("Task filter concatinator is invalid [Concatinator: %s]", err.Concatinator)
}

// ErrCodeInvalidTaskFilterConcatinator holds the unique world-error code of this error
const ErrCodeInvalidTaskFilterConcatinator = 4018

// HTTPError holds the http error description
func (err ErrInvalidTaskFilterConcatinator) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidTaskFilterConcatinator,
		Message:  fmt.Sprintf("The task filter concatinator '%s' is invalid.", err.Concatinator),
	}
}

// ErrInvalidTaskFilterValue represents an error where the provided task filter value is invalid
type ErrInvalidTaskFilterValue struct {
	Value interface{}
	Field string
}

// IsErrInvalidTaskFilterValue checks if an error is ErrInvalidTaskFilterValue.
func IsErrInvalidTaskFilterValue(err error) bool {
	_, ok := err.(ErrInvalidTaskFilterValue)
	return ok
}

func (err ErrInvalidTaskFilterValue) Error() string {
	return fmt.Sprintf("Task filter value is invalid [Value: %v, Field: %s]", err.Value, err.Field)
}

// ErrCodeInvalidTaskFilterValue holds the unique world-error code of this error
const ErrCodeInvalidTaskFilterValue = 4019

// HTTPError holds the http error description
func (err ErrInvalidTaskFilterValue) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidTaskFilterValue,
		Message:  fmt.Sprintf("The task filter value '%s' for field '%s' is invalid.", err.Value, err.Field),
	}
}

// ErrAttachmentDoesNotBelongToTask represents an error where the provided task cover attachment does not belong to the same task
type ErrAttachmentDoesNotBelongToTask struct {
	TaskID       int64
	AttachmentID int64
}

// IsErrAttachmentAndCoverMustBelongToTheSameTask checks if an error is ErrAttachmentDoesNotBelongToTask.
func IsErrAttachmentAndCoverMustBelongToTheSameTask(err error) bool {
	_, ok := err.(ErrAttachmentDoesNotBelongToTask)
	return ok
}

func (err ErrAttachmentDoesNotBelongToTask) Error() string {
	return fmt.Sprintf("Task attachment and cover image do not belong to the same task [TaskID: %d, AttachmentID: %d]", err.TaskID, err.AttachmentID)
}

// ErrCodeAttachmentDoesNotBelongToTask holds the unique world-error code of this error
const ErrCodeAttachmentDoesNotBelongToTask = 4020

// HTTPError holds the http error description
func (err ErrAttachmentDoesNotBelongToTask) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeAttachmentDoesNotBelongToTask,
		Message:  "This attachment does not belong to that task.",
	}
}

// ErrUserAlreadyAssigned represents an error where the user is already assigned to this task
type ErrUserAlreadyAssigned struct {
	TaskID int64
	UserID int64
}

// IsErrUserAlreadyAssigned checks if an error is ErrUserAlreadyAssigned.
func IsErrUserAlreadyAssigned(err error) bool {
	_, ok := err.(ErrUserAlreadyAssigned)
	return ok
}

func (err ErrUserAlreadyAssigned) Error() string {
	return fmt.Sprintf("User is already assigned to task [TaskID: %d, ID: %d]", err.TaskID, err.UserID)
}

// ErrCodeUserAlreadyAssigned holds the unique world-error code of this error
const ErrCodeUserAlreadyAssigned = 4021

// HTTPError holds the http error description
func (err ErrUserAlreadyAssigned) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeUserAlreadyAssigned,
		Message:  "This user is already assigned to that task.",
	}
}

// ErrReminderRelativeToMissing represents an error where a task has a relative reminder without reference date
type ErrReminderRelativeToMissing struct {
	TaskID int64
}

// IsErrReminderRelativeToMissing checks if an error is ErrReminderRelativeToMissing.
func IsErrReminderRelativeToMissing(err error) bool {
	_, ok := err.(ErrReminderRelativeToMissing)
	return ok
}

func (err ErrReminderRelativeToMissing) Error() string {
	return fmt.Sprintf("Task [TaskID: %v] has a relative reminder without relative_to", err.TaskID)
}

// ErrCodeRelationDoesNotExist holds the unique world-error code of this error
const ErrCodeReminderRelativeToMissing = 4022

// HTTPError holds the http error description
func (err ErrReminderRelativeToMissing) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeReminderRelativeToMissing,
		Message:  "Please provide what the reminder date is relative to",
	}
}

// ErrTaskRelationCycle represents an error where the user tries to create an already existing relation
type ErrTaskRelationCycle struct {
	Kind        RelationKind
	TaskID      int64
	OtherTaskID int64
}

// IsErrTaskRelationCycle checks if an error is ErrTaskRelationCycle.
func IsErrTaskRelationCycle(err error) bool {
	_, ok := err.(ErrTaskRelationCycle)
	return ok
}

func (err ErrTaskRelationCycle) Error() string {
	return fmt.Sprintf("Task relation cycle detectetd [TaskID: %v, OtherTaskID: %v, Kind: %v]", err.TaskID, err.OtherTaskID, err.Kind)
}

// ErrCodeTaskRelationCycle holds the unique world-error code of this error
const ErrCodeTaskRelationCycle = 4022

// HTTPError holds the http error description
func (err ErrTaskRelationCycle) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusConflict,
		Code:     ErrCodeTaskRelationCycle,
		Message:  "This task relation would create a cycle.",
	}
}

// ============
// Team errors
// ============

// ErrTeamNameCannotBeEmpty represents an error where a team name is empty.
type ErrTeamNameCannotBeEmpty struct {
	TeamID int64
}

// IsErrTeamNameCannotBeEmpty checks if an error is a ErrTeamNameCannotBeEmpty.
func IsErrTeamNameCannotBeEmpty(err error) bool {
	_, ok := err.(ErrTeamNameCannotBeEmpty)
	return ok
}

func (err ErrTeamNameCannotBeEmpty) Error() string {
	return fmt.Sprintf("Team name cannot be empty [Team ID: %d]", err.TeamID)
}

// ErrCodeTeamNameCannotBeEmpty holds the unique world-error code of this error
const ErrCodeTeamNameCannotBeEmpty = 6001

// HTTPError holds the http error description
func (err ErrTeamNameCannotBeEmpty) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTeamNameCannotBeEmpty, Message: "The team name cannot be empty"}
}

type ErrTeamDoesNotExist struct {
	TeamID int64
}

// IsErrTeamDoesNotExist checks if an error is ErrTeamDoesNotExist.
func IsErrTeamDoesNotExist(err error) bool {
	_, ok := err.(ErrTeamDoesNotExist)
	return ok
}

func (err ErrTeamDoesNotExist) Error() string {
	return fmt.Sprintf("Team does not exist [Team ID: %d]", err.TeamID)
}

// ErrCodeTeamDoesNotExist holds the unique world-error code of this error
const ErrCodeTeamDoesNotExist = 6002

// HTTPError holds the http error description
func (err ErrTeamDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
}

// ErrTeamAlreadyHasAccess represents an error where a team already has access to a project
type ErrTeamAlreadyHasAccess struct {
	TeamID int64
	ID     int64
}

// IsErrTeamAlreadyHasAccess checks if an error is ErrTeamAlreadyHasAccess.
func IsErrTeamAlreadyHasAccess(err error) bool {
	_, ok := err.(ErrTeamAlreadyHasAccess)
	return ok
}

func (err ErrTeamAlreadyHasAccess) Error() string {
	return fmt.Sprintf("Team already has access. [Team ID: %d, ID: %d]", err.TeamID, err.ID)
}

// ErrCodeTeamAlreadyHasAccess holds the unique world-error code of this error
const ErrCodeTeamAlreadyHasAccess = 6004

// HTTPError holds the http error description
func (err ErrTeamAlreadyHasAccess) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeTeamAlreadyHasAccess, Message: "This team already has access."}
}

// ErrUserIsMemberOfTeam represents an error where a user is already member of a team.
type ErrUserIsMemberOfTeam struct {
	TeamID int64
	UserID int64
}

// IsErrUserIsMemberOfTeam checks if an error is ErrUserIsMemberOfTeam.
func IsErrUserIsMemberOfTeam(err error) bool {
	_, ok := err.(ErrUserIsMemberOfTeam)
	return ok
}

func (err ErrUserIsMemberOfTeam) Error() string {
	return fmt.Sprintf("User is already a member of that team. [Team ID: %d, User ID: %d]", err.TeamID, err.UserID)
}

// ErrCodeUserIsMemberOfTeam holds the unique world-error code of this error
const ErrCodeUserIsMemberOfTeam = 6005

// HTTPError holds the http error description
func (err ErrUserIsMemberOfTeam) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserIsMemberOfTeam, Message: "This user is already a member of that team."}
}

// ErrCannotDeleteLastTeamMember represents an error where a user wants to delete the last member of a team (probably himself)
type ErrCannotDeleteLastTeamMember struct {
	TeamID int64
	UserID int64
}

// IsErrCannotDeleteLastTeamMember checks if an error is ErrCannotDeleteLastTeamMember.
func IsErrCannotDeleteLastTeamMember(err error) bool {
	_, ok := err.(ErrCannotDeleteLastTeamMember)
	return ok
}

func (err ErrCannotDeleteLastTeamMember) Error() string {
	return fmt.Sprintf("Cannot delete last team member. [Team ID: %d, User ID: %d]", err.TeamID, err.UserID)
}

// ErrCodeCannotDeleteLastTeamMember holds the unique world-error code of this error
const ErrCodeCannotDeleteLastTeamMember = 6006

// HTTPError holds the http error description
func (err ErrCannotDeleteLastTeamMember) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."}
}

// ErrTeamDoesNotHaveAccessToProject represents an error, where the Team is not the owner of that Project (used i.e. when deleting a Project)
type ErrTeamDoesNotHaveAccessToProject struct {
	ProjectID int64
	TeamID    int64
}

// IsErrTeamDoesNotHaveAccessToProject checks if an error is a ErrProjectDoesNotExist.
func IsErrTeamDoesNotHaveAccessToProject(err error) bool {
	_, ok := err.(ErrTeamDoesNotHaveAccessToProject)
	return ok
}

func (err ErrTeamDoesNotHaveAccessToProject) Error() string {
	return fmt.Sprintf("Team does not have access to the project [ProjectID: %d, TeamID: %d]", err.ProjectID, err.TeamID)
}

// ErrCodeTeamDoesNotHaveAccessToProject holds the unique world-error code of this error
const ErrCodeTeamDoesNotHaveAccessToProject = 6007

// HTTPError holds the http error description
func (err ErrTeamDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToProject, Message: "This team does not have access to the project."}
}

// ErrOIDCTeamDoesNotExist represents an error where a team with specified name and specified oidcId property does not exist
type ErrOIDCTeamDoesNotExist struct {
	OidcID string
	Name   string
}

// IsErrOIDCTeamDoesNotExist checks if an error is ErrOIDCTeamDoesNotExist.
func IsErrOIDCTeamDoesNotExist(err error) bool {
	_, ok := err.(ErrOIDCTeamDoesNotExist)
	return ok
}

// ErrTeamDoesNotExist represents an error where a team does not exist
func (err ErrOIDCTeamDoesNotExist) Error() string {
	return fmt.Sprintf("No team with that name and valid oidcId could be found. [Team Name: %v] [OidcID : %v] ", err.Name, err.OidcID)
}

// ErrCodeTeamDoesNotExist holds the unique world-error code of this error
const ErrCodeOIDCTeamDoesNotExist = 6008

// HTTPError holds the http error description
func (err ErrOIDCTeamDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "No team with that name and valid oidcId could be found."}
}

// ErrOIDCTeamsDoNotExistForUser represents an error where an oidcTeam does not exist for the user
type ErrOIDCTeamsDoNotExistForUser struct {
	UserID int64
}

// IsErrOIDCTeamsDoNotExistForUser checks if an error is ErrOIDCTeamsDoNotExistForUser.
func IsErrOIDCTeamsDoNotExistForUser(err error) bool {
	_, ok := err.(ErrOIDCTeamsDoNotExistForUser)
	return ok
}

func (err ErrOIDCTeamsDoNotExistForUser) Error() string {
	return fmt.Sprintf("No teams with property oidcId could be found for user [User ID: %d]", err.UserID)
}

// ErrCodeTeamDoesNotExist holds the unique world-error code of this error
const ErrCodeOIDCTeamsDoNotExistForUser = 6009

// HTTPError holds the http error description
func (err ErrOIDCTeamsDoNotExistForUser) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "No Teams with property oidcId could be found for User."}
}

// ====================
// User <-> Project errors
// ====================

// ErrUserAlreadyHasAccess represents an error where a user already has access to a project
type ErrUserAlreadyHasAccess struct {
	UserID    int64
	ProjectID int64
}

// IsErrUserAlreadyHasAccess checks if an error is ErrUserAlreadyHasAccess.
func IsErrUserAlreadyHasAccess(err error) bool {
	_, ok := err.(ErrUserAlreadyHasAccess)
	return ok
}

func (err ErrUserAlreadyHasAccess) Error() string {
	return fmt.Sprintf("User already has access to that project. [User ID: %d, Project ID: %d]", err.UserID, err.ProjectID)
}

// ErrCodeUserAlreadyHasAccess holds the unique world-error code of this error
const ErrCodeUserAlreadyHasAccess = 7002

// HTTPError holds the http error description
func (err ErrUserAlreadyHasAccess) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this project."}
}

// ErrUserDoesNotHaveAccessToProject represents an error, where the user is not the owner of that Project (used i.e. when deleting a Project)
type ErrUserDoesNotHaveAccessToProject struct {
	ProjectID int64
	UserID    int64
}

// IsErrUserDoesNotHaveAccessToProject checks if an error is a ErrProjectDoesNotExist.
func IsErrUserDoesNotHaveAccessToProject(err error) bool {
	_, ok := err.(ErrUserDoesNotHaveAccessToProject)
	return ok
}

func (err ErrUserDoesNotHaveAccessToProject) Error() string {
	return fmt.Sprintf("User does not have access to the project [ProjectID: %d, ID: %d]", err.ProjectID, err.UserID)
}

// ErrCodeUserDoesNotHaveAccessToProject holds the unique world-error code of this error
const ErrCodeUserDoesNotHaveAccessToProject = 7003

// HTTPError holds the http error description
func (err ErrUserDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
	return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToProject, Message: "This user does not have access to the project."}
}

// =============
// Label errors
// =============

// ErrLabelIsAlreadyOnTask represents an error where a label is already bound to a task
type ErrLabelIsAlreadyOnTask struct {
	LabelID int64
	TaskID  int64
}

// IsErrLabelIsAlreadyOnTask checks if an error is ErrLabelIsAlreadyOnTask.
func IsErrLabelIsAlreadyOnTask(err error) bool {
	_, ok := err.(ErrLabelIsAlreadyOnTask)
	return ok
}

func (err ErrLabelIsAlreadyOnTask) Error() string {
	return fmt.Sprintf("Label already exists on task [TaskID: %v, LabelID: %v]", err.TaskID, err.LabelID)
}

// ErrCodeLabelIsAlreadyOnTask holds the unique world-error code of this error
const ErrCodeLabelIsAlreadyOnTask = 8001

// HTTPError holds the http error description
func (err ErrLabelIsAlreadyOnTask) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeLabelIsAlreadyOnTask,
		Message:  "This label already exists on the task.",
	}
}

// ErrLabelDoesNotExist represents an error where a label does not exist
type ErrLabelDoesNotExist struct {
	LabelID int64
}

// IsErrLabelDoesNotExist checks if an error is ErrLabelDoesNotExist.
func IsErrLabelDoesNotExist(err error) bool {
	_, ok := err.(ErrLabelDoesNotExist)
	return ok
}

func (err ErrLabelDoesNotExist) Error() string {
	return fmt.Sprintf("Label does not exist [LabelID: %v]", err.LabelID)
}

// ErrCodeLabelDoesNotExist holds the unique world-error code of this error
const ErrCodeLabelDoesNotExist = 8002

// HTTPError holds the http error description
func (err ErrLabelDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusNotFound,
		Code:     ErrCodeLabelDoesNotExist,
		Message:  "This label does not exist.",
	}
}

// ErrUserHasNoAccessToLabel represents an error where a user does not have the right to see a label
type ErrUserHasNoAccessToLabel struct {
	LabelID int64
	UserID  int64
}

// IsErrUserHasNoAccessToLabel checks if an error is ErrUserHasNoAccessToLabel.
func IsErrUserHasNoAccessToLabel(err error) bool {
	_, ok := err.(ErrUserHasNoAccessToLabel)
	return ok
}

func (err ErrUserHasNoAccessToLabel) Error() string {
	return fmt.Sprintf("The user does not have access to this label [LabelID: %v, ID: %v]", err.LabelID, err.UserID)
}

// ErrCodeUserHasNoAccessToLabel holds the unique world-error code of this error
const ErrCodeUserHasNoAccessToLabel = 8003

// HTTPError holds the http error description
func (err ErrUserHasNoAccessToLabel) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusForbidden,
		Code:     ErrCodeUserHasNoAccessToLabel,
		Message:  "You don't have access to this label.",
	}
}

// ========
// Rights
// ========

// ErrInvalidRight represents an error where a right is invalid
type ErrInvalidRight struct {
	Right Right
}

// IsErrInvalidRight checks if an error is ErrInvalidRight.
func IsErrInvalidRight(err error) bool {
	_, ok := err.(ErrInvalidRight)
	return ok
}

func (err ErrInvalidRight) Error() string {
	return fmt.Sprintf("Right invalid [Right: %d]", err.Right)
}

// ErrCodeInvalidRight holds the unique world-error code of this error
const ErrCodeInvalidRight = 9001

// HTTPError holds the http error description
func (err ErrInvalidRight) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidRight,
		Message:  "The right is invalid.",
	}
}

// ========
// Kanban
// ========

// ErrBucketDoesNotExist represents an error where a kanban bucket does not exist
type ErrBucketDoesNotExist struct {
	BucketID int64
}

// IsErrBucketDoesNotExist checks if an error is ErrBucketDoesNotExist.
func IsErrBucketDoesNotExist(err error) bool {
	_, ok := err.(ErrBucketDoesNotExist)
	return ok
}

func (err ErrBucketDoesNotExist) Error() string {
	return fmt.Sprintf("Bucket does not exist [BucketID: %d]", err.BucketID)
}

// ErrCodeBucketDoesNotExist holds the unique world-error code of this error
const ErrCodeBucketDoesNotExist = 10001

// HTTPError holds the http error description
func (err ErrBucketDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusNotFound,
		Code:     ErrCodeBucketDoesNotExist,
		Message:  "This bucket does not exist.",
	}
}

// ErrBucketDoesNotBelongToProject represents an error where a kanban bucket does not belong to a project
type ErrBucketDoesNotBelongToProject struct {
	BucketID  int64
	ProjectID int64
}

// IsErrBucketDoesNotBelongToProject checks if an error is ErrBucketDoesNotBelongToProject.
func IsErrBucketDoesNotBelongToProject(err error) bool {
	_, ok := err.(ErrBucketDoesNotBelongToProject)
	return ok
}

func (err ErrBucketDoesNotBelongToProject) Error() string {
	return fmt.Sprintf("Bucket does not not belong to project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
}

// ErrCodeBucketDoesNotBelongToProject holds the unique world-error code of this error
const ErrCodeBucketDoesNotBelongToProject = 10002

// HTTPError holds the http error description
func (err ErrBucketDoesNotBelongToProject) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeBucketDoesNotBelongToProject,
		Message:  "This bucket does not belong to that project.",
	}
}

// ErrCannotRemoveLastBucket represents an error where a kanban bucket is the last on a project and thus cannot be removed.
type ErrCannotRemoveLastBucket struct {
	BucketID  int64
	ProjectID int64
}

// IsErrCannotRemoveLastBucket checks if an error is ErrCannotRemoveLastBucket.
func IsErrCannotRemoveLastBucket(err error) bool {
	_, ok := err.(ErrCannotRemoveLastBucket)
	return ok
}

func (err ErrCannotRemoveLastBucket) Error() string {
	return fmt.Sprintf("Cannot remove last bucket of project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
}

// ErrCodeCannotRemoveLastBucket holds the unique world-error code of this error
const ErrCodeCannotRemoveLastBucket = 10003

// HTTPError holds the http error description
func (err ErrCannotRemoveLastBucket) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeCannotRemoveLastBucket,
		Message:  "You cannot remove the last bucket on this project.",
	}
}

// ErrBucketLimitExceeded represents an error where a task is being created or moved to a bucket which has its limit already exceeded.
type ErrBucketLimitExceeded struct {
	BucketID int64
	Limit    int64
	TaskID   int64 // may be 0
}

// IsErrBucketLimitExceeded checks if an error is ErrBucketLimitExceeded.
func IsErrBucketLimitExceeded(err error) bool {
	_, ok := err.(ErrBucketLimitExceeded)
	return ok
}

func (err ErrBucketLimitExceeded) Error() string {
	return fmt.Sprintf("Cannot add a task to this bucket because it would exceed the limit [BucketID: %d, Limit: %d, TaskID: %d]", err.BucketID, err.Limit, err.TaskID)
}

// ErrCodeBucketLimitExceeded holds the unique world-error code of this error
const ErrCodeBucketLimitExceeded = 10004

// HTTPError holds the http error description
func (err ErrBucketLimitExceeded) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeBucketLimitExceeded,
		Message:  "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
	}
}

// ErrOnlyOneDoneBucketPerProject represents an error where a bucket is set to the done bucket but one already exists for its project.
type ErrOnlyOneDoneBucketPerProject struct {
	BucketID     int64
	ProjectID    int64
	DoneBucketID int64
}

// IsErrOnlyOneDoneBucketPerProject checks if an error is ErrBucketLimitExceeded.
func IsErrOnlyOneDoneBucketPerProject(err error) bool {
	_, ok := err.(*ErrOnlyOneDoneBucketPerProject)
	return ok
}

func (err *ErrOnlyOneDoneBucketPerProject) Error() string {
	return fmt.Sprintf("There can be only one done bucket per project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
}

// ErrCodeOnlyOneDoneBucketPerProject holds the unique world-error code of this error
const ErrCodeOnlyOneDoneBucketPerProject = 10005

// HTTPError holds the http error description
func (err *ErrOnlyOneDoneBucketPerProject) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeOnlyOneDoneBucketPerProject,
		Message:  "There can be only one done bucket per project.",
	}
}

// =============
// Saved Filters
// =============

// ErrSavedFilterDoesNotExist represents an error where a kanban bucket does not exist
type ErrSavedFilterDoesNotExist struct {
	SavedFilterID int64
}

// IsErrSavedFilterDoesNotExist checks if an error is ErrSavedFilterDoesNotExist.
func IsErrSavedFilterDoesNotExist(err error) bool {
	_, ok := err.(ErrSavedFilterDoesNotExist)
	return ok
}

func (err ErrSavedFilterDoesNotExist) Error() string {
	return fmt.Sprintf("Saved filter does not exist [SavedFilterID: %d]", err.SavedFilterID)
}

// ErrCodeSavedFilterDoesNotExist holds the unique world-error code of this error
const ErrCodeSavedFilterDoesNotExist = 11001

// HTTPError holds the http error description
func (err ErrSavedFilterDoesNotExist) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusNotFound,
		Code:     ErrCodeSavedFilterDoesNotExist,
		Message:  "This saved filter does not exist.",
	}
}

// ErrSavedFilterNotAvailableForLinkShare represents an error where a kanban bucket does not exist
type ErrSavedFilterNotAvailableForLinkShare struct {
	SavedFilterID int64
	LinkShareID   int64
}

// IsErrSavedFilterNotAvailableForLinkShare checks if an error is ErrSavedFilterNotAvailableForLinkShare.
func IsErrSavedFilterNotAvailableForLinkShare(err error) bool {
	_, ok := err.(ErrSavedFilterNotAvailableForLinkShare)
	return ok
}

func (err ErrSavedFilterNotAvailableForLinkShare) Error() string {
	return fmt.Sprintf("Saved filters are not available for link shares [SavedFilterID: %d, LinkShareID: %d]", err.SavedFilterID, err.LinkShareID)
}

// ErrCodeSavedFilterNotAvailableForLinkShare holds the unique world-error code of this error
const ErrCodeSavedFilterNotAvailableForLinkShare = 11002

// HTTPError holds the http error description
func (err ErrSavedFilterNotAvailableForLinkShare) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeSavedFilterNotAvailableForLinkShare,
		Message:  "Saved filters are not available for link shares.",
	}
}

// =============
// Subscriptions
// =============

// ErrUnknownSubscriptionEntityType represents an error where a subscription entity type is unknown
type ErrUnknownSubscriptionEntityType struct {
	EntityType SubscriptionEntityType
}

// IsErrUnknownSubscriptionEntityType checks if an error is ErrUnknownSubscriptionEntityType.
func IsErrUnknownSubscriptionEntityType(err error) bool {
	_, ok := err.(*ErrUnknownSubscriptionEntityType)
	return ok
}

func (err *ErrUnknownSubscriptionEntityType) Error() string {
	return fmt.Sprintf("Subscription entity type is unkowns [EntityType: %d]", err.EntityType)
}

// ErrCodeUnknownSubscriptionEntityType holds the unique world-error code of this error
const ErrCodeUnknownSubscriptionEntityType = 12001

// HTTPError holds the http error description
func (err ErrUnknownSubscriptionEntityType) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeUnknownSubscriptionEntityType,
		Message:  "The subscription entity type is invalid.",
	}
}

// ErrSubscriptionAlreadyExists represents an error where a subscription entity already exists
type ErrSubscriptionAlreadyExists struct {
	EntityID   int64
	EntityType SubscriptionEntityType
	UserID     int64
}

// IsErrSubscriptionAlreadyExists checks if an error is ErrSubscriptionAlreadyExists.
func IsErrSubscriptionAlreadyExists(err error) bool {
	_, ok := err.(*ErrSubscriptionAlreadyExists)
	return ok
}

func (err *ErrSubscriptionAlreadyExists) Error() string {
	return fmt.Sprintf("Subscription for this (entity_id, entity_type, user_id) already exists [EntityType: %d, EntityID: %d, ID: %d]", err.EntityType, err.EntityID, err.UserID)
}

// ErrCodeSubscriptionAlreadyExists holds the unique world-error code of this error
const ErrCodeSubscriptionAlreadyExists = 12002

// HTTPError holds the http error description
func (err ErrSubscriptionAlreadyExists) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeSubscriptionAlreadyExists,
		Message:  "You're already subscribed.",
	}
}

// =================
// Link Share errors
// =================

// ErrLinkSharePasswordRequired represents an error where a link share authentication requires a password and none was provided
type ErrLinkSharePasswordRequired struct {
	ShareID int64
}

// IsErrLinkSharePasswordRequired checks if an error is ErrLinkSharePasswordRequired.
func IsErrLinkSharePasswordRequired(err error) bool {
	_, ok := err.(*ErrLinkSharePasswordRequired)
	return ok
}

func (err *ErrLinkSharePasswordRequired) Error() string {
	return fmt.Sprintf("Link Share requires a password for authentication [ShareID: %d]", err.ShareID)
}

// ErrCodeLinkSharePasswordRequired holds the unique world-error code of this error
const ErrCodeLinkSharePasswordRequired = 13001

// HTTPError holds the http error description
func (err ErrLinkSharePasswordRequired) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusPreconditionFailed,
		Code:     ErrCodeLinkSharePasswordRequired,
		Message:  "This link share requires a password for authentication, but none was provided.",
	}
}

// ErrLinkSharePasswordInvalid represents an error where a subscription entity type is unknown
type ErrLinkSharePasswordInvalid struct {
	ShareID int64
}

// IsErrLinkSharePasswordInvalid checks if an error is ErrLinkSharePasswordInvalid.
func IsErrLinkSharePasswordInvalid(err error) bool {
	_, ok := err.(*ErrLinkSharePasswordInvalid)
	return ok
}

func (err *ErrLinkSharePasswordInvalid) Error() string {
	return fmt.Sprintf("Provided Link Share password did not match the saved one [ShareID: %d]", err.ShareID)
}

// ErrCodeLinkSharePasswordInvalid holds the unique world-error code of this error
const ErrCodeLinkSharePasswordInvalid = 13002

// HTTPError holds the http error description
func (err ErrLinkSharePasswordInvalid) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusForbidden,
		Code:     ErrCodeLinkSharePasswordInvalid,
		Message:  "The provided link share password is invalid.",
	}
}

// ErrLinkShareTokenInvalid represents an error where a link share token is invalid
type ErrLinkShareTokenInvalid struct {
}

// IsErrLinkShareTokenInvalid checks if an error is ErrLinkShareTokenInvalid.
func IsErrLinkShareTokenInvalid(err error) bool {
	_, ok := err.(*ErrLinkShareTokenInvalid)
	return ok
}

func (err *ErrLinkShareTokenInvalid) Error() string {
	return "Provided Link Share Token is invalid"
}

// ErrCodeLinkShareTokenInvalid holds the unique world-error code of this error
const ErrCodeLinkShareTokenInvalid = 13003

// HTTPError holds the http error description
func (err ErrLinkShareTokenInvalid) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeLinkShareTokenInvalid,
		Message:  "The provided link share token is invalid.",
	}
}

// ================
// API Token Errors
// ================

// ErrAPITokenInvalid represents an error where an api token is invalid
type ErrAPITokenInvalid struct {
}

// IsErrAPITokenInvalid checks if an error is ErrAPITokenInvalid.
func IsErrAPITokenInvalid(err error) bool {
	_, ok := err.(*ErrAPITokenInvalid)
	return ok
}

func (err *ErrAPITokenInvalid) Error() string {
	return "Provided API token is invalid"
}

// ErrCodeAPITokenInvalid holds the unique world-error code of this error
const ErrCodeAPITokenInvalid = 14001

// HTTPError holds the http error description
func (err ErrAPITokenInvalid) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeAPITokenInvalid,
		Message:  "The provided api token is invalid.",
	}
}

// ErrInvalidAPITokenPermission represents an error where an api token is invalid
type ErrInvalidAPITokenPermission struct {
	Group      string
	Permission string
}

// IsErrInvalidAPITokenPermission checks if an error is ErrInvalidAPITokenPermission.
func IsErrInvalidAPITokenPermission(err error) bool {
	_, ok := err.(*ErrInvalidAPITokenPermission)
	return ok
}

func (err *ErrInvalidAPITokenPermission) Error() string {
	return fmt.Sprintf("API token permission %s of group %s is invalid", err.Permission, err.Group)
}

// ErrCodeInvalidAPITokenPermission holds the unique world-error code of this error
const ErrCodeInvalidAPITokenPermission = 14002

// HTTPError holds the http error description
func (err ErrInvalidAPITokenPermission) HTTPError() web.HTTPError {
	return web.HTTPError{
		HTTPCode: http.StatusBadRequest,
		Code:     ErrCodeInvalidAPITokenPermission,
		Message:  fmt.Sprintf("The permission %s of group %s is invalid.", err.Permission, err.Group),
	}
}
