package garth

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// WorkoutService provides methods for interacting with Garmin workout data.
type WorkoutService struct {
	client *APIClient
}

// NewWorkoutService creates a new WorkoutService instance.
// client: The authenticated APIClient used to make requests.
func NewWorkoutService(client *APIClient) *WorkoutService {
	return &WorkoutService{client: client}
}

// Workout represents a Garmin workout with basic information
type Workout struct {
	WorkoutID    int64     `json:"workoutId"`
	Name         string    `json:"workoutName"`
	Type         string    `json:"workoutType"`
	Description  string    `json:"description"`
	CreatedDate  time.Time `json:"createdDate"`
	UpdatedDate  time.Time `json:"updatedDate"`
	OwnerID      int64     `json:"ownerId"`
	IsPublic     bool      `json:"isPublic"`
	SportType    string    `json:"sportType"`
	SubSportType string    `json:"subSportType"`
}

// WorkoutDetails contains detailed information about a workout
type WorkoutDetails struct {
	Workout
	WorkoutSegments   []WorkoutSegment `json:"workoutSegments"`
	EstimatedDuration int              `json:"estimatedDuration"`
	EstimatedDistance float64          `json:"estimatedDistance"`
	TrainingLoad      float64          `json:"trainingLoad"`
	Tags              []string         `json:"tags"`
}

// WorkoutSegment represents a segment within a workout
type WorkoutSegment struct {
	SegmentID   int64             `json:"segmentId"`
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Order       int               `json:"order"`
	Exercises   []WorkoutExercise `json:"exercises"`
}

// WorkoutExercise represents an exercise within a workout segment
type WorkoutExercise struct {
	ExerciseID   int64   `json:"exerciseId"`
	Name         string  `json:"name"`
	Category     string  `json:"category"`
	Type         string  `json:"type"`
	Duration     int     `json:"duration,omitempty"`
	Distance     float64 `json:"distance,omitempty"`
	Repetitions  int     `json:"repetitions,omitempty"`
	Weight       float64 `json:"weight,omitempty"`
	RestInterval int     `json:"restInterval,omitempty"`
}

// WorkoutListOptions provides filtering options for listing workouts
type WorkoutListOptions struct {
	Limit        int
	StartDate    time.Time
	EndDate      time.Time
	SportType    string
	NameContains string
	OwnerID      int64
	Offset       int
	SortBy       string
	SortOrder    string
	Type         string
	Status       string
}

// WorkoutUpdate represents fields that can be updated on a workout
type WorkoutUpdate struct {
	Name         string `json:"name,omitempty"`
	Description  string `json:"description,omitempty"`
	Type         string `json:"type,omitempty"`
	SportType    string `json:"sportType,omitempty"`
	SubSportType string `json:"subSportType,omitempty"`
}

// List retrieves a list of workouts for the current user with optional filters
func (s *WorkoutService) List(ctx context.Context, opts WorkoutListOptions) ([]Workout, error) {
	params := url.Values{}
	if opts.Limit > 0 {
		params.Set("limit", strconv.Itoa(opts.Limit))
	}
	if opts.Offset > 0 {
		params.Set("offset", strconv.Itoa(opts.Offset))
	}
	if !opts.StartDate.IsZero() {
		params.Set("startDate", opts.StartDate.Format(time.RFC3339))
	}
	if !opts.EndDate.IsZero() {
		params.Set("endDate", opts.EndDate.Format(time.RFC3339))
	}
	if opts.SportType != "" {
		params.Set("sportType", opts.SportType)
	}
	if opts.Type != "" {
		params.Set("type", opts.Type)
	}
	if opts.Status != "" {
		params.Set("status", opts.Status)
	}
	if opts.NameContains != "" {
		params.Set("nameContains", opts.NameContains)
	}
	if opts.OwnerID > 0 {
		params.Set("ownerId", strconv.FormatInt(opts.OwnerID, 10))
	}
	if opts.SortBy != "" {
		params.Set("sortBy", opts.SortBy)
	}
	if opts.SortOrder != "" {
		params.Set("sortOrder", opts.SortOrder)
	}

	path := "/workout-service/workouts"
	if len(params) > 0 {
		path += "?" + params.Encode()
	}

	resp, err := s.client.Get(ctx, path)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to get workouts list",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read workouts response",
			Cause:      err,
		}
	}

	var workouts []Workout
	if err := json.Unmarshal(body, &workouts); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse workouts data",
			Cause:      err,
		}
	}

	return workouts, nil
}

// Get retrieves detailed information about a specific workout
func (s *WorkoutService) Get(ctx context.Context, id string) (*WorkoutDetails, error) {
	path := "/workout-service/workout/" + id

	resp, err := s.client.Get(ctx, path)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to get workout details",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read workout response",
			Cause:      err,
		}
	}

	var details WorkoutDetails
	if err := json.Unmarshal(body, &details); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse workout data",
			Cause:      err,
		}
	}

	return &details, nil
}

// Create creates a new workout
func (s *WorkoutService) Create(ctx context.Context, workout Workout) (*Workout, error) {
	jsonBody, err := json.Marshal(workout)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to marshal workout",
			Cause:      err,
		}
	}

	resp, err := s.client.Post(ctx, "/workout-service/workout", bytes.NewReader(jsonBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to create workout",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read workout response",
			Cause:      err,
		}
	}

	var createdWorkout Workout
	if err := json.Unmarshal(body, &createdWorkout); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse workout data",
			Cause:      err,
		}
	}

	return &createdWorkout, nil
}

// Update updates an existing workout
func (s *WorkoutService) Update(ctx context.Context, id string, update WorkoutUpdate) (*Workout, error) {
	jsonBody, err := json.Marshal(update)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to marshal workout update",
			Cause:      err,
		}
	}

	path := "/workout-service/workout/" + id
	resp, err := s.client.Put(ctx, path, bytes.NewReader(jsonBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to update workout",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read workout response",
			Cause:      err,
		}
	}

	var updatedWorkout Workout
	if err := json.Unmarshal(body, &updatedWorkout); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse workout data",
			Cause:      err,
		}
	}

	return &updatedWorkout, nil
}

// Delete deletes an existing workout
func (s *WorkoutService) Delete(ctx context.Context, id string) error {
	path := "/workout-service/workout/" + id
	resp, err := s.client.Delete(ctx, path, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
		return &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to delete workout",
		}
	}

	return nil
}

// GetWorkoutTemplates retrieves all workout templates for the current user
func (s *WorkoutService) GetWorkoutTemplates(ctx context.Context) ([]Workout, error) {
	path := "/workout-service/templates"

	resp, err := s.client.Get(ctx, path)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to get workout templates",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read workout templates response",
			Cause:      err,
		}
	}

	var templates []Workout
	if err := json.Unmarshal(body, &templates); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse workout templates data",
			Cause:      err,
		}
	}

	return templates, nil
}

// SearchWorkouts searches workouts by name or description
func (s *WorkoutService) SearchWorkouts(ctx context.Context, query string, limit int) ([]Workout, error) {
	params := url.Values{}
	params.Set("q", query)
	if limit > 0 {
		params.Set("limit", strconv.Itoa(limit))
	}

	path := "/workout-service/search?" + params.Encode()

	resp, err := s.client.Get(ctx, path)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to search workouts",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read search response",
			Cause:      err,
		}
	}

	var workouts []Workout
	if err := json.Unmarshal(body, &workouts); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse search results",
			Cause:      err,
		}
	}

	return workouts, nil
}

// CopyWorkout creates a copy of an existing workout
func (s *WorkoutService) CopyWorkout(ctx context.Context, id string, newName string) (*Workout, error) {
	path := "/workout-service/workout/" + id + "/copy"

	requestBody := map[string]string{"name": newName}
	jsonBody, err := json.Marshal(requestBody)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to marshal request body",
			Cause:      err,
		}
	}

	resp, err := s.client.Post(ctx, path, bytes.NewReader(jsonBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to copy workout",
		}
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to read copy response",
			Cause:      err,
		}
	}

	var copiedWorkout Workout
	if err := json.Unmarshal(body, &copiedWorkout); err != nil {
		return nil, &APIError{
			StatusCode: http.StatusInternalServerError,
			Message:    "Failed to parse copied workout data",
			Cause:      err,
		}
	}

	return &copiedWorkout, nil
}

// Export exports a workout in the specified format (fit, tcx, json)
func (s *WorkoutService) Export(ctx context.Context, id string, format string) (io.ReadCloser, error) {
	if format != "fit" && format != "tcx" && format != "json" {
		return nil, &APIError{
			StatusCode: http.StatusBadRequest,
			Message:    "Invalid format. Supported formats: fit, tcx, json",
		}
	}

	path := "/download-service/export/" + format + "/workout/" + id

	resp, err := s.client.Get(ctx, path)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		defer resp.Body.Close()
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    "Failed to export workout",
		}
	}

	return resp.Body, nil
}
