package cmd

import (
	"errors"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"github.com/Snider/Borg/pkg/datanode"
	borg_github "github.com/Snider/Borg/pkg/github"
	"github.com/google/go-github/v39/github"
	"github.com/spf13/cobra"
	"golang.org/x/mod/semver"
)

func NewCollectGithubReleaseCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "release [repository-url]",
		Short: "Download the latest release of a file from GitHub releases",
		Long:  `Download the latest release of a file from GitHub releases. If the file or URL has a version number, it will check for a higher version and download it if found.`,
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			logVal := cmd.Context().Value("logger")
			log, ok := logVal.(*slog.Logger)
			if !ok || log == nil {
				return errors.New("logger not properly initialised")
			}
			repoURL := args[0]
			outputDir, _ := cmd.Flags().GetString("output")
			pack, _ := cmd.Flags().GetBool("pack")
			file, _ := cmd.Flags().GetString("file")
			version, _ := cmd.Flags().GetString("version")

			_, err := GetRelease(log, repoURL, outputDir, pack, file, version)
			return err
		},
	}
	cmd.PersistentFlags().String("output", ".", "Output directory for the downloaded file")
	cmd.PersistentFlags().Bool("pack", false, "Pack all assets into a DataNode")
	cmd.PersistentFlags().String("file", "", "The file to download from the release")
	cmd.PersistentFlags().String("version", "", "The version to check against")
	return cmd
}

func init() {
	collectGithubCmd.AddCommand(NewCollectGithubReleaseCmd())
}

func GetRelease(log *slog.Logger, repoURL string, outputDir string, pack bool, file string, version string) (*github.RepositoryRelease, error) {
	owner, repo, err := borg_github.ParseRepoFromURL(repoURL)
	if err != nil {
		return nil, fmt.Errorf("failed to parse repository url: %w", err)
	}

	release, err := borg_github.GetLatestRelease(owner, repo)
	if err != nil {
		return nil, fmt.Errorf("failed to get latest release: %w", err)
	}

	log.Info("found latest release", "tag", release.GetTagName())

	if version != "" {
		tag := release.GetTagName()
		if !semver.IsValid(tag) {
			log.Info("latest release tag is not a valid semantic version, skipping comparison", "tag", tag)
		} else {
			if !semver.IsValid(version) {
				return nil, fmt.Errorf("invalid version string: %s", version)
			}
			if semver.Compare(tag, version) <= 0 {
				log.Info("latest release is not newer than the provided version", "latest", tag, "provided", version)
				return nil, nil
			}
		}
	}

	if pack {
		dn := datanode.New()
		var failedAssets []string
		for _, asset := range release.Assets {
			log.Info("downloading asset", "name", asset.GetName())
			data, err := borg_github.DownloadReleaseAsset(asset)
			if err != nil {
				log.Error("failed to download asset", "name", asset.GetName(), "err", err)
				failedAssets = append(failedAssets, asset.GetName())
				continue
			}
			dn.AddData(asset.GetName(), data)
		}
		if len(failedAssets) > 0 {
			return nil, fmt.Errorf("failed to download assets: %v", failedAssets)
		}

		tar, err := dn.ToTar()
		if err != nil {
			return nil, fmt.Errorf("failed to create datanode: %w", err)
		}

		if err := os.MkdirAll(outputDir, 0755); err != nil {
			return nil, fmt.Errorf("failed to create output directory: %w", err)
		}
		basename := release.GetTagName()
		if basename == "" {
			basename = "release"
		}
		outputFile := filepath.Join(outputDir, basename+".dat")

		err = os.WriteFile(outputFile, tar, 0644)
		if err != nil {
			return nil, fmt.Errorf("failed to write datanode: %w", err)
		}
		log.Info("datanode saved", "path", outputFile)
	} else {
		if len(release.Assets) == 0 {
			log.Info("no assets found in the latest release")
			return nil, nil
		}
		var assetToDownload *github.ReleaseAsset
		if file != "" {
			for _, asset := range release.Assets {
				if asset.GetName() == file {
					assetToDownload = asset
					break
				}
			}
			if assetToDownload == nil {
				return nil, fmt.Errorf("asset not found in the latest release: %s", file)
			}
		} else {
			assetToDownload = release.Assets[0]
		}
		if outputDir != "" {
			if err := os.MkdirAll(outputDir, 0755); err != nil {
				return nil, fmt.Errorf("failed to create output directory: %w", err)
			}
		}
		outputPath := filepath.Join(outputDir, assetToDownload.GetName())
		log.Info("downloading asset", "name", assetToDownload.GetName())
		data, err := borg_github.DownloadReleaseAsset(assetToDownload)
		if err != nil {
			return nil, fmt.Errorf("failed to download asset: %w", err)
		}
		err = os.WriteFile(outputPath, data, 0644)
		if err != nil {
			return nil, fmt.Errorf("failed to write asset to file: %w", err)
		}
		log.Info("asset downloaded", "path", outputPath)
	}
	return release, nil
}
