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

package crud

import (
	"context"

	"git.secluded.site/go-lunatask"
	"git.secluded.site/lune/internal/dateutil"
	"git.secluded.site/lune/internal/mcp/shared"
	"git.secluded.site/lune/internal/validate"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

// UpdateToolName is the name of the consolidated update tool.
const UpdateToolName = "update"

// UpdateToolDescription describes the update tool for LLMs.
const UpdateToolDescription = `Updates an existing entity in Lunatask.

Required:
- entity: Type to update (task, note, person)
- id: Entity UUID or lunatask:// deep link

Entity-specific fields (only provided fields are modified):

**task**:
- name: New task title
- area_id: Move to area (UUID, deep link, or config key)
- goal_id: Move to goal (UUID, deep link, or config key; requires area_id)
- status: later, next, started, waiting, completed
- note: New markdown note (replaces existing)
- priority: lowest, low, normal, high, highest
- estimate: Time estimate in minutes (0-720)
- motivation: must, should, want
- important: true/false for Eisenhower matrix
- urgent: true/false for Eisenhower matrix
- scheduled_on: Date to schedule (YYYY-MM-DD or natural language)

**note**:
- name: New note title
- notebook_id: Move to notebook (UUID)
- content: Replace content (Markdown)
- date: Note date (YYYY-MM-DD or natural language)

**person**:
- first_name: New first name
- last_name: New last name
- relationship: New relationship strength (family, intimate-friends, close-friends,
  casual-friends, acquaintances, business-contacts, almost-strangers)

Returns the updated entity's deep link.`

// UpdateInput is the input schema for the consolidated update tool.
type UpdateInput struct {
	Entity string `json:"entity" jsonschema:"required"`
	ID     string `json:"id"     jsonschema:"required"`

	// Common fields
	Name    *string `json:"name,omitempty"`
	Content *string `json:"content,omitempty"`

	// Task-specific fields
	AreaID      *string `json:"area_id,omitempty"`
	GoalID      *string `json:"goal_id,omitempty"`
	Status      *string `json:"status,omitempty"`
	Note        *string `json:"note,omitempty"`
	Priority    *string `json:"priority,omitempty"`
	Estimate    *int    `json:"estimate,omitempty"`
	Motivation  *string `json:"motivation,omitempty"`
	Important   *bool   `json:"important,omitempty"`
	Urgent      *bool   `json:"urgent,omitempty"`
	ScheduledOn *string `json:"scheduled_on,omitempty"`

	// Note-specific fields
	NotebookID *string `json:"notebook_id,omitempty"`
	Date       *string `json:"date,omitempty"`

	// Person-specific fields
	FirstName    *string `json:"first_name,omitempty"`
	LastName     *string `json:"last_name,omitempty"`
	Relationship *string `json:"relationship,omitempty"`
}

// UpdateOutput is the output schema for the consolidated update tool.
type UpdateOutput struct {
	Entity   string `json:"entity"`
	DeepLink string `json:"deep_link"`
}

// HandleUpdate updates an existing entity based on the entity type.
func (h *Handler) HandleUpdate(
	ctx context.Context,
	_ *mcp.CallToolRequest,
	input UpdateInput,
) (*mcp.CallToolResult, UpdateOutput, error) {
	switch input.Entity {
	case EntityTask:
		return h.updateTask(ctx, input)
	case EntityNote:
		return h.updateNote(ctx, input)
	case EntityPerson:
		return h.updatePerson(ctx, input)
	default:
		return shared.ErrorResult("invalid entity: must be task, note, or person"),
			UpdateOutput{Entity: input.Entity}, nil
	}
}

// parsedTaskUpdateInput holds validated and parsed task update input fields.
type parsedTaskUpdateInput struct {
	ID          string
	Name        *string
	AreaID      *string
	GoalID      *string
	Status      *lunatask.TaskStatus
	Note        *string
	Priority    *lunatask.Priority
	Estimate    *int
	Motivation  *lunatask.Motivation
	Important   *bool
	Urgent      *bool
	ScheduledOn *lunatask.Date
}

func (h *Handler) updateTask(
	ctx context.Context,
	input UpdateInput,
) (*mcp.CallToolResult, UpdateOutput, error) {
	parsed, errResult := h.parseTaskUpdateInput(input)
	if errResult != nil {
		return errResult, UpdateOutput{Entity: input.Entity}, nil
	}

	builder := h.client.NewTaskUpdate(parsed.ID)
	applyToTaskUpdateBuilder(builder, parsed)

	task, err := builder.Update(ctx)
	if err != nil {
		return shared.ErrorResult(err.Error()), UpdateOutput{Entity: input.Entity}, nil
	}

	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Task updated: " + deepLink,
		}},
	}, UpdateOutput{Entity: input.Entity, DeepLink: deepLink}, nil
}

//nolint:cyclop,funlen
func (h *Handler) parseTaskUpdateInput(input UpdateInput) (*parsedTaskUpdateInput, *mcp.CallToolResult) {
	_, id, err := lunatask.ParseReference(input.ID)
	if err != nil {
		return nil, shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link")
	}

	parsed := &parsedTaskUpdateInput{
		ID:        id,
		Name:      input.Name,
		Note:      input.Note,
		Estimate:  input.Estimate,
		Important: input.Important,
		Urgent:    input.Urgent,
	}

	if input.AreaID != nil {
		areaID, err := validate.AreaRef(h.cfg, *input.AreaID)
		if err != nil {
			return nil, shared.ErrorResult(err.Error())
		}

		parsed.AreaID = &areaID
	}

	if input.GoalID != nil {
		areaID := ""
		if parsed.AreaID != nil {
			areaID = *parsed.AreaID
		}

		goalID, err := validate.GoalRef(h.cfg, areaID, *input.GoalID)
		if err != nil {
			return nil, shared.ErrorResult(err.Error())
		}

		parsed.GoalID = &goalID
	}

	if input.Estimate != nil {
		if err := shared.ValidateEstimate(*input.Estimate); err != nil {
			return nil, shared.ErrorResult(err.Error())
		}
	}

	if input.Status != nil {
		status, err := lunatask.ParseTaskStatus(*input.Status)
		if err != nil {
			return nil, shared.ErrorResult(err.Error())
		}

		parsed.Status = &status
	}

	if input.Priority != nil {
		priority, err := lunatask.ParsePriority(*input.Priority)
		if err != nil {
			return nil, shared.ErrorResult(err.Error())
		}

		parsed.Priority = &priority
	}

	if input.Motivation != nil {
		motivation, err := lunatask.ParseMotivation(*input.Motivation)
		if err != nil {
			return nil, shared.ErrorResult(err.Error())
		}

		parsed.Motivation = &motivation
	}

	if input.ScheduledOn != nil {
		date, err := dateutil.Parse(*input.ScheduledOn)
		if err != nil {
			return nil, shared.ErrorResult(err.Error())
		}

		parsed.ScheduledOn = &date
	}

	return parsed, nil
}

//nolint:cyclop
func applyToTaskUpdateBuilder(builder *lunatask.TaskUpdateBuilder, parsed *parsedTaskUpdateInput) {
	if parsed.Name != nil {
		builder.Name(*parsed.Name)
	}

	if parsed.AreaID != nil {
		builder.InArea(*parsed.AreaID)
	}

	if parsed.GoalID != nil {
		builder.InGoal(*parsed.GoalID)
	}

	if parsed.Status != nil {
		builder.WithStatus(*parsed.Status)
	}

	if parsed.Note != nil {
		builder.WithNote(*parsed.Note)
	}

	if parsed.Priority != nil {
		builder.Priority(*parsed.Priority)
	}

	if parsed.Estimate != nil {
		builder.WithEstimate(*parsed.Estimate)
	}

	if parsed.Motivation != nil {
		builder.WithMotivation(*parsed.Motivation)
	}

	if parsed.Important != nil {
		if *parsed.Important {
			builder.Important()
		} else {
			builder.NotImportant()
		}
	}

	if parsed.Urgent != nil {
		if *parsed.Urgent {
			builder.Urgent()
		} else {
			builder.NotUrgent()
		}
	}

	if parsed.ScheduledOn != nil {
		builder.ScheduledOn(*parsed.ScheduledOn)
	}
}

func (h *Handler) updateNote(
	ctx context.Context,
	input UpdateInput,
) (*mcp.CallToolResult, UpdateOutput, error) {
	_, id, err := lunatask.ParseReference(input.ID)
	if err != nil {
		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"),
			UpdateOutput{Entity: input.Entity}, nil
	}

	if input.NotebookID != nil {
		if err := lunatask.ValidateUUID(*input.NotebookID); err != nil {
			return shared.ErrorResult("invalid notebook_id: expected UUID"),
				UpdateOutput{Entity: input.Entity}, nil
		}
	}

	builder := h.client.NewNoteUpdate(id)

	if input.Name != nil {
		builder.WithName(*input.Name)
	}

	if input.NotebookID != nil {
		builder.InNotebook(*input.NotebookID)
	}

	if input.Content != nil {
		builder.WithContent(*input.Content)
	}

	if input.Date != nil {
		date, err := dateutil.Parse(*input.Date)
		if err != nil {
			return shared.ErrorResult(err.Error()), UpdateOutput{Entity: input.Entity}, nil
		}

		builder.OnDate(date)
	}

	note, err := builder.Update(ctx)
	if err != nil {
		return shared.ErrorResult(err.Error()), UpdateOutput{Entity: input.Entity}, nil
	}

	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceNote, note.ID)

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Note updated: " + deepLink,
		}},
	}, UpdateOutput{Entity: input.Entity, DeepLink: deepLink}, nil
}

func (h *Handler) updatePerson(
	ctx context.Context,
	input UpdateInput,
) (*mcp.CallToolResult, UpdateOutput, error) {
	_, id, err := lunatask.ParseReference(input.ID)
	if err != nil {
		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"),
			UpdateOutput{Entity: input.Entity}, nil
	}

	builder := h.client.NewPersonUpdate(id)

	if input.FirstName != nil {
		builder.FirstName(*input.FirstName)
	}

	if input.LastName != nil {
		builder.LastName(*input.LastName)
	}

	if input.Relationship != nil {
		rel, err := lunatask.ParseRelationshipStrength(*input.Relationship)
		if err != nil {
			return shared.ErrorResult(err.Error()), UpdateOutput{Entity: input.Entity}, nil
		}

		builder.WithRelationshipStrength(rel)
	}

	person, err := builder.Update(ctx)
	if err != nil {
		return shared.ErrorResult(err.Error()), UpdateOutput{Entity: input.Entity}, nil
	}

	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourcePerson, person.ID)

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Person updated: " + deepLink,
		}},
	}, UpdateOutput{Entity: input.Entity, DeepLink: deepLink}, nil
}
