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

package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"git.sr.ht/~amolith/lunatask-mcp-server/lunatask"
	"github.com/mark3labs/mcp-go/mcp"
)

// CreateTaskArgs defines the arguments for create_task tool call.
type CreateTaskArgs struct {
	AreaID      string  `json:"area_id"`
	GoalID      string  `json:"goal_id,omitempty"`
	Name        string  `json:"name"`
	Note        string  `json:"note,omitempty"`
	Estimate    float64 `json:"estimate,omitempty"`
	Priority    string  `json:"priority,omitempty"`
	Eisenhower  string  `json:"eisenhower,omitempty"`
	Motivation  string  `json:"motivation,omitempty"`
	Status      string  `json:"status,omitempty"`
	ScheduledOn string  `json:"scheduled_on,omitempty"`
}

// UpdateTaskArgs defines the arguments for update_task tool call.
type UpdateTaskArgs struct {
	TaskID      string  `json:"task_id"`
	AreaID      string  `json:"area_id,omitempty"`
	GoalID      string  `json:"goal_id,omitempty"`
	Name        string  `json:"name"`
	Note        string  `json:"note,omitempty"`
	Estimate    float64 `json:"estimate,omitempty"`
	Priority    string  `json:"priority,omitempty"`
	Eisenhower  string  `json:"eisenhower,omitempty"`
	Motivation  string  `json:"motivation,omitempty"`
	Status      string  `json:"status,omitempty"`
	ScheduledOn string  `json:"scheduled_on,omitempty"`
}

// DeleteTaskArgs defines the arguments for delete_task tool call.
type DeleteTaskArgs struct {
	TaskID string `json:"task_id"`
}

// HandleCreateTask handles the create_task tool call.
func (h *Handlers) HandleCreateTask(ctx context.Context, request mcp.CallToolRequest, args CreateTaskArgs) (*mcp.CallToolResult, error) {
	if _, err := LoadLocation(h.config.Timezone); err != nil {
		return reportMCPError(err.Error())
	}

	if args.AreaID == "" {
		return reportMCPError("Missing or invalid required argument: area_id")
	}

	var areaFoundProvider AreaProvider
	for _, ap := range h.config.Areas {
		if ap.GetID() == args.AreaID {
			areaFoundProvider = ap
			break
		}
	}
	if areaFoundProvider == nil {
		return reportMCPError("Area not found for given area_id")
	}

	if args.GoalID != "" {
		found := false
		for _, goal := range areaFoundProvider.GetGoals() {
			if goal.GetID() == args.GoalID {
				found = true
				break
			}
		}
		if !found {
			return reportMCPError("Goal not found in specified area for given goal_id")
		}
	}

	// Create a map to store the final arguments for JSON marshaling
	finalArgs := make(map[string]any)
	finalArgs["area_id"] = args.AreaID
	if args.GoalID != "" {
		finalArgs["goal_id"] = args.GoalID
	}
	finalArgs["name"] = args.Name
	if args.Note != "" {
		finalArgs["note"] = args.Note
	}
	if args.Estimate > 0 {
		finalArgs["estimate"] = int(args.Estimate)
	}

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

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

	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,
	}

	if args.Eisenhower != "" {
		translatedEisenhower, isValid := eisenhowerMap[strings.ToLower(args.Eisenhower)]
		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'.", args.Eisenhower))
		}
		finalArgs["eisenhower"] = translatedEisenhower
	}

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

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

	if args.ScheduledOn != "" {
		if _, err := time.Parse(time.RFC3339, args.ScheduledOn); err != nil {
			return reportMCPError(fmt.Sprintf("Invalid format for scheduled_on: '%s'. Must be RFC3339 timestamp (e.g., YYYY-MM-DDTHH:MM:SSZ). Use get_timestamp tool first.", args.ScheduledOn))
		}
		finalArgs["scheduled_on"] = args.ScheduledOn
	}

	client := lunatask.NewClient(h.config.AccessToken)
	var task lunatask.CreateTaskRequest
	argBytes, err := json.Marshal(finalArgs)
	if err != nil {
		return reportMCPError(fmt.Sprintf("Failed to process arguments: %v", err))
	}
	if err := json.Unmarshal(argBytes, &task); err != nil {
		return reportMCPError(fmt.Sprintf("Failed to parse arguments: %v", err))
	}

	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: fmt.Sprintf("Task created successfully with ID: %s", response.Task.ID),
			},
		},
	}, nil
}

// HandleUpdateTask handles the update_task tool call.
func (h *Handlers) HandleUpdateTask(ctx context.Context, request mcp.CallToolRequest, args UpdateTaskArgs) (*mcp.CallToolResult, error) {
	if args.TaskID == "" {
		return reportMCPError("Missing or invalid required argument: task_id")
	}

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

	updatePayload := lunatask.CreateTaskRequest{}

	var specifiedAreaProvider AreaProvider
	areaIDProvided := false

	if args.AreaID != "" {
		updatePayload.AreaID = args.AreaID
		areaIDProvided = true
		found := false
		for _, ap := range h.config.Areas {
			if ap.GetID() == args.AreaID {
				specifiedAreaProvider = ap
				found = true
				break
			}
		}
		if !found {
			return reportMCPError(fmt.Sprintf("Area not found for given area_id: %s", args.AreaID))
		}
	}

	if args.GoalID != "" {
		updatePayload.GoalID = args.GoalID
		if specifiedAreaProvider != nil {
			foundGoal := false
			for _, goal := range specifiedAreaProvider.GetGoals() {
				if goal.GetID() == args.GoalID {
					foundGoal = true
					break
				}
			}
			if !foundGoal {
				return reportMCPError(fmt.Sprintf("Goal not found in specified area '%s' for given goal_id: %s", specifiedAreaProvider.GetName(), args.GoalID))
			}
		} else if areaIDProvided {
			return reportMCPError("Internal error: area_id provided but area details not loaded for goal validation.")
		}
		// If area_id is not provided, we're not moving the task to a different area
		// In this case, the goal validation should be skipped as we don't know the current area
	}

	updatePayload.Name = args.Name
	updatePayload.Note = args.Note

	if args.Estimate > 0 {
		updatePayload.Estimate = int(args.Estimate)
	}

	if args.Priority != "" {
		priorityMap := map[string]int{
			"lowest":  -2,
			"low":     -1,
			"neutral": 0,
			"high":    1,
			"highest": 2,
		}
		translatedPriority, isValid := priorityMap[strings.ToLower(args.Priority)]
		if !isValid {
			return reportMCPError(fmt.Sprintf("Invalid 'priority' value: '%s'. Must be one of 'lowest', 'low', 'neutral', 'high', 'highest'.", args.Priority))
		}
		updatePayload.Priority = translatedPriority
	}

	if args.Eisenhower != "" {
		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(args.Eisenhower)]
		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'.", args.Eisenhower))
		}
		updatePayload.Eisenhower = translatedEisenhower
	}

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

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

	if args.ScheduledOn != "" {
		if _, err := time.Parse(time.RFC3339, args.ScheduledOn); err != nil {
			return reportMCPError(fmt.Sprintf("Invalid format for scheduled_on: '%s'. Must be RFC3339. Use get_timestamp tool.", args.ScheduledOn))
		}
	}
	updatePayload.ScheduledOn = args.ScheduledOn

	client := lunatask.NewClient(h.config.AccessToken)
	response, err := client.UpdateTask(ctx, args.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: fmt.Sprintf("Task updated successfully. ID: %s", response.Task.ID),
			},
		},
	}, nil
}

// HandleDeleteTask handles the delete_task tool call.
func (h *Handlers) HandleDeleteTask(ctx context.Context, request mcp.CallToolRequest, args DeleteTaskArgs) (*mcp.CallToolResult, error) {
	if args.TaskID == "" {
		return reportMCPError("Missing or invalid required argument: task_id")
	}

	client := lunatask.NewClient(h.config.AccessToken)
	_, err := client.DeleteTask(ctx, args.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
}
