// 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 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package caldav

import (
	"errors"
	"strconv"
	"strings"
	"time"

	"code.vikunja.io/api/pkg/config"
	"code.vikunja.io/api/pkg/log"
	"code.vikunja.io/api/pkg/models"
	"code.vikunja.io/api/pkg/utils"

	ics "github.com/arran4/golang-ical"
)

var cssColorsToHex map[string]string

func init() {
	cssColorsToHex = map[string]string{
		"aliceblue":            "f0f8ff",
		"antiquewhite":         "faebd7",
		"aqua":                 "00ffff",
		"aquamarine":           "7fffd4",
		"azure":                "f0ffff",
		"beige":                "f5f5dc",
		"bisque":               "ffe4c4",
		"black":                "000000",
		"blanchedalmond":       "ffebcd",
		"blue":                 "0000ff",
		"blueviolet":           "8a2be2",
		"brown":                "a52a2a",
		"burlywood":            "deb887",
		"cadetblue":            "5f9ea0",
		"chartreuse":           "7fff00",
		"chocolate":            "d2691e",
		"coral":                "ff7f50",
		"cornflowerblue":       "6495ed",
		"cornsilk":             "fff8dc",
		"crimson":              "dc143c",
		"cyan":                 "00ffff",
		"darkblue":             "00008b",
		"darkcyan":             "008b8b",
		"darkgoldenrod":        "b8860b",
		"darkgray":             "a9a9a9",
		"darkgreen":            "006400",
		"darkgrey":             "a9a9a9",
		"darkkhaki":            "bdb76b",
		"darkmagenta":          "8b008b",
		"darkolivegreen":       "556b2f",
		"darkorange":           "ff8c00",
		"darkorchid":           "9932cc",
		"darkred":              "8b0000",
		"darksalmon":           "e9967a",
		"darkseagreen":         "8fbc8f",
		"darkslateblue":        "483d8b",
		"darkslategray":        "2f4f4f",
		"darkslategrey":        "2f4f4f",
		"darkturquoise":        "00ced1",
		"darkviolet":           "9400d3",
		"deeppink":             "ff1493",
		"deepskyblue":          "00bfff",
		"dimgray":              "696969",
		"dimgrey":              "696969",
		"dodgerblue":           "1e90ff",
		"firebrick":            "b22222",
		"floralwhite":          "fffaf0",
		"forestgreen":          "228b22",
		"fuchsia":              "ff00ff",
		"gainsboro":            "dcdcdc",
		"ghostwhite":           "f8f8ff",
		"gold":                 "ffd700",
		"goldenrod":            "daa520",
		"gray":                 "808080",
		"green":                "008000",
		"greenyellow":          "adff2f",
		"grey":                 "808080",
		"honeydew":             "f0fff0",
		"hotpink":              "ff69b4",
		"indianred":            "cd5c5c",
		"indigo":               "4b0082",
		"ivory":                "fffff0",
		"khaki":                "f0e68c",
		"lavender":             "e6e6fa",
		"lavenderblush":        "fff0f5",
		"lawngreen":            "7cfc00",
		"lemonchiffon":         "fffacd",
		"lightblue":            "add8e6",
		"lightcoral":           "f08080",
		"lightcyan":            "e0ffff",
		"lightgoldenrodyellow": "fafad2",
		"lightgray":            "d3d3d3",
		"lightgreen":           "90ee90",
		"lightgrey":            "d3d3d3",
		"lightpink":            "ffb6c1",
		"lightsalmon":          "ffa07a",
		"lightseagreen":        "20b2aa",
		"lightskyblue":         "87cefa",
		"lightslategray":       "778899",
		"lightslategrey":       "778899",
		"lightsteelblue":       "b0c4de",
		"lightyellow":          "ffffe0",
		"lime":                 "00ff00",
		"limegreen":            "32cd32",
		"linen":                "faf0e6",
		"magenta":              "ff00ff",
		"maroon":               "800000",
		"mediumaquamarine":     "66cdaa",
		"mediumblue":           "0000cd",
		"mediumorchid":         "ba55d3",
		"mediumpurple":         "9370db",
		"mediumseagreen":       "3cb371",
		"mediumslateblue":      "7b68ee",
		"mediumspringgreen":    "00fa9a",
		"mediumturquoise":      "48d1cc",
		"mediumvioletred":      "c71585",
		"midnightblue":         "191970",
		"mintcream":            "f5fffa",
		"mistyrose":            "ffe4e1",
		"moccasin":             "ffe4b5",
		"navajowhite":          "ffdead",
		"navy":                 "000080",
		"oldlace":              "fdf5e6",
		"olive":                "808000",
		"olivedrab":            "6b8e23",
		"orange":               "ffa500",
		"orangered":            "ff4500",
		"orchid":               "da70d6",
		"palegoldenrod":        "eee8aa",
		"palegreen":            "98fb98",
		"paleturquoise":        "afeeee",
		"palevioletred":        "db7093",
		"papayawhip":           "ffefd5",
		"peachpuff":            "ffdab9",
		"peru":                 "cd853f",
		"pink":                 "ffc0cb",
		"plum":                 "dda0dd",
		"powderblue":           "b0e0e6",
		"purple":               "800080",
		"red":                  "ff0000",
		"rosybrown":            "bc8f8f",
		"royalblue":            "4169e1",
		"saddlebrown":          "8b4513",
		"salmon":               "fa8072",
		"sandybrown":           "f4a460",
		"seagreen":             "2e8b57",
		"seashell":             "fff5ee",
		"sienna":               "a0522d",
		"silver":               "c0c0c0",
		"skyblue":              "87ceeb",
		"slateblue":            "6a5acd",
		"slategray":            "708090",
		"slategrey":            "708090",
		"snow":                 "fffafa",
		"springgreen":          "00ff7f",
		"steelblue":            "4682b4",
		"tan":                  "d2b48c",
		"teal":                 "008080",
		"thistle":              "d8bfd8",
		"tomato":               "ff6347",
		"turquoise":            "40e0d0",
		"violet":               "ee82ee",
		"wheat":                "f5deb3",
		"white":                "ffffff",
		"whitesmoke":           "f5f5f5",
		"yellow":               "ffff00",
		"yellowgreen":          "9acd32",
	}
}

func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectTasks []*models.TaskWithComments) string {

	// Make caldav todos from Vikunja todos
	var caldavtodos []*Todo
	for _, t := range projectTasks {

		duration := t.EndDate.Sub(t.StartDate)
		var categories []string
		for _, label := range t.Labels {
			categories = append(categories, label.Title)
		}
		var alarms []Alarm
		for _, reminder := range t.Reminders {
			alarms = append(alarms, Alarm{
				Time:       reminder.Reminder,
				Duration:   time.Duration(reminder.RelativePeriod) * time.Second,
				RelativeTo: reminder.RelativeTo,
			})
		}

		var relations []Relation
		for reltype, tasks := range t.RelatedTasks {
			for _, r := range tasks {
				relations = append(relations, Relation{
					Type: reltype,
					UID:  r.UID,
				})
			}
		}

		caldavtodos = append(caldavtodos, &Todo{
			Timestamp:   t.Updated,
			UID:         t.UID,
			Summary:     t.Title,
			Description: t.Description,
			Completed:   t.DoneAt,
			// Organizer:     &t.CreatedBy, // Disabled until we figure out how this works
			Categories:  categories,
			Priority:    t.Priority,
			Start:       t.StartDate,
			End:         t.EndDate,
			Created:     t.Created,
			Updated:     t.Updated,
			DueDate:     t.DueDate,
			Duration:    duration,
			RepeatAfter: t.RepeatAfter,
			RepeatMode:  t.RepeatMode,
			Alarms:      alarms,
			Relations:   relations,
		})
	}

	caldavConfig := &Config{
		Name:   project.Title,
		ProdID: "Vikunja Todo App",
	}

	return ParseTodos(caldavConfig, caldavtodos)
}

func getHexColorFromCaldavColor(caldavColor string) string {
	if caldavColor == "" {
		return ""
	}

	if caldavColor[:1] == "#" {
		caldavColor = strings.TrimPrefix(caldavColor, "#")
		if len(caldavColor) > 6 {
			caldavColor = caldavColor[:6]
		}
		return caldavColor
	}

	hexColor, has := cssColorsToHex[caldavColor]
	if !has {
		return ""
	}

	return hexColor
}

//nolint:gocyclo
func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
	parsed, err := ics.ParseCalendar(strings.NewReader(content))
	if err != nil {
		return nil, err
	}
	if len(parsed.Components) == 0 {
		return nil, errors.New("VTODO element does seem not contain any components")
	}
	var vTodo *ics.VTodo
	for _, comp := range parsed.Components {
		if todo, ok := comp.(*ics.VTodo); ok {
			vTodo = todo
			break
		}
	}
	if vTodo == nil {
		return nil, errors.New("VTODO element not found")
	}
	// We put the vTodo details in a map to be able to handle them more easily
	task := make(map[string]ics.IANAProperty)

	var relations []ics.IANAProperty
	var color string
	for _, c := range vTodo.UnknownPropertiesIANAProperties() {
		task[c.IANAToken] = c
		if strings.HasPrefix(c.IANAToken, "RELATED-TO") {
			relations = append(relations, c)
		}
		if c.IANAToken == "X-APPLE-CALENDAR-COLOR" {
			color = c.Value
		}
		if c.IANAToken == "X-OUTLOOK-COLOR" {
			color = c.Value
		}
		if c.IANAToken == "X-FUNAMBOL-COLOR" {
			color = c.Value
		}
		if c.IANAToken == "COLOR" {
			color = c.Value
		}
	}

	// Get UID and SUMMARY (log warning if missing, but don't fail for backwards compatibility)
	uid, hasUID := task["UID"]
	if !hasUID {
		log.Warningf("[CALDAV] VTODO missing UID field")
	}

	summary, hasSummary := task["SUMMARY"]
	if !hasSummary {
		log.Warningf("[CALDAV] VTODO missing SUMMARY field")
	}

	// Parse the priority
	var priority int64
	if _, ok := task["PRIORITY"]; ok {
		priorityParsed, err := strconv.ParseInt(task["PRIORITY"].Value, 10, 64)
		if err != nil {
			log.Errorf("[CALDAV] Failed to parse PRIORITY: %v", err)
			return nil, err
		}

		priority = parseVTODOPriority(priorityParsed)
	}

	// Parse the enddate
	var duration time.Duration
	if durationProp, ok := task["DURATION"]; ok {
		duration, _ = time.ParseDuration(durationProp.Value)
	}

	description := ""
	if descProp, ok := task["DESCRIPTION"]; ok {
		description = strings.ReplaceAll(descProp.Value, "\\,", ",")
		description = strings.ReplaceAll(description, "\\n", "\n")
	}

	var labels []*models.Label
	if val, ok := task["CATEGORIES"]; ok {
		categories := strings.Split(val.Value, ",")
		labels = make([]*models.Label, 0, len(categories))
		for _, category := range categories {
			labels = append(labels, &models.Label{
				Title: category,
			})
		}
	}

	// Safely extract values
	var uidValue, titleValue string
	if hasUID {
		uidValue = uid.Value
	}
	if hasSummary {
		titleValue = summary.Value
	}

	vTask = &models.Task{
		UID:         uidValue,
		Title:       titleValue,
		Description: description,
		Priority:    priority,
		Labels:      labels,
		DueDate:     caldavTimeToTimestamp(task["DUE"]),
		Updated:     caldavTimeToTimestamp(task["DTSTAMP"]),
		StartDate:   caldavTimeToTimestamp(task["DTSTART"]),
		DoneAt:      caldavTimeToTimestamp(task["COMPLETED"]),
		HexColor:    getHexColorFromCaldavColor(color),
	}

	for _, c := range relations {
		var relTypeStr string
		if _, ok := c.ICalParameters["RELTYPE"]; ok {
			if len(c.ICalParameters["RELTYPE"]) != 1 {
				continue
			}

			relTypeStr = c.ICalParameters["RELTYPE"][0]
		}

		var relationKind models.RelationKind
		switch relTypeStr {
		case "PARENT":
			relationKind = models.RelationKindParenttask
		case "CHILD":
			relationKind = models.RelationKindSubtask
		default:
			relationKind = models.RelationKindParenttask
		}

		if vTask.RelatedTasks == nil {
			vTask.RelatedTasks = make(map[models.RelationKind][]*models.Task)
		}

		vTask.RelatedTasks[relationKind] = append(vTask.RelatedTasks[relationKind], &models.Task{
			UID: c.Value,
		})
	}

	if status, ok := task["STATUS"]; ok && status.Value == "COMPLETED" {
		vTask.Done = true
	}

	if duration > 0 && !vTask.StartDate.IsZero() {
		vTask.EndDate = vTask.StartDate.Add(duration)
	}

	for _, vAlarm := range vTodo.SubComponents() {
		if vAlarm, ok := vAlarm.(*ics.VAlarm); ok {
			vTask = parseVAlarm(vAlarm, vTask)
		}
	}

	return
}

func parseVAlarm(vAlarm *ics.VAlarm, vTask *models.Task) *models.Task {
	for _, property := range vAlarm.UnknownPropertiesIANAProperties() {
		if property.IANAToken != "TRIGGER" {
			continue
		}

		if contains(property.ICalParameters["VALUE"], "DATE-TIME") {
			// Example: TRIGGER;VALUE=DATE-TIME:20181201T011210Z
			vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
				Reminder: caldavTimeToTimestamp(property),
			})
			continue
		}

		duration := utils.ParseISO8601Duration(property.Value)

		if contains(property.ICalParameters["RELATED"], "END") {
			// Example: TRIGGER;RELATED=END:-P2D
			if vTask.EndDate.IsZero() {
				vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
					RelativePeriod: int64(duration.Seconds()),
					RelativeTo:     models.ReminderRelationDueDate})
			} else {
				vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
					RelativePeriod: int64(duration.Seconds()),
					RelativeTo:     models.ReminderRelationEndDate})
			}
			continue
		}

		// Example: TRIGGER;RELATED=START:-P2D
		// Example: TRIGGER:-PT60M
		vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
			RelativePeriod: int64(duration.Seconds()),
			RelativeTo:     models.ReminderRelationStartDate})
	}
	return vTask
}

func contains(array []string, str string) bool {
	for _, value := range array {
		if value == str {
			return true
		}
	}
	return false
}

// https://tools.ietf.org/html/rfc5545#section-3.3.5
func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time {
	tstring := ianaProperty.Value
	if tstring == "" {
		return time.Time{}
	}

	format := DateFormat

	if strings.HasSuffix(tstring, "Z") {
		format = `20060102T150405Z`
	}

	if len(tstring) == 8 {
		format = `20060102`
	}

	var t time.Time
	var err error
	tzParameter := ianaProperty.ICalParameters["TZID"]
	if len(tzParameter) > 0 {
		loc, locErr := time.LoadLocation(tzParameter[0])
		if locErr != nil {
			log.Warningf("Error while parsing caldav timezone %s: %s", tzParameter[0], locErr)
		} else {
			t, err = time.ParseInLocation(format, tstring, loc)
		}
	} else {
		t, err = time.ParseInLocation(format, tstring, config.GetTimeZone())
	}

	if err != nil {
		log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
		return time.Time{}
	}

	return t.In(config.GetTimeZone())
}
