package cli

import (
	"fmt"
	"path/filepath"
	"sync"
	"time"

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

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

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

	s := scanner.NewWithOptions(opts)

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

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

	if args.Len() == 0 {
		// Default to current directory
		if err := s.EnumeratePath(".", enumProgress); err != nil {
			return err
		}
	} else {
		// Collect all paths first
		paths := make([]string, 0, args.Len())
		for i := 0; i < args.Len(); i++ {
			ap, err := filepath.Abs(args.Get(i))
			if err != nil {
				return err
			}
			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, %d bytes total", s.FileCount(), 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)
		}
	}

	// Open output file
	outFile, err := mfa.Fs.Create(outputPath)
	if err != nil {
		return fmt.Errorf("failed to create output file: %w", err)
	}
	defer func() { _ = outFile.Close() }()

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

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

	if !ctx.Bool("quiet") {
		elapsed := time.Since(mfa.startupTime).Seconds()
		rate := float64(s.TotalBytes()) / elapsed / 1e6
		log.Infof("wrote %d files (%.1f MB) to %s in %.1fs (%.1f MB/s)", s.FileCount(), float64(s.TotalBytes())/1e6, outputPath, elapsed, rate)
	}

	return nil
}
