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

// Package crud provides consolidated MCP tools for Lunatask CRUD operations.
package crud

import (
	"context"

	"git.secluded.site/go-lunatask"
	"git.secluded.site/lune/internal/config"
	"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"
)

// Entity type constants.
const (
	EntityTask     = "task"
	EntityNote     = "note"
	EntityPerson   = "person"
	EntityJournal  = "journal"
	EntityArea     = "area"
	EntityGoal     = "goal"
	EntityNotebook = "notebook"
	EntityHabit    = "habit"
)

// CreateToolName is the name of the consolidated create tool.
const CreateToolName = "create"

// CreateToolDescription describes the create tool for LLMs.
const CreateToolDescription = `Create a new entity in Lunatask.
Required fields depend on entity type:
- task: name, area_id
- person: first_name
- note, journal: all fields optional
Returns the new entity's ID and deep link.`

// CreateToolAnnotations returns hints about tool behavior.
func CreateToolAnnotations() *mcp.ToolAnnotations {
	return &mcp.ToolAnnotations{
		DestructiveHint: ptr(false),
		OpenWorldHint:   ptr(true),
		Title:           "Create entity",
	}
}

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

	schema.Properties["entity"].Enum = []any{
		EntityTask, EntityNote, EntityPerson, EntityJournal,
	}
	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
}

// CreateInput is the input schema for the consolidated create tool.
type CreateInput struct {
	Entity string `json:"entity" jsonschema:"Entity type to create"`

	// Common fields
	Name     *string `json:"name,omitempty"      jsonschema:"Title/name (required for task/person)"`
	Content  *string `json:"content,omitempty"   jsonschema:"Markdown content"`
	Source   *string `json:"source,omitempty"    jsonschema:"Origin identifier for integrations"`
	SourceID *string `json:"source_id,omitempty" jsonschema:"Source-specific ID (requires source)"`

	// Task-specific fields
	AreaID      *string `json:"area_id,omitempty"      jsonschema:"Area (UUID, deep link, or key) - required for task"`
	GoalID      *string `json:"goal_id,omitempty"      jsonschema:"Goal (UUID, deep link, or config key)"`
	Status      *string `json:"status,omitempty"       jsonschema:"Initial status (default: later)"`
	Note        *string `json:"note,omitempty"         jsonschema:"Task note (Markdown)"`
	Priority    *string `json:"priority,omitempty"     jsonschema:"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:"Notebook UUID"`

	// Person-specific fields
	FirstName    *string `json:"first_name,omitempty"   jsonschema:"First name (required for person)"`
	LastName     *string `json:"last_name,omitempty"    jsonschema:"Last name"`
	Relationship *string `json:"relationship,omitempty" jsonschema:"Relationship strength"`

	// Journal-specific fields
	Date *string `json:"date,omitempty" jsonschema:"Entry date (strtotime syntax, default: today)"`
}

// CreateOutput is the output schema for the consolidated create tool.
type CreateOutput struct {
	Entity   string `json:"entity"`
	DeepLink string `json:"deep_link,omitempty"`
	ID       string `json:"id,omitempty"`
}

// Handler handles consolidated CRUD tool requests.
type Handler struct {
	client    *lunatask.Client
	cfg       *config.Config
	areas     []shared.AreaProvider
	habits    []shared.HabitProvider
	notebooks []shared.NotebookProvider
}

// NewHandler creates a new consolidated CRUD handler.
func NewHandler(
	accessToken string,
	cfg *config.Config,
	areas []shared.AreaProvider,
	habits []shared.HabitProvider,
	notebooks []shared.NotebookProvider,
) *Handler {
	return &Handler{
		client:    lunatask.NewClient(accessToken, lunatask.UserAgent("lune-mcp/1.0")),
		cfg:       cfg,
		areas:     areas,
		habits:    habits,
		notebooks: notebooks,
	}
}

// HandleCreate creates a new entity based on the entity type.
func (h *Handler) HandleCreate(
	ctx context.Context,
	_ *mcp.CallToolRequest,
	input CreateInput,
) (*mcp.CallToolResult, CreateOutput, error) {
	switch input.Entity {
	case EntityTask:
		return h.createTask(ctx, input)
	case EntityNote:
		return h.createNote(ctx, input)
	case EntityPerson:
		return h.createPerson(ctx, input)
	case EntityJournal:
		return h.createJournal(ctx, input)
	default:
		return shared.ErrorResult("invalid entity: must be task, note, person, or journal"),
			CreateOutput{Entity: input.Entity}, nil
	}
}

// parsedTaskCreateInput holds validated and parsed task create input fields.
type parsedTaskCreateInput struct {
	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) createTask(
	ctx context.Context,
	input CreateInput,
) (*mcp.CallToolResult, CreateOutput, error) {
	parsed, errResult := h.parseTaskCreateInput(input)
	if errResult != nil {
		return errResult, CreateOutput{Entity: input.Entity}, nil
	}

	builder := h.client.NewTask(parsed.Name)
	applyToTaskBuilder(builder, parsed)

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

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

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Task created: " + deepLink,
		}},
	}, CreateOutput{Entity: input.Entity, DeepLink: deepLink, ID: task.ID}, nil
}

//nolint:cyclop,funlen
func (h *Handler) parseTaskCreateInput(input CreateInput) (*parsedTaskCreateInput, *mcp.CallToolResult) {
	if input.Name == nil || *input.Name == "" {
		return nil, shared.ErrorResult("name is required for task creation")
	}

	if input.AreaID == nil || *input.AreaID == "" {
		return nil, shared.ErrorResult("area_id is required for task creation")
	}

	parsed := &parsedTaskCreateInput{
		Name:      *input.Name,
		Note:      input.Note,
		Estimate:  input.Estimate,
		Important: input.Important,
		Urgent:    input.Urgent,
	}

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

	parsed.AreaID = areaID

	if input.GoalID != nil {
		goalID, err := validate.GoalRef(h.cfg, parsed.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 {
		area := h.cfg.AreaByID(parsed.AreaID)
		if area == nil {
			return nil, shared.ErrorResult("internal error: area not found after validation")
		}

		status, err := shared.ValidateStatusForWorkflow(*input.Status, area.Workflow)
		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 applyToTaskBuilder(builder *lunatask.TaskBuilder, parsed *parsedTaskCreateInput) {
	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) createNote(
	ctx context.Context,
	input CreateInput,
) (*mcp.CallToolResult, CreateOutput, error) {
	if input.NotebookID != nil {
		if err := lunatask.ValidateUUID(*input.NotebookID); err != nil {
			return shared.ErrorResult("invalid notebook_id: expected UUID"),
				CreateOutput{Entity: input.Entity}, nil
		}
	}

	builder := h.client.NewNote()

	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.Source != nil && input.SourceID != nil {
		builder.FromSource(*input.Source, *input.SourceID)
	}

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

	if note == nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{
				Text: "Note already exists (duplicate source)",
			}},
		}, CreateOutput{Entity: input.Entity}, nil
	}

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

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Note created: " + deepLink,
		}},
	}, CreateOutput{Entity: input.Entity, DeepLink: deepLink, ID: note.ID}, nil
}

func (h *Handler) createPerson(
	ctx context.Context,
	input CreateInput,
) (*mcp.CallToolResult, CreateOutput, error) {
	if input.FirstName == nil || *input.FirstName == "" {
		return shared.ErrorResult("first_name is required for person creation"),
			CreateOutput{Entity: input.Entity}, nil
	}

	lastName := ""
	if input.LastName != nil {
		lastName = *input.LastName
	}

	builder := h.client.NewPerson(*input.FirstName, lastName)

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

		builder.WithRelationshipStrength(rel)
	}

	if input.Source != nil && input.SourceID != nil {
		builder.FromSource(*input.Source, *input.SourceID)
	}

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

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

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Person created: " + deepLink,
		}},
	}, CreateOutput{Entity: input.Entity, DeepLink: deepLink, ID: person.ID}, nil
}

func (h *Handler) createJournal(
	ctx context.Context,
	input CreateInput,
) (*mcp.CallToolResult, CreateOutput, error) {
	dateStr := ""
	if input.Date != nil {
		dateStr = *input.Date
	}

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

	builder := h.client.NewJournalEntry(date)

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

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

	entry, err := builder.Create(ctx)
	if err != nil {
		return shared.ErrorResult(err.Error()), CreateOutput{Entity: input.Entity}, nil
	}

	formattedDate := date.Format("2006-01-02")

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{
			Text: "Journal entry created for " + formattedDate,
		}},
	}, CreateOutput{Entity: input.Entity, ID: entry.ID}, nil
}
