package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"

	"github.com/safing/portbase/updater"
)

var (
	releaseCmd = &cobra.Command{
		Use:   "release",
		Short: "Release scans the distribution directory and creates registry indexes and the symlink structure",
		Args:  cobra.ExactArgs(1),
		RunE:  release,
	}
	preReleaseCmd = &cobra.Command{
		Use:   "prerelease",
		Short: "Stage scans the specified directory and loads the indexes - it then creates a staging index with all files newer than the stable and beta indexes",
		Args:  cobra.ExactArgs(1),
		RunE:  release,
	}
	preReleaseFrom   string
	resetPreReleases bool
)

func init() {
	rootCmd.AddCommand(releaseCmd)
	rootCmd.AddCommand(preReleaseCmd)

	preReleaseCmd.Flags().StringVar(&preReleaseFrom, "from", "", "Make a pre-release based on the given channel")
	_ = preReleaseCmd.MarkFlagRequired("from")
	preReleaseCmd.Flags().BoolVar(&resetPreReleases, "reset", false, "Reset pre-release assets")
}

func release(cmd *cobra.Command, args []string) error {
	channel := args[0]

	// Check if we want to reset instead.
	if resetPreReleases {
		return removeFilesFromIndex(getChannelVersions(preReleaseFrom, true))
	}

	// Write new index.
	err := writeIndex(
		channel,
		getChannelVersions(preReleaseFrom, false),
	)
	if err != nil {
		return err
	}

	// Only when doing a release:
	if preReleaseFrom == "" {
		// Create symlinks to latest stable versions.
		if !confirm("\nDo you want to write latest symlinks?") {
			fmt.Println("aborted...")
			return nil
		}
		symlinksDir := registry.StorageDir().ChildDir("latest", 0o755)
		err = registry.CreateSymlinks(symlinksDir)
		if err != nil {
			return err
		}
		fmt.Println("written latest symlinks")
	}

	return nil
}

func writeIndex(channel string, versions map[string]string) error {
	// Create new index file.
	indexFile := &updater.IndexFile{
		Channel:   channel,
		Published: time.Now().UTC().Round(time.Second),
		Releases:  versions,
	}

	// Export versions and format them.
	confirmData, err := json.MarshalIndent(indexFile, "", " ")
	if err != nil {
		return err
	}

	// Build index paths.
	oldIndexPath := filepath.Join(registry.StorageDir().Path, channel+".json")
	newIndexPath := filepath.Join(registry.StorageDir().Path, channel+".v2.json")

	// Print preview.
	fmt.Printf("%s\n%s\n%s\n\n", channel, oldIndexPath, newIndexPath)
	fmt.Println(string(confirmData))

	// Ask for confirmation.
	if !confirm("\nDo you want to write this index?") {
		fmt.Println("aborted...")
		return nil
	}

	// Write indexes.
	err = writeAsJSON(oldIndexPath, versions)
	if err != nil {
		return fmt.Errorf("failed to write %s: %w", oldIndexPath, err)
	}
	err = writeAsJSON(newIndexPath, indexFile)
	if err != nil {
		return fmt.Errorf("failed to write %s: %w", newIndexPath, err)
	}

	return nil
}

func writeAsJSON(path string, data any) error {
	// Marshal to JSON.
	jsonData, err := json.MarshalIndent(data, "", " ")
	if err != nil {
		return err
	}

	// Write to disk.
	err = os.WriteFile(path, jsonData, 0o0644) //nolint:gosec
	if err != nil {
		return err
	}

	fmt.Printf("written %s\n", path)
	return nil
}

func removeFilesFromIndex(versions map[string]string) error {
	// Print preview.
	fmt.Println("To be deleted:")
	for _, filePath := range versions {
		fmt.Println(filePath)
	}

	// Ask for confirmation.
	if !confirm("\nDo you want to delete these files?") {
		fmt.Println("aborted...")
		return nil
	}

	// Delete files.
	for _, filePath := range versions {
		err := os.Remove(filePath)
		if err != nil {
			return err
		}
	}
	fmt.Println("deleted")

	return nil
}

func getChannelVersions(prereleaseFrom string, storagePath bool) map[string]string {
	if prereleaseFrom != "" {
		registry.AddIndex(updater.Index{
			Path:       prereleaseFrom + ".json",
			PreRelease: false,
		})
		err := registry.LoadIndexes(context.Background())
		if err != nil {
			panic(err)
		}
	}

	// Sort all versions.
	registry.SelectVersions()
	export := registry.Export()

	// Go through all versions and save the highest version, if not stable or beta.
	versions := make(map[string]string)
	for _, rv := range export {
		highestVersion := rv.Versions[0]

		// Ignore versions that are in the reference release channel.
		if highestVersion.CurrentRelease {
			continue
		}

		// Add highest version of matching release channel.
		if storagePath {
			versions[rv.Identifier] = rv.GetFile().Path()
		} else {
			versions[rv.Identifier] = highestVersion.VersionNumber
		}
	}

	return versions
}
