package cli

import (
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"sync"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/spf13/afero"
	"github.com/urfave/cli/v2"
	"sneak.berlin/go/mfer/internal/log"
	"sneak.berlin/go/mfer/mfer"
)

func (mfa *CLIApp) generateManifestOperation(ctx *cli.Context) error {
	log.Debug("generateManifestOperation()")

	opts := &mfer.ScannerOptions{
		IncludeDotfiles: ctx.Bool("IncludeDotfiles"),
		FollowSymLinks:  ctx.Bool("FollowSymLinks"),
		Fs:              mfa.Fs,
	}

	// Set up signing options if sign-key is provided
	if signKey := ctx.String("sign-key"); signKey != "" {
		opts.SigningOptions = &mfer.SigningOptions{
			KeyID: mfer.GPGKeyID(signKey),
		}
		log.Infof("signing manifest with GPG key: %s", signKey)
	}

	s := mfer.NewScannerWithOptions(opts)

	// Phase 1: Enumeration - collect paths and stat files
	args := ctx.Args()
	showProgress := ctx.Bool("progress")

	// Set up enumeration progress reporting
	var enumProgress chan mfer.EnumerateStatus
	var enumWg sync.WaitGroup
	if showProgress {
		enumProgress = make(chan mfer.EnumerateStatus, 1)
		enumWg.Add(1)
		go func() {
			defer enumWg.Done()
			for status := range enumProgress {
				log.Progressf("Enumerating: %d files, %s",
					status.FilesFound,
					humanize.IBytes(uint64(status.BytesFound)))
			}
			log.ProgressDone()
		}()
	}

	if args.Len() == 0 {
		// Default to current directory
		if err := s.EnumeratePath(".", enumProgress); err != nil {
			return err
		}
	} else {
		// Collect and validate all paths first
		paths := make([]string, 0, args.Len())
		for i := 0; i < args.Len(); i++ {
			inputPath := args.Get(i)
			ap, err := filepath.Abs(inputPath)
			if err != nil {
				return err
			}
			// Validate path exists before adding to list
			if exists, _ := afero.Exists(mfa.Fs, ap); !exists {
				return fmt.Errorf("path does not exist: %s", inputPath)
			}
			log.Debugf("enumerating path: %s", ap)
			paths = append(paths, ap)
		}
		if err := s.EnumeratePaths(enumProgress, paths...); err != nil {
			return err
		}
	}
	enumWg.Wait()

	log.Infof("enumerated %d files, %s total", s.FileCount(), humanize.IBytes(uint64(s.TotalBytes())))

	// Check if output file exists
	outputPath := ctx.String("output")
	if exists, _ := afero.Exists(mfa.Fs, outputPath); exists {
		if !ctx.Bool("force") {
			return fmt.Errorf("output file %s already exists (use --force to overwrite)", outputPath)
		}
	}

	// Create temp file for atomic write
	tmpPath := outputPath + ".tmp"
	outFile, err := mfa.Fs.Create(tmpPath)
	if err != nil {
		return fmt.Errorf("failed to create temp file: %w", err)
	}

	// Set up signal handler to clean up temp file on Ctrl-C
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
	go func() {
		sig, ok := <-sigChan
		if !ok || sig == nil {
			return // Channel closed normally, not a signal
		}
		_ = outFile.Close()
		_ = mfa.Fs.Remove(tmpPath)
		os.Exit(1)
	}()

	// Clean up temp file on any error or interruption
	success := false
	defer func() {
		signal.Stop(sigChan)
		close(sigChan)
		_ = outFile.Close()
		if !success {
			_ = mfa.Fs.Remove(tmpPath)
		}
	}()

	// Phase 2: Scan - read file contents and generate manifest
	var scanProgress chan mfer.ScanStatus
	var scanWg sync.WaitGroup
	if showProgress {
		scanProgress = make(chan mfer.ScanStatus, 1)
		scanWg.Add(1)
		go func() {
			defer scanWg.Done()
			for status := range scanProgress {
				if status.ETA > 0 {
					log.Progressf("Scanning: %d/%d files, %s/s, ETA %s",
						status.ScannedFiles,
						status.TotalFiles,
						humanize.IBytes(uint64(status.BytesPerSec)),
						status.ETA.Round(time.Second))
				} else {
					log.Progressf("Scanning: %d/%d files, %s/s",
						status.ScannedFiles,
						status.TotalFiles,
						humanize.IBytes(uint64(status.BytesPerSec)))
				}
			}
			log.ProgressDone()
		}()
	}

	err = s.ToManifest(ctx.Context, outFile, scanProgress)
	scanWg.Wait()
	if err != nil {
		return fmt.Errorf("failed to generate manifest: %w", err)
	}

	// Close file before rename to ensure all data is flushed
	if err := outFile.Close(); err != nil {
		return fmt.Errorf("failed to close temp file: %w", err)
	}

	// Atomic rename
	if err := mfa.Fs.Rename(tmpPath, outputPath); err != nil {
		return fmt.Errorf("failed to rename temp file: %w", err)
	}

	success = true

	elapsed := time.Since(mfa.startupTime).Seconds()
	rate := float64(s.TotalBytes()) / elapsed
	log.Infof("wrote %d files (%s) to %s in %.1fs (%s/s)", s.FileCount(), humanize.IBytes(uint64(s.TotalBytes())), outputPath, elapsed, humanize.IBytes(uint64(rate)))

	return nil
}
