package main

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

// runGuided is what happens when somebody double-clicks the program instead of
// typing its name at a prompt.
//
// Without this, Explorer opens a console, main() finds no arguments, prints
// the usage text to stderr and exits — and Windows destroys the window in the
// same instant. From the other side of the screen that is indistinguishable
// from a crash. So when we know we were double-clicked, we ask the one
// question the program actually needs and stay on screen until the reader is
// done.
//
// This path is entered ONLY when there are no arguments and both ends of the
// program are a real console. Any scripted or piped use takes exactly the same
// code path it always did.
//
// Of trafficpilot's three commands, the guided session runs "report", which
// reads a traffic log and finishes. It deliberately does not start the proxy
// or the live watch: both are listeners that run until somebody stops them,
// and a double-clicked window that appears to hang — with a network port
// quietly open behind it — is a worse first impression than the crash this
// session exists to fix. "report" answers the actual question, prints, and
// returns.
func runGuided() {
	in := bufio.NewScanner(os.Stdin)

	fmt.Println()
	fmt.Println("  TrafficPilot")
	fmt.Println("  Shows where your bandwidth actually went, host by host.")
	fmt.Println()
	fmt.Println("  While it is running as a proxy it writes down every site a program")
	fmt.Println("  talks to and how many bytes each one cost. This window reads that")
	fmt.Println("  record and adds it up: the busiest hosts, their share of the total,")
	fmt.Println("  and how long their requests took.")
	fmt.Println()
	fmt.Println("  It only reads the log file. No connection is made and nothing on")
	fmt.Println("  your machine is changed.")
	fmt.Println()

	suggested := suggestedLog()
	if suggested == "" {
		fmt.Println("  I could not find a traffic log lying about, so you will need to")
		fmt.Println("  tell me where yours is. A log is written by the proxy: start it")
		fmt.Println("  from a command prompt with")
		fmt.Println()
		fmt.Println("      trafficpilot proxy --listen 127.0.0.1:8080 --log traffic.jsonl")
		fmt.Println()
		fmt.Println("  point a program's proxy setting at 127.0.0.1:8080, use it for a")
		fmt.Println("  while, then come back here.")
		fmt.Println()
	}

	for {
		fmt.Println("  Which traffic log shall I add up?")
		fmt.Println("  (the .jsonl file the proxy was told to write)")
		if suggested != "" {
			fmt.Printf("  (press Enter for %s)\n", suggested)
		}
		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 traffic log to read. 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("  Tip: you can drag the log file — or the folder holding it —")
			fmt.Println("  from Explorer onto this window to paste its location, then")
			fmt.Println("  press Enter.")
			fmt.Println()
			continue
		case info.IsDir():
			// A dragged-in folder is a common answer; look inside it rather
			// than sending the reader away to find the file themselves.
			found := logIn(answer)
			if found == "" {
				fmt.Println()
				fmt.Printf("  %q is a folder, and I cannot see a traffic log in it.\n", answer)
				fmt.Println("  Give me the .jsonl file itself.")
				fmt.Println()
				continue
			}
			fmt.Printf("\n  Using the log I found in that folder: %s\n", found)
			answer = found
		case info.Size() == 0:
			fmt.Println()
			fmt.Printf("  %s is empty: the proxy has not recorded any traffic yet.\n", answer)
			fmt.Println("  Point a program at the proxy, use it for a minute, then try")
			fmt.Println("  again.")
			fmt.Println()
			continue
		}

		fmt.Println()
		if err := cmdReport([]string{"--log", answer}); err != nil {
			fmt.Println()
			fmt.Printf("  I could not read %q as a traffic log.\n", answer)
			fmt.Printf("  (%v)\n", err)
			fmt.Println("  A traffic log is the .jsonl file written by `trafficpilot proxy`.")
			fmt.Println()
			continue
		}
		break
	}

	fmt.Println()
	fmt.Println("  Done. Nothing was changed — this only read the log.")
	fmt.Println("  There is a command-line version too, which runs the proxy that")
	fmt.Println("  writes these logs and can watch one live: trafficpilot --help")
	pause(in)
}

// suggestedLog offers a traffic log the reader plausibly has in mind, so the
// common case is one keypress. It returns "" when there is nothing to offer,
// and the prompt says so plainly and explains where a log comes from.
func suggestedLog() string {
	var dirs []string
	if wd, err := os.Getwd(); err == nil {
		dirs = append(dirs, wd)
	}
	if home, err := os.UserHomeDir(); err == nil {
		dirs = append(dirs, home,
			filepath.Join(home, "Documents"),
			filepath.Join(home, "Downloads"),
			filepath.Join(home, "Desktop"))
	}
	for _, dir := range dirs {
		if found := logIn(dir); found != "" {
			return found
		}
	}
	return ""
}

// logIn returns the most recently modified file directly inside dir whose first
// record really is a traffic event, or "" if there is none. Checking the
// content rather than the name keeps the offered default from being some
// unrelated .jsonl file, and only the top level is searched: a guided default
// should be instant, not a disk crawl.
func logIn(dir string) string {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return ""
	}
	type candidate struct {
		path  string
		mtime int64
	}
	var found []candidate
	for _, e := range entries {
		name := strings.ToLower(e.Name())
		if e.IsDir() || (!strings.HasSuffix(name, ".jsonl") && !strings.HasSuffix(name, ".log")) {
			continue
		}
		info, err := e.Info()
		if err != nil || info.Size() == 0 {
			continue
		}
		found = append(found, candidate{filepath.Join(dir, e.Name()), info.ModTime().UnixNano()})
	}
	sort.Slice(found, func(i, j int) bool { return found[i].mtime > found[j].mtime })
	for _, c := range found {
		if looksLikeTrafficLog(c.path) {
			return c.path
		}
	}
	return ""
}

// looksLikeTrafficLog reads just the first record and asks the log parser
// whether it is a traffic event, so an offered default is one that will
// actually produce a report.
func looksLikeTrafficLog(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()
	sc := bufio.NewScanner(f)
	sc.Buffer(make([]byte, 0, 64*1024), 1<<20)
	for n := 1; n <= 5 && sc.Scan(); n++ {
		line := strings.TrimSpace(sc.Text())
		if line == "" {
			continue
		}
		e, note := parseLine(n, line)
		return note == nil && e.Host != ""
	}
	return false
}

// 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()
}
