package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/spf13/cobra"

	portlog "github.com/safing/portbase/log"
	"github.com/safing/portbase/updater"
	"github.com/safing/portmaster/updates/helper"
)

var (
	reset     bool
	intelOnly bool
)

func init() {
	rootCmd.AddCommand(updateCmd)
	rootCmd.AddCommand(purgeCmd)

	flags := updateCmd.Flags()
	flags.BoolVar(&reset, "reset", false, "Delete all resources and re-download the basic set")
	flags.BoolVar(&intelOnly, "intel-only", false, "Only make downloading intel updates mandatory")
}

var (
	updateCmd = &cobra.Command{
		Use:   "update",
		Short: "Run a manual update process",
		RunE: func(cmd *cobra.Command, args []string) error {
			return downloadUpdates()
		},
	}

	purgeCmd = &cobra.Command{
		Use:   "purge",
		Short: "Remove old resource versions that are superseded by at least three versions",
		RunE: func(cmd *cobra.Command, args []string) error {
			return purge()
		},
	}
)

func indexRequired(cmd *cobra.Command) bool {
	switch cmd {
	case updateCmd, purgeCmd:
		return true
	default:
		return false
	}
}

func downloadUpdates() error {
	// Check if only intel data is mandatory.
	if intelOnly {
		helper.IntelOnly()
	}

	// Set registry state notify callback.
	registry.StateNotifyFunc = logProgress

	// Set required updates.
	registry.MandatoryUpdates = helper.MandatoryUpdates()
	registry.AutoUnpack = helper.AutoUnpackUpdates()

	if reset {
		// Delete storage.
		err := os.RemoveAll(registry.StorageDir().Path)
		if err != nil {
			return fmt.Errorf("failed to reset update dir: %w", err)
		}
		err = registry.StorageDir().Ensure()
		if err != nil {
			return fmt.Errorf("failed to create update dir: %w", err)
		}

		// Reset registry resources.
		registry.ResetResources()
	}

	// Update all indexes.
	err := registry.UpdateIndexes(context.TODO())
	if err != nil {
		return err
	}

	// Check if updates are available.
	if len(registry.GetState().Updates.PendingDownload) == 0 {
		log.Println("all resources are up to date")
		return nil
	}

	// Download all required updates.
	err = registry.DownloadUpdates(context.TODO(), true)
	if err != nil {
		return err
	}

	// Select versions and unpack the selected.
	registry.SelectVersions()
	err = registry.UnpackResources()
	if err != nil {
		return fmt.Errorf("failed to unpack resources: %w", err)
	}

	if !intelOnly {
		// Fix chrome-sandbox permissions
		if err := helper.EnsureChromeSandboxPermissions(registry); err != nil {
			return fmt.Errorf("failed to fix electron permissions: %w", err)
		}
	}

	return nil
}

func logProgress(state *updater.RegistryState) {
	switch state.ID {
	case updater.StateChecking:
		if state.Updates.LastCheckAt == nil {
			log.Println("checking for new versions")
		}
	case updater.StateDownloading:
		if state.Details == nil {
			log.Printf("downloading %d updates\n", len(state.Updates.PendingDownload))
		} else if downloadDetails, ok := state.Details.(*updater.StateDownloadingDetails); ok {
			if downloadDetails.FinishedUpTo < len(downloadDetails.Resources) {
				log.Printf(
					"[%d/%d] downloading %s",
					downloadDetails.FinishedUpTo+1,
					len(downloadDetails.Resources),
					downloadDetails.Resources[downloadDetails.FinishedUpTo],
				)
			} else if state.Updates.LastDownloadAt == nil {
				log.Println("finalizing downloads")
			}
		}
	}
}

func purge() error {
	portlog.SetLogLevel(portlog.TraceLevel)

	// logging is configured as a persistent pre-run method inherited from
	// the root command but since we don't use run.Run() we need to start
	// logging ourself.
	err := portlog.Start()
	if err != nil {
		fmt.Printf("failed to start logging: %s\n", err)
	}
	defer portlog.Shutdown()

	registry.Purge(3)
	return nil
}
