package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// runGuided is what happens when somebody double-clicks CleanInstall instead
// of typing its name at a prompt.
//
// Explorer opens a console, runs the program with no arguments, and destroys
// the window the instant the process exits — so printing usage and quitting
// looks exactly like a crash. When we know we were double-clicked we ask the
// one thing CleanInstall needs and stay on screen until the reader is done.
//
// The guided session runs the read-only scan and only that. It reports what
// each cleanup policy currently matches; it never moves a file into
// quarantine, whatever it finds. Quarantining is a separate, deliberate
// command-line step where the operator has to name a quarantine folder and an
// audit ledger, and ask for it explicitly.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  CleanInstall")
	fmt.Println("  See what your cleanup policies would sweep up — before anything moves.")
	fmt.Println()
	fmt.Println("  A policy file lists the folders you want kept tidy and the kinds of")
	fmt.Println("  leftover files that may go, such as temporary and backup files older")
	fmt.Println("  than a given age. Give me that file and I will check each policy and")
	fmt.Println("  show you exactly what it matches right now.")
	fmt.Println()
	fmt.Println("  This is a report only. Nothing is moved, changed or deleted.")
	fmt.Println()

	suggested := suggestedPolicyFile()
	for {
		fmt.Println("  Where is your policy file?")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		} else {
			fmt.Println("  (it is a .json file — the one you or your admin wrote)")
		}
		fmt.Println("  Tip: you can drag the file, or the folder it lives in, from Explorer")
		fmt.Println("  onto this window to paste its location, then press Enter.")
		fmt.Print("  > ")

		if !in.Scan() {
			// stdin closed on us; there is nothing sensible left to ask.
			return
		}
		answer := strings.TrimSpace(in.Text())
		answer = strings.Trim(answer, `"`)
		if answer == "" {
			answer = suggested
		}
		if answer == "" {
			fmt.Println()
			fmt.Println("  I need a policy file to work from. Try again, or close this window.")
			fmt.Println()
			continue
		}

		info, err := os.Stat(answer)
		switch {
		case err != nil:
			fmt.Println()
			fmt.Printf("  I cannot find %q.\n", answer)
			fmt.Println()
			continue
		case info.IsDir():
			// A dragged folder is a common and easily fixed mistake: look for
			// the obvious file inside it before complaining.
			inside := filepath.Join(answer, "policies.json")
			if fi, err := os.Stat(inside); err == nil && !fi.IsDir() {
				fmt.Println()
				fmt.Printf("  That is a folder — I will use %s inside it.\n", inside)
				answer = inside
				break // leaves the switch, and carries on with the file below
			}
			fmt.Println()
			fmt.Printf("  %q is a folder. I need the policy file itself, which ends in .json.\n", answer)
			fmt.Println()
			continue
		}

		cfg, err := loadPolicyConfig(answer)
		if err != nil {
			fmt.Println()
			fmt.Printf("  I could not read that as a policy file.\n")
			fmt.Printf("  %v\n", err)
			fmt.Println()
			continue
		}

		fmt.Println()
		fmt.Printf("  %s lists %d %s. Checking each one.\n", answer, len(cfg.Policies), plural(len(cfg.Policies), "policy", "policies"))
		fmt.Println()
		reportPolicies(cfg)
		break
	}

	fmt.Println()
	fmt.Println("  Done. Nothing above was touched — that is a list of what each policy")
	fmt.Println("  currently matches, and that is all this session does.")
	fmt.Println("  Actually moving those files into a quarantine folder, with every")
	fmt.Println("  file recorded in a permanent ledger, is a separate command-line step:")
	fmt.Println("  run cleaninstall --help to see it. Even then CleanInstall never")
	fmt.Println("  deletes anything; it moves files aside so you can put them back.")
	pause(in)
}

// reportPolicies prints, for every policy in the file, exactly what it matches
// today. This is the same read-only work `cleaninstall scan` does, one policy
// after another, and it never touches a file it lists.
func reportPolicies(cfg *policyConfig) {
	var grandTotal int64
	var grandFiles int
	for i := range cfg.Policies {
		p := &cfg.Policies[i]
		fmt.Printf("  Policy %q — %s\n", p.Name, p.TargetDir)

		if err := checkDir(p.TargetDir); err != nil {
			fmt.Printf("    Skipped: %v\n\n", err)
			continue
		}
		patterns, maxAge, hasMaxAge, err := p.resolve()
		if err != nil {
			fmt.Printf("    Skipped: %v\n\n", err)
			continue
		}
		matches, err := findMatches(p.TargetDir, patterns, maxAge, hasMaxAge)
		if err != nil {
			fmt.Printf("    Could not read that folder: %v\n\n", err)
			continue
		}
		if len(matches) == 0 {
			fmt.Println("    Nothing matched — that folder is already tidy.")
			fmt.Println()
			continue
		}
		total := printCandidates(matches)
		fmt.Printf("    %d %s, %s in total.\n\n", len(matches), plural(len(matches), "file", "files"), humanBytes(total))
		grandTotal += total
		grandFiles += len(matches)
	}
	fmt.Printf("  Across every policy: %d %s, %s.\n", grandFiles, plural(grandFiles, "file", "files"), humanBytes(grandTotal))
}

// plural picks the right word for a count, so the report reads like English.
func plural(n int, one, many string) string {
	if n == 1 {
		return one
	}
	return many
}

// suggestedPolicyFile offers a policy file the reader can accept with one
// keypress: the obvious one next to the program itself, or in the folder the
// console opened in. It returns "" when there is no such file, and the prompt
// copes with that.
func suggestedPolicyFile() string {
	var dirs []string
	if exe, err := os.Executable(); err == nil {
		dirs = append(dirs, filepath.Dir(exe))
	}
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	for _, dir := range dirs {
		for _, name := range []string{"policies.json", "cleaninstall.json"} {
			candidate := filepath.Join(dir, name)
			if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
				return candidate
			}
		}
	}
	return ""
}

// pause keeps the console window open. Explorer closes it the moment the
// process exits, so without this the reader never sees the output.
func pause(in *bufio.Scanner) {
	fmt.Println()
	fmt.Print("  Press Enter to close this window. ")
	in.Scan()
}
