package cmd

import (
	"fmt"
	"io"
	"io/fs"
	"net/url"
	"os"
	"strings"

	"github.com/Snider/Borg/pkg/compress"
	"github.com/Snider/Borg/pkg/datanode"
	"github.com/Snider/Borg/pkg/github"
	"github.com/Snider/Borg/pkg/tim"
	"github.com/Snider/Borg/pkg/trix"
	"github.com/Snider/Borg/pkg/ui"
	"github.com/Snider/Borg/pkg/vcs"
	"github.com/spf13/cobra"
)

var allCmd = NewAllCmd()

func NewAllCmd() *cobra.Command {
	allCmd := &cobra.Command{
		Use:   "all [url]",
		Short: "Collect all resources from a URL",
		Long:  `Collect all resources from a URL, dispatching to the appropriate collector based on the URL type.`,
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			url := args[0]
			outputFile, _ := cmd.Flags().GetString("output")
			format, _ := cmd.Flags().GetString("format")
			compression, _ := cmd.Flags().GetString("compression")
			password, _ := cmd.Flags().GetString("password")

			if format != "datanode" && format != "tim" && format != "trix" {
				return fmt.Errorf("invalid format: %s (must be 'datanode', 'tim', or 'trix')", format)
			}

			owner, err := parseGithubOwner(url)
			if err != nil {
				return err
			}

			repos, err := GithubClient.GetPublicRepos(cmd.Context(), owner)
			if err != nil {
				return err
			}

			prompter := ui.NewNonInteractivePrompter(ui.GetVCSQuote)
			prompter.Start()
			defer prompter.Stop()

			var progressWriter io.Writer
			if prompter.IsInteractive() {
				bar := ui.NewProgressBar(len(repos), "Cloning repositories")
				progressWriter = ui.NewProgressWriter(bar)
			}

			cloner := vcs.NewGitCloner()
			allDataNodes := datanode.New()

			for _, repoURL := range repos {
				dn, err := cloner.CloneGitRepository(repoURL, progressWriter)
				if err != nil {
					// Log the error and continue
					fmt.Fprintln(cmd.ErrOrStderr(), "Error cloning repository:", err)
					continue
				}
				// This is not an efficient way to merge datanodes, but it's the only way for now
				// A better approach would be to add a Merge method to the DataNode
				repoName := strings.TrimSuffix(repoURL, ".git")
				parts := strings.Split(repoName, "/")
				repoName = parts[len(parts)-1]

				err = dn.Walk(".", func(path string, de fs.DirEntry, err error) error {
					if err != nil {
						return err
					}
					if !de.IsDir() {
						err := func() error {
							file, err := dn.Open(path)
							if err != nil {
								return err
							}
							defer file.Close()
							data, err := io.ReadAll(file)
							if err != nil {
								return err
							}
							allDataNodes.AddData(repoName+"/"+path, data)
							return nil
						}()
						if err != nil {
							return err
						}
					}
					return nil
				})
				if err != nil {
					fmt.Fprintln(cmd.ErrOrStderr(), "Error walking datanode:", err)
					continue
				}
			}

			var data []byte
			if format == "tim" {
				tim, err := tim.FromDataNode(allDataNodes)
				if err != nil {
					return fmt.Errorf("error creating tim: %w", err)
				}
				data, err = tim.ToTar()
				if err != nil {
					return fmt.Errorf("error serializing tim: %w", err)
				}
			} else if format == "trix" {
				data, err = trix.ToTrix(allDataNodes, password)
				if err != nil {
					return fmt.Errorf("error serializing trix: %w", err)
				}
			} else {
				data, err = allDataNodes.ToTar()
				if err != nil {
					return fmt.Errorf("error serializing DataNode: %w", err)
				}
			}

			compressedData, err := compress.Compress(data, compression)
			if err != nil {
				return fmt.Errorf("error compressing data: %w", err)
			}

			err = os.WriteFile(outputFile, compressedData, 0644)
			if err != nil {
				return fmt.Errorf("error writing DataNode to file: %w", err)
			}

			fmt.Fprintln(cmd.OutOrStdout(), "All repositories saved to", outputFile)

			return nil
		},
	}
	allCmd.PersistentFlags().String("output", "all.dat", "Output file for the DataNode")
	allCmd.PersistentFlags().String("format", "datanode", "Output format (datanode, tim, or trix)")
	allCmd.PersistentFlags().String("compression", "none", "Compression format (none, gz, or xz)")
	allCmd.PersistentFlags().String("password", "", "Password for encryption")
	return allCmd
}

func GetAllCmd() *cobra.Command {
	return allCmd
}

func init() {
	RootCmd.AddCommand(GetAllCmd())
}

func parseGithubOwner(u string) (string, error) {
	owner, _, err := github.ParseRepoFromURL(u)
	if err == nil {
		return owner, nil
	}

	parsedURL, err := url.Parse(u)
	if err != nil {
		return "", fmt.Errorf("invalid URL: %w", err)
	}

	path := strings.Trim(parsedURL.Path, "/")
	if path == "" {
		return "", fmt.Errorf("invalid owner URL: %s", u)
	}
	parts := strings.Split(path, "/")
	if len(parts) != 1 || parts[0] == "" {
		return "", fmt.Errorf("invalid owner URL: %s", u)
	}
	return parts[0], nil
}
