// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package tools

import (
	"context"
	"fmt"
	"strings"

	"git.secluded.site/go-lunatask"
	"github.com/mark3labs/mcp-go/mcp"
)

// HandleCreateTask handles the create_task tool call.
func (h *Handlers) HandleCreateTask(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	arguments := request.Params.Arguments

	if _, err := LoadLocation(h.config.Timezone); err != nil {
		return reportMCPError(err.Error())
	}

	areaID, ok := arguments["area_id"].(string)
	if !ok || areaID == "" {
		return reportMCPError("Missing or invalid required argument: area_id")
	}

	var areaFoundProvider AreaProvider

	for _, ap := range h.config.Areas {
		if ap.GetID() == areaID {
			areaFoundProvider = ap

			break
		}
	}

	if areaFoundProvider == nil {
		return reportMCPError("Area not found for given area_id")
	}

	var goalID *string
	if goalIDStr, exists := arguments["goal_id"].(string); exists && goalIDStr != "" {
		found := false

		for _, goal := range areaFoundProvider.GetGoals() {
			if goal.GetID() == goalIDStr {
				found = true

				break
			}
		}

		if !found {
			return reportMCPError("Goal not found in specified area for given goal_id")
		}

		goalID = &goalIDStr
	}

	name, ok := arguments["name"].(string)
	if !ok || name == "" {
		return reportMCPError("Missing or invalid required argument: name")
	}

	if len(name) > 100 {
		return reportMCPError("'name' must be 100 characters or fewer")
	}

	task := lunatask.CreateTaskRequest{
		Name:   name,
		AreaID: &areaID,
		GoalID: goalID,
	}

	if noteVal, exists := arguments["note"].(string); exists {
		task.Note = &noteVal
	}

	if priorityArg, exists := arguments["priority"]; exists && priorityArg != nil {
		priorityStr, ok := priorityArg.(string)
		if !ok {
			return reportMCPError("Invalid type for 'priority' argument: expected string.")
		}

		priorityMap := map[string]int{
			"lowest":  -2,
			"low":     -1,
			"neutral": 0,
			"high":    1,
			"highest": 2,
		}

		translatedPriority, isValid := priorityMap[strings.ToLower(priorityStr)]
		if !isValid {
			return reportMCPError(fmt.Sprintf("Invalid 'priority' value: '%s'. Must be one of 'lowest', 'low', 'neutral', 'high', 'highest'.", priorityStr))
		}

		task.Priority = &translatedPriority
	}

	if eisenhowerArg, exists := arguments["eisenhower"]; exists && eisenhowerArg != nil {
		eisenhowerStr, ok := eisenhowerArg.(string)
		if !ok {
			return reportMCPError("Invalid type for 'eisenhower' argument: expected string.")
		}

		eisenhowerMap := map[string]int{
			"uncategorised":                0,
			"both urgent and important":    1,
			"urgent, but not important":    2,
			"important, but not urgent":    3,
			"neither urgent nor important": 4,
		}

		translatedEisenhower, isValid := eisenhowerMap[strings.ToLower(eisenhowerStr)]
		if !isValid {
			return reportMCPError(fmt.Sprintf("Invalid 'eisenhower' value: '%s'. Must be one of 'uncategorised', 'both urgent and important', 'urgent, but not important', 'important, but not urgent', 'neither urgent nor important'.", eisenhowerStr))
		}

		task.Eisenhower = &translatedEisenhower
	}

	if motivationVal, exists := arguments["motivation"]; exists && motivationVal != nil {
		motivation, ok := motivationVal.(string)
		if !ok {
			return reportMCPError("'motivation' must be a string")
		}

		if motivation != "" {
			validMotivations := map[string]bool{"must": true, "should": true, "want": true}
			if !validMotivations[motivation] {
				return reportMCPError("'motivation' must be one of 'must', 'should', or 'want'")
			}

			task.Motivation = &motivation
		}
	}

	if statusVal, exists := arguments["status"]; exists && statusVal != nil {
		status, ok := statusVal.(string)
		if !ok {
			return reportMCPError("'status' must be a string")
		}

		if status != "" {
			validStatus := map[string]bool{"later": true, "next": true, "started": true, "waiting": true, "completed": true}
			if !validStatus[status] {
				return reportMCPError("'status' must be one of 'later', 'next', 'started', 'waiting', or 'completed'")
			}

			task.Status = &status
		}
	}

	if estimateArg, exists := arguments["estimate"]; exists && estimateArg != nil {
		estimateVal, ok := estimateArg.(float64)
		if !ok {
			return reportMCPError("Invalid type for 'estimate' argument: expected number.")
		}

		estimate := int(estimateVal)
		if estimate < 0 || estimate > 720 {
			return reportMCPError("'estimate' must be between 0 and 720 minutes")
		}

		task.Estimate = &estimate
	}

	if scheduledOnArg, exists := arguments["scheduled_on"]; exists {
		scheduledOnStr, ok := scheduledOnArg.(string)
		if !ok {
			return reportMCPError("Invalid type for scheduled_on argument: expected string.")
		}

		if scheduledOnStr != "" {
			date, err := lunatask.ParseDate(scheduledOnStr)
			if err != nil {
				return reportMCPError(fmt.Sprintf("Invalid format for scheduled_on: '%s'. Must be YYYY-MM-DD.", scheduledOnStr))
			}

			task.ScheduledOn = &date
		}
	}

	if sourceArg, exists := arguments["source"].(string); exists && sourceArg != "" {
		task.Source = &sourceArg
	}

	if sourceIDArg, exists := arguments["source_id"].(string); exists && sourceIDArg != "" {
		task.SourceID = &sourceIDArg
	}

	client := lunatask.NewClient(h.config.AccessToken)

	response, err := client.CreateTask(ctx, &task)
	if err != nil {
		return reportMCPError(fmt.Sprintf("%v", err))
	}

	if response == nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Task already exists (not an error).",
				},
			},
		}, nil
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: "Task created successfully with ID: " + response.ID,
			},
		},
	}, nil
}

// HandleUpdateTask handles the update_task tool call.
func (h *Handlers) HandleUpdateTask(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	arguments := request.Params.Arguments

	taskID, ok := arguments["task_id"].(string)
	if !ok || taskID == "" {
		return reportMCPError("Missing or invalid required argument: task_id")
	}

	if _, err := LoadLocation(h.config.Timezone); err != nil {
		return reportMCPError(err.Error())
	}

	updatePayload := lunatask.UpdateTaskRequest{}

	var specifiedAreaProvider AreaProvider

	areaIDProvided := false

	if areaIDArg, exists := arguments["area_id"]; exists {
		areaIDStr, ok := areaIDArg.(string)
		if !ok && areaIDArg != nil {
			return reportMCPError("Invalid type for area_id argument: expected string.")
		}

		if ok && areaIDStr != "" {
			updatePayload.AreaID = &areaIDStr
			areaIDProvided = true
			found := false

			for _, ap := range h.config.Areas {
				if ap.GetID() == areaIDStr {
					specifiedAreaProvider = ap
					found = true

					break
				}
			}

			if !found {
				return reportMCPError("Area not found for given area_id: " + areaIDStr)
			}
		}
	}

	if goalIDArg, exists := arguments["goal_id"]; exists {
		goalIDStr, ok := goalIDArg.(string)
		if !ok && goalIDArg != nil {
			return reportMCPError("Invalid type for goal_id argument: expected string.")
		}

		if ok && goalIDStr != "" {
			updatePayload.GoalID = &goalIDStr

			if specifiedAreaProvider != nil {
				foundGoal := false

				for _, goal := range specifiedAreaProvider.GetGoals() {
					if goal.GetID() == goalIDStr {
						foundGoal = true

						break
					}
				}

				if !foundGoal {
					return reportMCPError(fmt.Sprintf("Goal not found in specified area '%s' for given goal_id: %s", specifiedAreaProvider.GetName(), goalIDStr))
				}
			} else if areaIDProvided {
				return reportMCPError("Internal error: area_id provided but area details not loaded for goal validation.")
			}
		}
	}

	nameArg := arguments["name"]
	if nameStr, ok := nameArg.(string); ok {
		if len(nameStr) > 100 {
			return reportMCPError("'name' must be 100 characters or fewer")
		}

		updatePayload.Name = &nameStr
	} else {
		return reportMCPError("Invalid type for name argument: expected string.")
	}

	if noteArg, exists := arguments["note"]; exists {
		noteStr, ok := noteArg.(string)
		if !ok && noteArg != nil {
			return reportMCPError("Invalid type for note argument: expected string.")
		}

		if ok {
			updatePayload.Note = &noteStr
		}
	}

	if estimateArg, exists := arguments["estimate"]; exists && estimateArg != nil {
		estimateVal, ok := estimateArg.(float64)
		if !ok {
			return reportMCPError("Invalid type for estimate argument: expected number.")
		}

		estimate := int(estimateVal)
		if estimate < 0 || estimate > 720 {
			return reportMCPError("'estimate' must be between 0 and 720 minutes")
		}

		updatePayload.Estimate = &estimate
	}

	if priorityArg, exists := arguments["priority"]; exists && priorityArg != nil {
		priorityStr, ok := priorityArg.(string)
		if !ok {
			return reportMCPError("Invalid type for 'priority' argument: expected string.")
		}

		priorityMap := map[string]int{
			"lowest":  -2,
			"low":     -1,
			"neutral": 0,
			"high":    1,
			"highest": 2,
		}

		translatedPriority, isValid := priorityMap[strings.ToLower(priorityStr)]
		if !isValid {
			return reportMCPError(fmt.Sprintf("Invalid 'priority' value: '%s'. Must be one of 'lowest', 'low', 'neutral', 'high', 'highest'.", priorityStr))
		}

		updatePayload.Priority = &translatedPriority
	}

	if eisenhowerArg, exists := arguments["eisenhower"]; exists && eisenhowerArg != nil {
		eisenhowerStr, ok := eisenhowerArg.(string)
		if !ok {
			return reportMCPError("Invalid type for 'eisenhower' argument: expected string.")
		}

		eisenhowerMap := map[string]int{
			"uncategorised":                0,
			"both urgent and important":    1,
			"urgent, but not important":    2,
			"important, but not urgent":    3,
			"neither urgent nor important": 4,
		}

		translatedEisenhower, isValid := eisenhowerMap[strings.ToLower(eisenhowerStr)]
		if !isValid {
			return reportMCPError(fmt.Sprintf("Invalid 'eisenhower' value: '%s'. Must be one of 'uncategorised', 'both urgent and important', 'urgent, but not important', 'important, but not urgent', 'neither urgent nor important'.", eisenhowerStr))
		}

		updatePayload.Eisenhower = &translatedEisenhower
	}

	if motivationArg, exists := arguments["motivation"]; exists {
		motivationStr, ok := motivationArg.(string)
		if !ok && motivationArg != nil {
			return reportMCPError("Invalid type for motivation argument: expected string.")
		}

		if ok {
			if motivationStr != "" {
				validMotivations := map[string]bool{"must": true, "should": true, "want": true}
				if !validMotivations[motivationStr] {
					return reportMCPError("'motivation' must be one of 'must', 'should', or 'want', or empty to clear.")
				}
			}

			updatePayload.Motivation = &motivationStr
		}
	}

	if statusArg, exists := arguments["status"]; exists {
		statusStr, ok := statusArg.(string)
		if !ok && statusArg != nil {
			return reportMCPError("Invalid type for status argument: expected string.")
		}

		if ok {
			if statusStr != "" {
				validStatus := map[string]bool{"later": true, "next": true, "started": true, "waiting": true, "completed": true}
				if !validStatus[statusStr] {
					return reportMCPError("'status' must be one of 'later', 'next', 'started', 'waiting', 'completed', or empty.")
				}
			}

			updatePayload.Status = &statusStr
		}
	}

	if scheduledOnArg, exists := arguments["scheduled_on"]; exists {
		scheduledOnStr, ok := scheduledOnArg.(string)
		if !ok && scheduledOnArg != nil {
			return reportMCPError("Invalid type for scheduled_on argument: expected string.")
		}

		if ok && scheduledOnStr != "" {
			date, err := lunatask.ParseDate(scheduledOnStr)
			if err != nil {
				return reportMCPError(fmt.Sprintf("Invalid format for scheduled_on: '%s'. Must be YYYY-MM-DD.", scheduledOnStr))
			}

			updatePayload.ScheduledOn = &date
		}
	}

	client := lunatask.NewClient(h.config.AccessToken)

	response, err := client.UpdateTask(ctx, taskID, &updatePayload)
	if err != nil {
		return reportMCPError(fmt.Sprintf("Failed to update task: %v", err))
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: "Task updated successfully. ID: " + response.ID,
			},
		},
	}, nil
}

// HandleDeleteTask handles the delete_task tool call.
func (h *Handlers) HandleDeleteTask(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	taskID, ok := request.Params.Arguments["task_id"].(string)
	if !ok || taskID == "" {
		return reportMCPError("Missing or invalid required argument: task_id")
	}

	client := lunatask.NewClient(h.config.AccessToken)

	_, err := client.DeleteTask(ctx, taskID)
	if err != nil {
		return reportMCPError(fmt.Sprintf("Failed to delete task: %v", err))
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: "Task deleted successfully.",
			},
		},
	}, nil
}
