package main

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/rodaine/table"
	"github.com/schollz/progressbar/v3"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"github.com/sstent/go-garth-cli/pkg/garmin"
)

var (
	activitiesCmd = &cobra.Command{
		Use:   "activities",
		Short: "Manage Garmin Connect activities",
		Long:  `Provides commands to list, get details, search, and download Garmin Connect activities.`,
	}

	listActivitiesCmd = &cobra.Command{
		Use:   "list",
		Short: "List recent activities",
		Long:  `List recent Garmin Connect activities with optional filters.`,
		RunE:  runListActivities,
	}

	getActivitiesCmd = &cobra.Command{
		Use:   "get [activityID]",
		Short: "Get activity details",
		Long:  `Get detailed information for a specific Garmin Connect activity.`,
		Args:  cobra.ExactArgs(1),
		RunE:  runGetActivity,
	}

	downloadActivitiesCmd = &cobra.Command{
		Use:   "download [activityID]",
		Short: "Download activity data",
		Args:  cobra.RangeArgs(0, 1), RunE: runDownloadActivity,
	}

	searchActivitiesCmd = &cobra.Command{
		Use:   "search",
		Short: "Search activities",
		Long:  `Search Garmin Connect activities by a query string.`,
		RunE:  runSearchActivities,
	}

	// Flags for listActivitiesCmd
	activityLimit    int
	activityOffset   int
	activityType     string
	activityDateFrom string
	activityDateTo   string

	// Flags for downloadActivitiesCmd
	downloadFormat   string
	outputDir        string
	downloadOriginal bool
	downloadAll      bool
)

func init() {
	rootCmd.AddCommand(activitiesCmd)

	activitiesCmd.AddCommand(listActivitiesCmd)
	listActivitiesCmd.Flags().IntVar(&activityLimit, "limit", 20, "Maximum number of activities to retrieve")
	listActivitiesCmd.Flags().IntVar(&activityOffset, "offset", 0, "Offset for activities list")
	listActivitiesCmd.Flags().StringVar(&activityType, "type", "", "Filter activities by type (e.g., running, cycling)")
	listActivitiesCmd.Flags().StringVar(&activityDateFrom, "from", "", "Start date for filtering activities (YYYY-MM-DD)")
	listActivitiesCmd.Flags().StringVar(&activityDateTo, "to", "", "End date for filtering activities (YYYY-MM-DD)")

	activitiesCmd.AddCommand(getActivitiesCmd)

	activitiesCmd.AddCommand(downloadActivitiesCmd)
	downloadActivitiesCmd.Flags().StringVar(&downloadFormat, "format", "gpx", "Download format (gpx, tcx, fit, csv)")
	downloadActivitiesCmd.Flags().StringVar(&outputDir, "output-dir", ".", "Output directory for downloaded files")
	downloadActivitiesCmd.Flags().BoolVar(&downloadOriginal, "original", false, "Download original uploaded file")

	downloadActivitiesCmd.Flags().BoolVar(&downloadAll, "all", false, "Download all activities matching filters")
	downloadActivitiesCmd.Flags().StringVar(&activityType, "type", "", "Filter activities by type (e.g., running, cycling)")
	downloadActivitiesCmd.Flags().StringVar(&activityDateFrom, "from", "", "Start date for filtering activities (YYYY-MM-DD)")
	downloadActivitiesCmd.Flags().StringVar(&activityDateTo, "to", "", "End date for filtering activities (YYYY-MM-DD)")

	activitiesCmd.AddCommand(searchActivitiesCmd)
	searchActivitiesCmd.Flags().StringP("query", "q", "", "Query string to search for activities")
}

func runListActivities(cmd *cobra.Command, args []string) error {
	garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	sessionFile := "garmin_session.json" // TODO: Make session file configurable
	if err := garminClient.LoadSession(sessionFile); err != nil {
		return fmt.Errorf("not logged in: %w", err)
	}

	opts := garmin.ActivityOptions{
		Limit:        activityLimit,
		Offset:       activityOffset,
		ActivityType: activityType,
	}

	if activityDateFrom != "" {
		opts.DateFrom, err = time.Parse("2006-01-02", activityDateFrom)
		if err != nil {
			return fmt.Errorf("invalid date format for --from: %w", err)
		}
	}

	if activityDateTo != "" {
		opts.DateTo, err = time.Parse("2006-01-02", activityDateTo)
		if err != nil {
			return fmt.Errorf("invalid date format for --to: %w", err)
		}
	}

	activities, err := garminClient.ListActivities(opts)
	if err != nil {
		return fmt.Errorf("failed to list activities: %w", err)
	}

	if len(activities) == 0 {
		fmt.Println("No activities found.")
		return nil
	}

	outputFormat := viper.GetString("output")

	switch outputFormat {
	case "json":
		data, err := json.MarshalIndent(activities, "", "  ")
		if err != nil {
			return fmt.Errorf("failed to marshal activities to JSON: %w", err)
		}
		fmt.Println(string(data))
	case "csv":
		writer := csv.NewWriter(os.Stdout)
		defer writer.Flush()

		writer.Write([]string{"ActivityID", "ActivityName", "ActivityType", "StartTime", "Distance(km)", "Duration(s)"})
		for _, activity := range activities {
			writer.Write([]string{
				fmt.Sprintf("%d", activity.ActivityID),
				activity.ActivityName,
				activity.ActivityType.TypeKey,
				activity.StartTimeLocal.Format("2006-01-02 15:04:05"),
				fmt.Sprintf("%.2f", activity.Distance/1000),
				fmt.Sprintf("%.0f", activity.Duration),
			})
		}
	case "table":
		tbl := table.New("ID", "Name", "Type", "Date", "Distance (km)", "Duration (s)")
		for _, activity := range activities {
			tbl.AddRow(
				fmt.Sprintf("%d", activity.ActivityID),
				activity.ActivityName,
				activity.ActivityType.TypeKey,
				activity.StartTimeLocal.Format("2006-01-02 15:04:05"),
				fmt.Sprintf("%.2f", activity.Distance/1000),
				fmt.Sprintf("%.0f", activity.Duration),
			)
		}
		tbl.Print()
	default:
		return fmt.Errorf("unsupported output format: %s", outputFormat)
	}

	return nil
}

func runGetActivity(cmd *cobra.Command, args []string) error {
	activityIDStr := args[0]
	activityID, err := strconv.Atoi(activityIDStr)
	if err != nil {
		return fmt.Errorf("invalid activity ID: %w", err)
	}

	garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	sessionFile := "garmin_session.json" // TODO: Make session file configurable
	if err := garminClient.LoadSession(sessionFile); err != nil {
		return fmt.Errorf("not logged in: %w", err)
	}

	activityDetail, err := garminClient.GetActivity(activityID)
	if err != nil {
		return fmt.Errorf("failed to get activity details: %w", err)
	}

	fmt.Printf("Activity Details (ID: %d):\n", activityDetail.ActivityID)
	fmt.Printf("  Name: %s\n", activityDetail.ActivityName)
	fmt.Printf("  Type: %s\n", activityDetail.ActivityType.TypeKey)
	fmt.Printf("  Date: %s\n", activityDetail.StartTimeLocal.Format("2006-01-02 15:04:05"))
	fmt.Printf("  Distance: %.2f km\n", activityDetail.Distance/1000)
	fmt.Printf("  Duration: %.0f s\n", activityDetail.Duration)
	fmt.Printf("  Description: %s\n", activityDetail.Description)

	return nil
}

func runDownloadActivity(cmd *cobra.Command, args []string) error {
	var wg sync.WaitGroup
	const concurrencyLimit = 5 // Limit concurrent downloads
	sem := make(chan struct{}, concurrencyLimit)

	garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	sessionFile := "garmin_session.json" // TODO: Make session file configurable
	if err := garminClient.LoadSession(sessionFile); err != nil {
		return fmt.Errorf("not logged in: %w", err)
	}

	var activitiesToDownload []garmin.Activity

	if downloadAll || len(args) == 0 {
		opts := garmin.ActivityOptions{
			ActivityType: activityType,
		}

		if activityDateFrom != "" {
			opts.DateFrom, err = time.Parse("2006-01-02", activityDateFrom)
			if err != nil {
				return fmt.Errorf("invalid date format for --from: %w", err)
			}
		}

		if activityDateTo != "" {
			opts.DateTo, err = time.Parse("2006-01-02", activityDateTo)
			if err != nil {
				return fmt.Errorf("invalid date format for --to: %w", err)
			}
		}

		activitiesToDownload, err = garminClient.ListActivities(opts)
		if err != nil {
			return fmt.Errorf("failed to list activities for batch download: %w", err)
		}

		if len(activitiesToDownload) == 0 {
			fmt.Println("No activities found matching the filters for download.")
			return nil
		}
	} else if len(args) == 1 {
		activityIDStr := args[0]
		activityID, err := strconv.Atoi(activityIDStr)
		if err != nil {
			return fmt.Errorf("invalid activity ID: %w", err)
		}
		// For single download, we need to fetch the activity details to get its name and type
		activityDetail, err := garminClient.GetActivity(activityID)
		if err != nil {
			return fmt.Errorf("failed to get activity details for download: %w", err)
		}
		activitiesToDownload = []garmin.Activity{activityDetail.Activity}
	} else {
		return fmt.Errorf("invalid arguments: specify an activity ID or use --all with filters")
	}

	fmt.Printf("Starting download of %d activities...\n", len(activitiesToDownload))

	bar := progressbar.NewOptions(len(activitiesToDownload),
		progressbar.OptionEnableColorCodes(true),
		progressbar.OptionShowBytes(false),
		progressbar.OptionSetWidth(15),
		progressbar.OptionSetDescription("Downloading activities..."),
		progressbar.OptionSetTheme(progressbar.Theme{
			Saucer:        "[green]=[reset]",
			SaucerPadding: " ",
			BarStart:      "[ ",
			BarEnd:        " ]",
		}),
	)

	for _, activity := range activitiesToDownload {
		wg.Add(1)
		sem <- struct{}{}
		go func(activity garmin.Activity) {
			defer wg.Done()
			defer func() { <-sem }()

			if downloadFormat == "csv" {
				activityDetail, err := garminClient.GetActivity(int(activity.ActivityID))
				if err != nil {
					fmt.Printf("Warning: Failed to get activity details for CSV export for activity %d: %v\n", activity.ActivityID, err)
					bar.Add(1)
					return
				}

				filename := fmt.Sprintf("%d.csv", activity.ActivityID)
				outputPath := filename
				if outputDir != "" {
					outputPath = filepath.Join(outputDir, filename)
				}

				file, err := os.Create(outputPath)
				if err != nil {
					fmt.Printf("Warning: Failed to create CSV file for activity %d: %v\n", activity.ActivityID, err)
					bar.Add(1)
					return
				}
				defer file.Close()

				writer := csv.NewWriter(file)
				defer writer.Flush()

				// Write header
				writer.Write([]string{"ActivityID", "ActivityName", "ActivityType", "StartTime", "Distance(km)", "Duration(s)", "Description"})

				// Write data
				writer.Write([]string{
					fmt.Sprintf("%d", activityDetail.ActivityID),
					activityDetail.ActivityName,
					activityDetail.ActivityType.TypeKey,
					activityDetail.StartTimeLocal.Format("2006-01-02 15:04:05"),
					fmt.Sprintf("%.2f", activityDetail.Distance/1000),
					fmt.Sprintf("%.0f", activityDetail.Duration),
					activityDetail.Description,
				})

				fmt.Printf("Activity %d summary exported to %s\n", activity.ActivityID, outputPath)
			} else {
				filename := fmt.Sprintf("%d.%s", activity.ActivityID, downloadFormat)
				if downloadOriginal {
					filename = fmt.Sprintf("%d_original.fit", activity.ActivityID) // Assuming original is .fit
				}
				outputPath := filepath.Join(outputDir, filename)

				// Check if file already exists
				if _, err := os.Stat(outputPath); err == nil {
					fmt.Printf("Skipping activity %d: file already exists at %s\n", activity.ActivityID, outputPath)
					bar.Add(1)
					return
				} else if !os.IsNotExist(err) {
					fmt.Printf("Warning: Failed to check existence of file %s for activity %d: %v\n", outputPath, activity.ActivityID, err)
					bar.Add(1)
					return
				}

				opts := garmin.DownloadOptions{
					Format:    downloadFormat,
					OutputDir: outputDir,
					Original:  downloadOriginal,
					Filename:  filename, // Pass filename to opts
				}

				fmt.Printf("Downloading activity %d in %s format to %s...\n", activity.ActivityID, downloadFormat, outputPath)
				if err := garminClient.DownloadActivity(int(activity.ActivityID), opts); err != nil {
					fmt.Printf("Warning: Failed to download activity %d: %v\n", activity.ActivityID, err)
					bar.Add(1)
					return
				}

				fmt.Printf("Activity %d downloaded successfully.\n", activity.ActivityID)
			}
			bar.Add(1)
		}(activity)
	}

	wg.Wait()
	bar.Finish()
	fmt.Println("All downloads finished.")

	return nil
}

func runSearchActivities(cmd *cobra.Command, args []string) error {
	query, err := cmd.Flags().GetString("query")
	if err != nil || query == "" {
		return fmt.Errorf("search query cannot be empty")
	}

	garminClient, err := garmin.NewClient("www.garmin.com") // TODO: Domain should be configurable
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}

	sessionFile := "garmin_session.json" // TODO: Make session file configurable
	if err := garminClient.LoadSession(sessionFile); err != nil {
		return fmt.Errorf("not logged in: %w", err)
	}

	activities, err := garminClient.SearchActivities(query)
	if err != nil {
		return fmt.Errorf("failed to search activities: %w", err)
	}

	if len(activities) == 0 {
		fmt.Printf("No activities found for query '%s'.\n", query)
		return nil
	}

	fmt.Printf("Activities matching '%s':\n", query)
	for _, activity := range activities {
		fmt.Printf("- ID: %d, Name: %s, Type: %s, Date: %s\n",
			activity.ActivityID, activity.ActivityName, activity.ActivityType.TypeKey,
			activity.StartTimeLocal.Format("2006-01-02"))
	}

	return nil
}
