package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"sort"
	"strconv"

	"github.com/gmgauthier/gostations/internal/config"
	"github.com/gmgauthier/gostations/internal/data"
	playerpkg "github.com/gmgauthier/gostations/internal/player"
	"github.com/gmgauthier/gostations/internal/radio"
	"github.com/gmgauthier/gostations/internal/ui"
)

var version string

func showVersion() {
	fmt.Println(version)
}

func precheck() {
	p := "mpv"
	if v, err := config.Get("player.command"); err == nil && v != "" {
		p = v
	}
	if !playerpkg.IsInstalled(p) {
		fmt.Printf("%s is either not installed, or not on your $PATH. Cannot continue.\n", p)
		os.Exit(1)
	}
}

// runFind implements the "find" subcommand for scripting / non-interactive lookup.
// Usage: gostations find [-c country] [-t tags] ... [-x] [-j]
func runFind(args []string) {
	fs := flag.NewFlagSet("find", flag.ExitOnError)
	var (
		name    string
		country string
		state   string
		tags    string
		notok   bool
		jsonOut bool
	)
	fs.StringVar(&name, "n", "", "Station name (or identifier).")
	fs.StringVar(&country, "c", "", "Home country.")
	fs.StringVar(&state, "s", "", "Home state (if in the United States).")
	fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)")
	fs.BoolVar(&notok, "x", false, "If toggled, will show stations that are down")
	fs.BoolVar(&jsonOut, "j", false, "Output as JSON array (for scripting)")
	fs.BoolVar(&jsonOut, "json", false, "Output as JSON array (for scripting)")

	fs.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: gostations find [options]\n\nOptions:\n")
		fs.PrintDefaults()
	}
	_ = fs.Parse(args)

	if err := config.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "config: %v\n", err)
		os.Exit(1)
	}

	stations, err := radio.Search(context.Background(), name, country, state, tags, notok)
	if err != nil {
		fmt.Fprintf(os.Stderr, "search: %v\n", err)
		os.Exit(1)
	}

	if jsonOut {
		b, _ := json.MarshalIndent(stations, "", "  ")
		fmt.Println(string(b))
		return
	}

	// human/script friendly list (similar to old output)
	fmt.Println("...Found Stations...")
	for i, s := range stations {
		listing := fmt.Sprintf("%-40s   %-5s %-5s %s", Short(s.Name, 40), s.Codec, s.Bitrate, s.Url)
		fmt.Printf("%d) %s\n", i+1, listing)
	}
	if len(stations) == 0 {
		fmt.Println("(no stations matched)")
	}
}

// runPlay implements the "play" subcommand for scripting direct playback.
// It accepts the same search flags or a direct URL as first positional arg.
// Plays the first match (or the URL) using the configured player and blocks.
func runPlay(args []string) {
	fs := flag.NewFlagSet("play", flag.ExitOnError)
	var (
		name    string
		country string
		state   string
		tags    string
		notok   bool
	)
	fs.StringVar(&name, "n", "", "Station name (or identifier).")
	fs.StringVar(&country, "c", "", "Home country.")
	fs.StringVar(&state, "s", "", "Home state (if in the United States).")
	fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)")
	fs.BoolVar(&notok, "x", false, "If toggled, will show stations that are down")

	fs.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: gostations play [options] [direct-url]\n\nOptions:\n")
		fs.PrintDefaults()
	}
	_ = fs.Parse(args)

	pos := fs.Args()

	if err := config.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "config: %v\n", err)
		os.Exit(1)
	}
	precheck()

	var playURL string
	if len(pos) > 0 {
		playURL = pos[0]
	} else {
		stations, err := radio.Search(context.Background(), name, country, state, tags, notok)
		if err != nil {
			fmt.Fprintf(os.Stderr, "search: %v\n", err)
			os.Exit(1)
		}
		if len(stations) == 0 {
			fmt.Fprintln(os.Stderr, "no stations found to play")
			os.Exit(1)
		}
		playURL = stations[0].Url
		fmt.Printf("Playing first match: %s\n", stations[0].Name)
	}

	// Use legacy for now (cleaned); will use richer player in TUI/IPC later.
	pname := "mpv"
	if v, err := config.Get("player.command"); err == nil && v != "" {
		pname = v
	}
	opts := ""
	if v, err := config.Get("player.options"); err == nil {
		opts = v
	}

	leg := playerpkg.NewLegacy(pname, opts)
	if err := leg.Play(playURL); err != nil {
		fmt.Fprintf(os.Stderr, "play error: %v\n", err)
		os.Exit(1)
	}
}

// runFav dispatches fav subcommands: list, add, del.
func runFav(args []string) {
	if len(args) == 0 {
		printFavHelp()
		return
	}
	switch args[0] {
	case "list":
		runFavList(args[1:])
	case "add":
		runFavAdd(args[1:])
	case "del", "delete", "rm", "remove":
		runFavDel(args[1:])
	case "-h", "--help", "help":
		printFavHelp()
	default:
		fmt.Fprintf(os.Stderr, "unknown fav subcommand: %s\n\n", args[0])
		printFavHelp()
		os.Exit(1)
	}
}

func printFavHelp() {
	fmt.Printf(`Usage: gostations fav <subcommand> [options]

Subcommands:
  list    List your favorites (supports -j/--json)
  add     Add a station (by search flags like find, or direct URL)
  del     Remove a station (by index from list, search flags, or direct URL)

Examples:
  gostations fav list
  gostations fav list -j
  gostations fav add -n "WFMT"
  gostations fav add http://stream.example.com/radio.mp3
  gostations fav del 3
  gostations fav del -n "WFMT"
  gostations fav del http://stream.example.com/radio.mp3

Search options for add/del are the same as for "find" / "play".
When deleting by index, the numbering matches the sorted order from "fav list".
`)
}

// sortedForDisplay returns a copy sorted by Name asc, then URL asc for stable 1-based indices in `fav list` / `fav del N`.
func sortedForDisplay(stations []radio.Station) []radio.Station {
	out := append([]radio.Station(nil), stations...)
	sort.Slice(out, func(i, j int) bool {
		if out[i].Name != out[j].Name {
			return out[i].Name < out[j].Name
		}
		return out[i].Url < out[j].Url
	})
	return out
}

// runFavList lists the current favorites.
func runFavList(args []string) {
	fs := flag.NewFlagSet("fav list", flag.ExitOnError)
	var jsonOut bool
	fs.BoolVar(&jsonOut, "j", false, "Output as JSON array (for scripting)")
	fs.BoolVar(&jsonOut, "json", false, "Output as JSON array (for scripting)")

	fs.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: gostations fav list [options]\n\nOptions:\n")
		fs.PrintDefaults()
	}
	_ = fs.Parse(args)

	favs, err := data.NewFavorites()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error loading favorites: %v\n", err)
		os.Exit(1)
	}

	list := sortedForDisplay(favs.List())

	if jsonOut {
		b, _ := json.MarshalIndent(list, "", "  ")
		fmt.Println(string(b))
		return
	}

	if len(list) == 0 {
		fmt.Println("(no favorites)")
		return
	}

	fmt.Println("Favorites:")
	for i, s := range list {
		listing := fmt.Sprintf("%-40s   %-5s %-5s %s", Short(s.Name, 40), s.Codec, s.Bitrate, s.Url)
		fmt.Printf("%d) %s\n", i+1, listing)
	}
}

// runFavAdd adds a station to favorites.
func runFavAdd(args []string) {
	fs := flag.NewFlagSet("fav add", flag.ExitOnError)
	var (
		name    string
		country string
		state   string
		tags    string
		notok   bool
	)
	fs.StringVar(&name, "n", "", "Station name (or identifier).")
	fs.StringVar(&country, "c", "", "Home country.")
	fs.StringVar(&state, "s", "", "Home state (if in the United States).")
	fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)")
	fs.BoolVar(&notok, "x", false, "If toggled, will show stations that are down")

	fs.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: gostations fav add [options] [direct-url]\n\nOptions:\n")
		fs.PrintDefaults()
	}
	_ = fs.Parse(args)

	pos := fs.Args()

	// no precheck or config needed for fav add, but call for consistency if search
	if err := config.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "config: %v\n", err)
		os.Exit(1)
	}

	var toAdd radio.Station
	if len(pos) > 0 {
		url := pos[0]
		toAdd = radio.Station{Url: url, Name: url}
	} else {
		stations, err := radio.Search(context.Background(), name, country, state, tags, notok)
		if err != nil {
			fmt.Fprintf(os.Stderr, "search: %v\n", err)
			os.Exit(1)
		}
		if len(stations) == 0 {
			fmt.Fprintln(os.Stderr, "no stations found to add")
			os.Exit(1)
		}
		toAdd = stations[0]
		fmt.Printf("Adding first match: %s\n", toAdd.Name)
	}

	favs, err := data.NewFavorites()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error loading favorites: %v\n", err)
		os.Exit(1)
	}
	favs.Add(toAdd)
	if err := favs.Save(); err != nil {
		fmt.Fprintf(os.Stderr, "error saving favorites: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Added to favorites: %s (%s)\n", toAdd.Name, toAdd.Url)
}

// runFavDel removes a station from favorites.
// Supports: index (1-based, matching `fav list` order), direct URL, or search flags.
func runFavDel(args []string) {
	fs := flag.NewFlagSet("fav del", flag.ExitOnError)
	var (
		name    string
		country string
		state   string
		tags    string
		notok   bool
	)
	fs.StringVar(&name, "n", "", "Station name (or identifier).")
	fs.StringVar(&country, "c", "", "Home country.")
	fs.StringVar(&state, "s", "", "Home state (if in the United States).")
	fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)")
	fs.BoolVar(&notok, "x", false, "If toggled, will show stations that are down")

	fs.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: gostations fav del [options] [index|direct-url]\n\nOptions:\n")
		fs.PrintDefaults()
	}
	_ = fs.Parse(args)

	pos := fs.Args()

	if err := config.Init(); err != nil {
		fmt.Fprintf(os.Stderr, "config: %v\n", err)
		os.Exit(1)
	}

	favs, err := data.NewFavorites()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error loading favorites: %v\n", err)
		os.Exit(1)
	}

	var urlToDel string
	var deletedName string

	if len(pos) > 0 {
		arg := pos[0]
		if idx, err := strconv.Atoi(arg); err == nil && idx > 0 {
			// Delete by 1-based index (matches sorted order from `fav list`)
			list := sortedForDisplay(favs.List())
			if idx > len(list) {
				fmt.Fprintf(os.Stderr, "index %d out of range (1-%d)\n", idx, len(list))
				os.Exit(1)
			}
			station := list[idx-1]
			urlToDel = station.Url
			deletedName = station.Name
		} else {
			// Treat as direct URL
			urlToDel = arg
			deletedName = arg
		}
	} else {
		// Use search flags to find station(s) to delete
		stations, err := radio.Search(context.Background(), name, country, state, tags, notok)
		if err != nil {
			fmt.Fprintf(os.Stderr, "search: %v\n", err)
			os.Exit(1)
		}
		if len(stations) == 0 {
			fmt.Fprintln(os.Stderr, "no stations found to delete")
			os.Exit(1)
		}
		urlToDel = stations[0].Url
		deletedName = stations[0].Name
		fmt.Printf("Deleting first match: %s\n", deletedName)
	}

	favs.Remove(urlToDel)
	if err := favs.Save(); err != nil {
		fmt.Fprintf(os.Stderr, "error saving favorites: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Removed from favorites: %s\n", deletedName)
}

func main() {
	// Early version / help for top level
	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "find":
			runFind(os.Args[2:])
			return
		case "play":
			runPlay(os.Args[2:])
			return
		case "fav":
			runFav(os.Args[2:])
			return
		case "-v", "--version", "version":
			showVersion()
			return
		case "-h", "--help", "help":
			printTopHelp()
			return
		}
	}

	// Common flags (used for TUI seeding or legacy)
	var (
		name    string
		country string
		state   string
		tags    string
		notok   bool
		legacy  bool
		version bool
	)

	fs := flag.NewFlagSet("gostations", flag.ExitOnError)
	fs.StringVar(&name, "n", "", "Station name (or identifier).")
	fs.StringVar(&country, "c", "", "Home country.")
	fs.StringVar(&state, "s", "", "Home state (if in the United States).")
	fs.StringVar(&tags, "t", "", "Tag (or comma-separated tag list)")
	fs.BoolVar(&notok, "x", false, "If toggled, will show stations that are down")
	fs.BoolVar(&legacy, "legacy", false, "Force old wmenu UI (kept until new TUI is perfect)")
	fs.BoolVar(&version, "v", false, "Show version.")

	fs.Usage = printTopHelp

	_ = fs.Parse(os.Args[1:])

	if version {
		showVersion()
		return
	}

	precheck()

	if err := config.Init(); err != nil {
		fmt.Printf("config init: %v\n", err)
		os.Exit(1)
	}

	var stations []radio.Station
	var loadErr error
	searchParamsProvided := name != "" || country != "" || state != "" || tags != "" || notok
	if searchParamsProvided {
		// CLI flags specify a search: use them (targeted lookup)
		stations, loadErr = radio.Search(context.Background(), name, country, state, tags, notok)
		if loadErr != nil {
			fmt.Printf("warning: station search: %v\n", loadErr)
			stations = nil
		}
	} else {
		// No explicit search params: decide based on favorites (per user request)
		favs, ferr := data.NewFavorites()
		if ferr == nil && favs != nil && len(favs.List()) > 0 {
			stations = favs.List()
		} else {
			// no favorites: do the default broad lookup (pruned)
			stations, loadErr = radio.Search(context.Background(), "", "", "", "", false)
			if loadErr != nil {
				fmt.Printf("warning: default station load: %v\n", loadErr)
				stations = nil
			}
		}
	}

	if legacy {
		// Old wmenu path (gated)
		menu := RadioMenu(stations)
		if err := menu.Run(); err != nil {
			fmt.Println(err.Error())
			os.Exit(1)
		}
		return
	}

	// Default: new TUI (Bubble Tea)
	if err := ui.Run(stations); err != nil {
		fmt.Printf("TUI error: %v\n", err)
		os.Exit(1)
	}
}

func printTopHelp() {
	fmt.Printf(`Usage:
  gostations [options]                 # Launch interactive TUI (default: your Favorites if any, else a default set of stations)
  gostations find [options]            # Non-interactive station search (scripting)
  gostations play [options] [url]      # Direct playback (scripting)
  gostations fav ...                   # Manage favorites (list / add / del)
  gostations -h | --help
  gostations -v | --version

Global options:
  -n string   Station name (or identifier).
  -c string   Home country.
  -s string   Home state (if in the United States).
  -t string   Tag (or comma-separated tag list)
  -x          If toggled, will show stations that are down
  -v          Show version.
  --legacy    Force old wmenu UI (temporary)

Subcommand examples:
  gostations find -c "United Kingdom" -t "news" -j
  gostations play -c "Gambia"
  gostations play http://stream.example.com/radio.mp3
  gostations fav list
  gostations fav add -n "WFMT"
  gostations fav del 3
  gostations fav del http://stream.example.com/radio.mp3
  gostations fav list -j

Initial TUI view:
  - If you have saved favorites: shows your Favorites (starred).
  - Otherwise: performs a default station lookup (broad results).
  - In either case, use / to filter the current list, or type a term and press ENTER while filtering to perform a fresh server-side search/lookup (replaces the list with new results; overlapping favorites get ★).

CLI favorites management (fav subcommand):
  - gostations fav list                (sorted output, supports -j)
  - gostations fav add / del           (support index from list, search flags like -n, or direct URL)

Old top-level behavior without subcommand now defaults to the new TUI.
Use --legacy to force the classic wmenu flow.
`)
}
