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

package lunatask

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"time"
)

// Task is a task in Lunatask. Name and Note are encrypted client-side
// and will be null when read back from the API.
type Task struct {
	ID             string     `json:"id"`
	AreaID         *string    `json:"area_id"`
	GoalID         *string    `json:"goal_id"`
	Name           *string    `json:"name"`
	Note           *string    `json:"note"`
	Status         *string    `json:"status"`
	PreviousStatus *string    `json:"previous_status"`
	Estimate       *int       `json:"estimate"`
	Priority       *int       `json:"priority"`
	Progress       *int       `json:"progress"`
	Motivation     *string    `json:"motivation"`
	Eisenhower     *int       `json:"eisenhower"`
	Sources        []Source   `json:"sources"`
	ScheduledOn    *Date      `json:"scheduled_on"`
	CompletedAt    *time.Time `json:"completed_at"`
	CreatedAt      time.Time  `json:"created_at"`
	UpdatedAt      time.Time  `json:"updated_at"`
}

// CreateTaskRequest defines a new task.
// Use [TaskBuilder] for a fluent construction API.
type CreateTaskRequest struct {
	Name        string     `json:"name"`
	AreaID      *string    `json:"area_id,omitempty"`
	GoalID      *string    `json:"goal_id,omitempty"`
	Note        *string    `json:"note,omitempty"`
	Status      *string    `json:"status,omitempty"`
	Motivation  *string    `json:"motivation,omitempty"`
	Estimate    *int       `json:"estimate,omitempty"`
	Priority    *int       `json:"priority,omitempty"`
	Eisenhower  *int       `json:"eisenhower,omitempty"`
	ScheduledOn *Date      `json:"scheduled_on,omitempty"`
	CompletedAt *time.Time `json:"completed_at,omitempty"`
	Source      *string    `json:"source,omitempty"`
	SourceID    *string    `json:"source_id,omitempty"`
}

// UpdateTaskRequest specifies which fields to change on a task.
// Only non-nil fields are updated. Use [TaskUpdateBuilder] for fluent construction.
type UpdateTaskRequest struct {
	Name        *string    `json:"name,omitempty"`
	AreaID      *string    `json:"area_id,omitempty"`
	GoalID      *string    `json:"goal_id,omitempty"`
	Note        *string    `json:"note,omitempty"`
	Status      *string    `json:"status,omitempty"`
	Motivation  *string    `json:"motivation,omitempty"`
	Estimate    *int       `json:"estimate,omitempty"`
	Priority    *int       `json:"priority,omitempty"`
	Eisenhower  *int       `json:"eisenhower,omitempty"`
	ScheduledOn *Date      `json:"scheduled_on,omitempty"`
	CompletedAt *time.Time `json:"completed_at,omitempty"`
}

// taskResponse wraps a single task from the API.
type taskResponse struct {
	Task Task `json:"task"`
}

// tasksResponse wraps a list of tasks from the API.
type tasksResponse struct {
	Tasks []Task `json:"tasks"`
}

// ListTasksOptions filters tasks by source integration.
type ListTasksOptions struct {
	Source   *string
	SourceID *string
}

// ListTasks returns all tasks, optionally filtered. Pass nil for all.
func (c *Client) ListTasks(ctx context.Context, opts *ListTasksOptions) ([]Task, error) {
	path := "/tasks"

	if opts != nil {
		params := url.Values{}
		if opts.Source != nil && *opts.Source != "" {
			params.Set("source", *opts.Source)
		}

		if opts.SourceID != nil && *opts.SourceID != "" {
			params.Set("source_id", *opts.SourceID)
		}

		if len(params) > 0 {
			path = fmt.Sprintf("%s?%s", path, params.Encode())
		}
	}

	resp, _, err := doJSON[tasksResponse](ctx, c, http.MethodGet, path, nil)
	if err != nil {
		return nil, err
	}

	return resp.Tasks, nil
}

// GetTask fetches a task by ID. Name and Note will be null (E2EE).
func (c *Client) GetTask(ctx context.Context, taskID string) (*Task, error) {
	if taskID == "" {
		return nil, fmt.Errorf("%w: task ID cannot be empty", ErrBadRequest)
	}

	resp, _, err := doJSON[taskResponse](ctx, c, http.MethodGet, "/tasks/"+taskID, nil)
	if err != nil {
		return nil, err
	}

	return &resp.Task, nil
}

// CreateTask adds a task. Returns (nil, nil) if a duplicate exists
// with matching source/source_id.
func (c *Client) CreateTask(ctx context.Context, task *CreateTaskRequest) (*Task, error) {
	if task.Name == "" {
		return nil, fmt.Errorf("%w: name is required", ErrBadRequest)
	}

	resp, noContent, err := doJSON[taskResponse](ctx, c, http.MethodPost, "/tasks", task)
	if err != nil {
		return nil, err
	}

	if noContent {
		// Intentional: duplicate exists (HTTP 204), not an error
		return nil, nil //nolint:nilnil
	}

	return &resp.Task, nil
}

// UpdateTask modifies a task. Only non-nil fields in the request are changed.
func (c *Client) UpdateTask(ctx context.Context, taskID string, task *UpdateTaskRequest) (*Task, error) {
	if taskID == "" {
		return nil, fmt.Errorf("%w: task ID cannot be empty", ErrBadRequest)
	}

	resp, _, err := doJSON[taskResponse](ctx, c, http.MethodPut, "/tasks/"+taskID, task)
	if err != nil {
		return nil, err
	}

	return &resp.Task, nil
}

// DeleteTask removes a task and returns its final state.
func (c *Client) DeleteTask(ctx context.Context, taskID string) (*Task, error) {
	if taskID == "" {
		return nil, fmt.Errorf("%w: task ID cannot be empty", ErrBadRequest)
	}

	resp, _, err := doJSON[taskResponse](ctx, c, http.MethodDelete, "/tasks/"+taskID, nil)
	if err != nil {
		return nil, err
	}

	return &resp.Task, nil
}
