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

package lunatask

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

// Source represents a task source like GitHub or other integrations
type Source struct {
	Source   string `json:"source"`
	SourceID string `json:"source_id"`
}

// Task represents a task returned from the Lunatask 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    *string  `json:"scheduled_on"`
	CompletedAt    *string  `json:"completed_at"`
	CreatedAt      string   `json:"created_at"`
	UpdatedAt      string   `json:"updated_at"`
}

// CreateTaskRequest represents the request to create a task in Lunatask
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 *string `json:"scheduled_on,omitempty"`
	CompletedAt *string `json:"completed_at,omitempty"`
	Source      *string `json:"source,omitempty"`
	SourceID    *string `json:"source_id,omitempty"`
}

// UpdateTaskRequest represents the request to update a task in Lunatask.
// All fields are optional; only provided fields will be updated.
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 *string `json:"scheduled_on,omitempty"`
	CompletedAt *string `json:"completed_at,omitempty"`
}

// TaskResponse represents a single task response from the API
type TaskResponse struct {
	Task Task `json:"task"`
}

// TasksResponse represents a list of tasks response from the API
type TasksResponse struct {
	Tasks []Task `json:"tasks"`
}

// ListTasksOptions contains optional filters for listing tasks
type ListTasksOptions struct {
	Source   *string
	SourceID *string
}

// ListTasks retrieves all tasks, optionally filtered by source and/or source_id
func (c *Client) ListTasks(ctx context.Context, opts *ListTasksOptions) ([]Task, error) {
	u := fmt.Sprintf("%s/tasks", c.BaseURL)

	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 {
			u = fmt.Sprintf("%s?%s", u, params.Encode())
		}
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
	}

	body, err := c.doRequest(req)
	if err != nil {
		return nil, err
	}

	var response TasksResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return response.Tasks, nil
}

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

	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodGet,
		fmt.Sprintf("%s/tasks/%s", c.BaseURL, taskID),
		nil,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
	}

	body, err := c.doRequest(req)
	if err != nil {
		return nil, err
	}

	var response TaskResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response.Task, nil
}

// CreateTask creates a new task in Lunatask.
// Returns nil, nil if a matching task already exists (HTTP 204).
func (c *Client) CreateTask(ctx context.Context, task *CreateTaskRequest) (*Task, error) {
	if task.Name == "" {
		return nil, fmt.Errorf("%w: name is required", ErrBadRequest)
	}

	payloadBytes, err := json.Marshal(task)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal payload: %w", err)
	}

	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodPost,
		fmt.Sprintf("%s/tasks", c.BaseURL),
		bytes.NewBuffer(payloadBytes),
	)
	if err != nil {
		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	body, err := c.doRequest(req)
	if err != nil {
		// Check for 204 No Content (task already exists)
		var apiErr *APIError
		if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNoContent {
			return nil, nil
		}
		return nil, err
	}

	// Handle empty body (204 case that slipped through)
	if len(body) == 0 {
		return nil, nil
	}

	var response TaskResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response.Task, nil
}

// UpdateTask updates an existing task in Lunatask
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)
	}

	payloadBytes, err := json.Marshal(task)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal payload: %w", err)
	}

	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodPut,
		fmt.Sprintf("%s/tasks/%s", c.BaseURL, taskID),
		bytes.NewBuffer(payloadBytes),
	)
	if err != nil {
		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	body, err := c.doRequest(req)
	if err != nil {
		return nil, err
	}

	var response TaskResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response.Task, nil
}

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

	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodDelete,
		fmt.Sprintf("%s/tasks/%s", c.BaseURL, taskID),
		nil,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to create HTTP request: %w", err)
	}

	body, err := c.doRequest(req)
	if err != nil {
		return nil, err
	}

	var response TaskResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &response.Task, nil
}
