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

package crud

import (
	"context"
	"fmt"

	"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/google/jsonschema-go/jsonschema"
	"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 = `Update an existing Lunatask entity.
Only provided fields are modified—omit fields to leave unchanged.
Task note/content replaces existing (not appended). Idempotent.`

// UpdateToolAnnotations returns hints about tool behavior.
func UpdateToolAnnotations() *mcp.ToolAnnotations {
	return &mcp.ToolAnnotations{
		IdempotentHint: true,
		OpenWorldHint:  ptr(true),
		Title:          "Update entity",
	}
}

// UpdateInputSchema returns a custom schema with enum constraints.
func UpdateInputSchema() *jsonschema.Schema {
	schema, _ := jsonschema.For[UpdateInput](nil)

	schema.Properties["entity"].Enum = []any{
		EntityTask, EntityNote, EntityPerson,
	}
	schema.Properties["status"].Enum = toAnyStrings(lunatask.AllTaskStatuses())
	schema.Properties["priority"].Enum = prioritiesToAny(lunatask.AllPriorities())
	schema.Properties["motivation"].Enum = toAnyStrings(lunatask.AllMotivations())
	schema.Properties["relationship"].Enum = toAnyStrings(lunatask.AllRelationshipStrengths())

	return schema
}

// UpdateInput is the input schema for the consolidated update tool.
type UpdateInput struct {
	Entity string `json:"entity" jsonschema:"Entity type to update"`
	ID     string `json:"id"     jsonschema:"UUID or lunatask:// deep link"`

	// Common fields
	Name    *string `json:"name,omitempty"    jsonschema:"New title/name"`
	Content *string `json:"content,omitempty" jsonschema:"New content (Markdown, replaces existing)"`

	// Task-specific fields
	AreaID      *string `json:"area_id,omitempty"      jsonschema:"Move to area (UUID, deep link, or config key)"`
	GoalID      *string `json:"goal_id,omitempty"      jsonschema:"Move to goal (requires area_id)"`
	Status      *string `json:"status,omitempty"       jsonschema:"New task status"`
	Note        *string `json:"note,omitempty"         jsonschema:"New task note (Markdown, replaces existing)"`
	Priority    *string `json:"priority,omitempty"     jsonschema:"New priority level"`
	Estimate    *int    `json:"estimate,omitempty"     jsonschema:"Time estimate in minutes (0-720)"`
	Motivation  *string `json:"motivation,omitempty"   jsonschema:"Task motivation"`
	Important   *bool   `json:"important,omitempty"    jsonschema:"Eisenhower matrix: important"`
	Urgent      *bool   `json:"urgent,omitempty"       jsonschema:"Eisenhower matrix: urgent"`
	ScheduledOn *string `json:"scheduled_on,omitempty" jsonschema:"Schedule date (strtotime syntax)"`

	// Note-specific fields
	NotebookID *string `json:"notebook_id,omitempty" jsonschema:"Move to notebook (UUID)"`
	Date       *string `json:"date,omitempty"        jsonschema:"Note date (strtotime syntax)"`

	// Person-specific fields
	FirstName    *string `json:"first_name,omitempty"   jsonschema:"New first name"`
	LastName     *string `json:"last_name,omitempty"    jsonschema:"New last name"`
	Relationship *string `json:"relationship,omitempty" jsonschema:"Relationship strength"`
}

// 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,gocognit,nestif
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 {
		if parsed.AreaID != nil {
			area := h.cfg.AreaByID(*parsed.AreaID)
			if area != nil {
				status, err := shared.ValidateStatusForWorkflow(*input.Status, area.Workflow)
				if err != nil {
					return nil, shared.ErrorResult(err.Error())
				}

				parsed.Status = &status
			}
		} else {
			status, err := lunatask.ParseTaskStatus(*input.Status)
			if err != nil {
				return nil, shared.ErrorResult(
					fmt.Sprintf("invalid status '%s'; valid: %s", *input.Status, shared.FormatAllStatuses()),
				)
			}

			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
}
