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

package lunatask

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/go-playground/validator/v10"
)

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

// Task is only ever used in responses
type Task struct {
	ID             string   `json:"id,omitempty"`
	AreaID         string   `json:"area_id,omitempty"`
	GoalID         string   `json:"goal_id,omitempty"`
	Status         string   `json:"status,omitempty"`
	PreviousStatus string   `json:"previous_status,omitempty"`
	Estimate       int      `json:"estimate,omitempty"`
	Priority       int      `json:"priority,omitempty"`
	Progress       int      `json:"progress,omitempty"`
	Motivation     string   `json:"motivation,omitempty"`
	Eisenhower     int      `json:"eisenhower,omitempty"`
	Sources        []Source `json:"sources,omitempty"`
	ScheduledOn    string   `json:"scheduled_on,omitempty"`
	CompletedAt    string   `json:"completed_at,omitempty"`
	CreatedAt      string   `json:"created_at,omitempty"`
	UpdatedAt      string   `json:"updated_at,omitempty"`
}

// CreateTaskRequest represents the request to create a task in Lunatask
type CreateTaskRequest struct {
	AreaID      string `json:"area_id,omitempty" validate:"omitempty,uuid4"`
	GoalID      string `json:"goal_id,omitempty" validate:"omitempty,uuid4"` // Assuming GoalID remains optional for tasks
	Name        string `json:"name" validate:"required,max=100"`
	Note        string `json:"note,omitempty" validate:"omitempty"`
	Status      string `json:"status,omitempty" validate:"omitempty,oneof=later next started waiting completed"`
	Motivation  string `json:"motivation,omitempty" validate:"omitempty,oneof=must should want unknown"`
	Estimate    int    `json:"estimate,omitempty" validate:"omitempty,min=0,max=720"`
	Priority    int    `json:"priority,omitempty" validate:"omitempty,min=-2,max=2"`
	Eisenhower  int    `json:"eisenhower,omitempty" validate:"omitempty,min=0,max=4"`
	ScheduledOn string `json:"scheduled_on,omitempty" validate:"omitempty"`
	CompletedAt string `json:"completed_at,omitempty" validate:"omitempty"`
	Source      string `json:"source,omitempty" validate:"omitempty"`
}

// CreateTaskResponse represents the response from Lunatask API when creating a task
type CreateTaskResponse struct {
	Task struct {
		ID string `json:"id"`
	} `json:"task"`
}

// UpdateTaskResponse represents the response from Lunatask API when updating a task
type UpdateTaskResponse struct {
	Task Task `json:"task"`
}

// DeleteTaskResponse represents the response from Lunatask API when deleting a task
type DeleteTaskResponse struct {
	Task Task `json:"task"`
}

// ValidationError represents errors returned by the validator
type ValidationError struct {
	Field   string
	Tag     string
	Message string
}

// Error implements the error interface for ValidationError
func (e ValidationError) Error() string {
	return e.Message
}

// ValidateTask validates the create task request
func ValidateTask(task *CreateTaskRequest) error {
	validate := validator.New()
	if err := validate.Struct(task); err != nil {
		var invalidValidationError *validator.InvalidValidationError
		if errors.As(err, &invalidValidationError) {
			return fmt.Errorf("invalid validation error: %w", err)
		}

		var validationErrs validator.ValidationErrors
		if errors.As(err, &validationErrs) {
			var msgBuilder strings.Builder
			msgBuilder.WriteString("task validation failed:")
			for _, e := range validationErrs {
				fmt.Fprintf(&msgBuilder, " field '%s' failed '%s' validation (was: %v);", e.Field(), e.Tag(), e.Value())
			}
			return errors.New(msgBuilder.String())
		}
		return fmt.Errorf("validation error: %w", err)
	}
	return nil
}

// CreateTask creates a new task in Lunatask
func (c *Client) CreateTask(ctx context.Context, task *CreateTaskRequest) (*CreateTaskResponse, error) {
	// Validate the task
	if err := ValidateTask(task); err != nil {
		return nil, err
	}

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

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

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "bearer "+c.AccessToken)

	// Send the request
	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
	}
	defer func() {
		if resp.Body != nil {
			if err := resp.Body.Close(); err != nil {
				// We're in a defer, so we can only log the error
				fmt.Printf("Error closing response body: %v\n", err)
			}
		}
	}()

	// Handle already exists (no content) case
	if resp.StatusCode == http.StatusNoContent {
		return nil, nil // Task already exists (not an error)
	}

	// Handle error status codes
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		respBody, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
	}

	// Read and parse the response
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %w", err)
	}

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

	return &response, nil
}

// UpdateTask updates an existing task in Lunatask
func (c *Client) UpdateTask(ctx context.Context, taskID string, task *CreateTaskRequest) (*UpdateTaskResponse, error) {
	if taskID == "" {
		return nil, errors.New("task ID cannot be empty")
	}

	// Validate the task payload
	// Note: ValidateTask checks fields like Name, Priority, Estimate, etc.
	// It's assumed that the API handles partial updates correctly,
	// especially for fields like Name or AreaID in CreateTaskRequest that lack `omitempty`.
	if err := ValidateTask(task); err != nil {
		return nil, err
	}

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

	// Create the request
	req, err := http.NewRequestWithContext(
		ctx,
		"PUT",
		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)
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "bearer "+c.AccessToken)

	// Send the request
	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
	}
	defer func() {
		if resp.Body != nil {
			if err := resp.Body.Close(); err != nil {
				fmt.Printf("Error closing response body: %v\n", err)
			}
		}
	}()

	// Handle error status codes
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		respBody, _ := io.ReadAll(resp.Body)
		// Consider specific handling for 404 Not Found if needed
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
	}

	// Read and parse the response
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %w", err)
	}

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

	return &response, nil
}

// DeleteTask deletes a task in Lunatask
func (c *Client) DeleteTask(ctx context.Context, taskID string) (*DeleteTaskResponse, error) {
	if taskID == "" {
		return nil, errors.New("task ID cannot be empty")
	}

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

	// Set headers
	req.Header.Set("Authorization", "bearer "+c.AccessToken)

	// Send the request
	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send HTTP request: %w", err)
	}
	defer func() {
		if resp.Body != nil {
			if err := resp.Body.Close(); err != nil {
				fmt.Printf("Error closing response body: %v\n", err)
			}
		}
	}()

	// Handle error status codes
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		respBody, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
	}

	// Read and parse the response
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %w", err)
	}

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

	return &response, nil
}
