package main

import (
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/gmgauthier/gostations/internal/radio"
)

func TestShowVersion_Unit(t *testing.T) {
	t.Parallel()
	t.Log("✓ Fast showVersion unit test")

	tests := []struct {
		name     string
		version  string
		expected string
	}{
		{"default version", "1.0.0", "1.0.0\n"},
		{"empty version", "", "\n"},
		{"dev version", "dev", "dev\n"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			originalVersion := version
			version = tt.version
			defer func() { version = originalVersion }()

			// Safe stdout capture using pipe (no os.Stdout reassignment)
			origStdout := os.Stdout
			r, w, _ := os.Pipe()
			os.Stdout = w

			showVersion()

			w.Close()
			os.Stdout = origStdout

			var buf bytes.Buffer
			_, _ = buf.ReadFrom(r)
			got := buf.String()

			if got != tt.expected {
				t.Errorf("showVersion() = %q, want %q", got, tt.expected)
			}
		})
	}
}

func TestPrecheck_Unit(t *testing.T) {
	t.Parallel()
	t.Log("✓ Fast precheck unit test")

	// Simple smoke test — calls the real precheck() in the common "player installed" path
	// (no mocking of globals — that's not allowed in Go)
	precheck()
	t.Log("✓ precheck ran without panic (happy path)")
}

func TestPrecheck_Live(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping live integration test (use without -short)")
	}
	t.Log("🧪 Running live precheck integration test...")

	// Real precheck with whatever player is configured on this system
	precheck()
	t.Log("✓ Live precheck passed (no early exit)")
}

// TestFavDelByIndex_Integration exercises `fav list` numbering and `fav del N` (1-based, stable sort).
// Uses a temp XDG dir and a built binary so we can exec the subcommands as users would.
// Skips under -short (like other live integration tests).
func TestFavDelByIndex_Integration(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping live integration test for fav del by index (use without -short)")
	}
	t.Log("🧪 Running fav del-by-index integration test...")

	td := t.TempDir()
	bin := filepath.Join(td, "gostations-favtest")

	// Build a dedicated binary from current source (self-contained, no reliance on external /tmp state)
	buildCmd := exec.Command("go", "build", "-o", bin, ".")
	buildCmd.Dir = "."
	if out, err := buildCmd.CombinedOutput(); err != nil {
		t.Fatalf("failed to build test binary: %v\n%s", err, string(out))
	}

	xdg := filepath.Join(td, "xdg-home")
	favDir := filepath.Join(xdg, "gostations")
	if err := os.MkdirAll(favDir, 0750); err != nil {
		t.Fatalf("mkdir xdg: %v", err)
	}

	// Pre-populate a minimal config to suppress "config file missing" logs during config.Init in add/del
	ini := filepath.Join(favDir, "radiostations.ini")
	iniContent := `[DEFAULT]
radio_browser.api=all.api.radio-browser.info
player.command=mpv
player.options=--no-video
menu_items.max=50
`
	if err := os.WriteFile(ini, []byte(iniContent), 0644); err != nil {
		t.Fatalf("write ini: %v", err)
	}

	// Helper to invoke the subcommand with isolated XDG
	run := func(args ...string) (stdout, stderr string, exitErr error) {
		c := exec.Command(bin, args...)
		c.Env = append(os.Environ(), "XDG_CONFIG_HOME="+xdg)
		var so, se bytes.Buffer
		c.Stdout = &so
		c.Stderr = &se
		exitErr = c.Run()
		return so.String(), se.String(), exitErr
	}

	// Seed two favorites via direct URLs (no network). Names will be the URLs themselves.
	// After sort by Name (which == URL here) order will be a < b.
	if _, _, err := run("fav", "add", "http://ex.com/b"); err != nil {
		t.Fatalf("fav add b: %v", err)
	}
	if _, _, err := run("fav", "add", "http://ex.com/a"); err != nil {
		t.Fatalf("fav add a: %v", err)
	}

	// List should show stable 1-based numbering, sorted by name/URL
	listOut, _, err := run("fav", "list")
	if err != nil {
		t.Fatalf("fav list after adds: %v", err)
	}
	if !strings.Contains(listOut, "1) http://ex.com/a") || !strings.Contains(listOut, "2) http://ex.com/b") {
		t.Fatalf("expected numbered sorted list with 1=a 2=b, got:\n%s", listOut)
	}

	// Delete by index 1 (should remove the first in sorted order, i.e. "a")
	delOut, delErrOut, err := run("fav", "del", "1")
	if err != nil {
		t.Fatalf("fav del 1 failed: %v\nstderr: %s", err, delErrOut)
	}
	if !strings.Contains(delOut, "Removed from favorites: http://ex.com/a") {
		t.Fatalf("unexpected del output: %s", delOut)
	}

	// Re-list: only b remains, now at position 1
	listOut, _, err = run("fav", "list")
	if err != nil {
		t.Fatalf("fav list after del: %v", err)
	}
	if strings.Contains(listOut, "http://ex.com/a") {
		t.Errorf("a should have been deleted, list:\n%s", listOut)
	}
	if !strings.Contains(listOut, "1) http://ex.com/b") || strings.Contains(listOut, "2)") {
		t.Errorf("after deleting first, b should be renumbered to 1 only; got:\n%s", listOut)
	}

	// Out of range should error and not mutate
	_, delErrOut, err = run("fav", "del", "99")
	if err == nil {
		t.Error("expected non-zero exit for out-of-range del")
	}
	if !strings.Contains(delErrOut, "index 99 out of range (1-1)") {
		t.Errorf("expected range error message, got stderr: %s", delErrOut)
	}

	// Final list still has exactly the one item
	listOut, _, _ = run("fav", "list")
	if !strings.Contains(listOut, "1) http://ex.com/b") {
		t.Errorf("list mutated by bad del index? got:\n%s", listOut)
	}

	t.Log("✓ fav del-by-index integration test passed")
}

// TestSortedForDisplay_Unit verifies the stable Name-then-URL ordering used for fav list indices.
func TestSortedForDisplay_Unit(t *testing.T) {
	t.Parallel()

	in := []radio.Station{
		{Name: "Zeta", Url: "u3"},
		{Name: "Alpha", Url: "u2"},
		{Name: "Alpha", Url: "u1"}, // same name, lower URL should come first
		{Name: "Beta", Url: "u0"},
	}

	out := sortedForDisplay(in)

	if len(out) != 4 {
		t.Fatalf("len=%d", len(out))
	}
	// Expected order: Alpha/u1 , Alpha/u2 , Beta , Zeta
	if out[0].Name != "Alpha" || out[0].Url != "u1" {
		t.Errorf("pos0: want Alpha/u1 got %s/%s", out[0].Name, out[0].Url)
	}
	if out[1].Name != "Alpha" || out[1].Url != "u2" {
		t.Errorf("pos1: want Alpha/u2 got %s/%s", out[1].Name, out[1].Url)
	}
	if out[2].Name != "Beta" {
		t.Errorf("pos2: want Beta got %s", out[2].Name)
	}
	if out[3].Name != "Zeta" {
		t.Errorf("pos3: want Zeta got %s", out[3].Name)
	}

	// Ensure input slice not mutated (we copy)
	if in[0].Name != "Zeta" {
		t.Error("input was mutated")
	}
}
