package resolve

import (
	"os"
	"path/filepath"
	"slices"
	"strings"
	"testing"
)

func TestCollectAudioFilesSingleFile(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "song.mp3")
	if err := os.WriteFile(path, []byte(""), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	got, err := CollectAudioFiles(path)
	if err != nil {
		t.Fatalf("CollectAudioFiles: %v", err)
	}
	if len(got) != 1 || got[0] != path {
		t.Errorf("got %v, want [%s]", got, path)
	}
}

func TestCollectAudioFilesUnsupportedExt(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "notes.txt")
	if err := os.WriteFile(path, []byte(""), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	got, err := CollectAudioFiles(path)
	if err != nil {
		t.Fatalf("CollectAudioFiles: %v", err)
	}
	if got != nil {
		t.Errorf("non-audio file should return nil, got %v", got)
	}
}

func TestCollectAudioFilesDirectoryWalk(t *testing.T) {
	dir := t.TempDir()
	paths := []string{
		filepath.Join(dir, "a.mp3"),
		filepath.Join(dir, "nested", "b.flac"),
		filepath.Join(dir, "README.txt"), // ignored
		filepath.Join(dir, "nested", "c.wav"),
	}
	for _, p := range paths {
		if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
			t.Fatalf("MkdirAll: %v", err)
		}
		if err := os.WriteFile(p, []byte(""), 0o644); err != nil {
			t.Fatalf("WriteFile: %v", err)
		}
	}

	got, err := CollectAudioFiles(dir)
	if err != nil {
		t.Fatalf("CollectAudioFiles: %v", err)
	}
	// Expect 3 audio files, sorted.
	if len(got) != 3 {
		t.Fatalf("got %d files, want 3 — paths=%v", len(got), got)
	}
	// slices.IsSorted confirms deterministic order.
	if !slices.IsSorted(got) {
		t.Errorf("files not sorted: %v", got)
	}
	// README should not be in the result.
	for _, p := range got {
		if strings.HasSuffix(p, "README.txt") {
			t.Errorf("README included in audio files: %v", got)
		}
	}
}

func TestCollectAudioFilesMissing(t *testing.T) {
	_, err := CollectAudioFiles(filepath.Join(t.TempDir(), "nonexistent"))
	if err == nil {
		t.Error("CollectAudioFiles on missing path should error")
	}
}

func TestArgsLocalFile(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "song.mp3")
	if err := os.WriteFile(path, []byte(""), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	got, err := Args([]string{path})
	if err != nil {
		t.Fatalf("Args: %v", err)
	}
	if len(got.Tracks) != 1 {
		t.Fatalf("got %d tracks, want 1", len(got.Tracks))
	}
	if got.Tracks[0].Path != path {
		t.Errorf("Path = %q, want %q", got.Tracks[0].Path, path)
	}
	if len(got.Pending) != 0 {
		t.Errorf("Pending = %v, want empty", got.Pending)
	}
}

func TestArgsLocalDirectory(t *testing.T) {
	dir := t.TempDir()
	files := []string{"a.mp3", "b.flac", "c.ogg"}
	for _, f := range files {
		p := filepath.Join(dir, f)
		if err := os.WriteFile(p, []byte(""), 0o644); err != nil {
			t.Fatalf("WriteFile: %v", err)
		}
	}

	got, err := Args([]string{dir})
	if err != nil {
		t.Fatalf("Args: %v", err)
	}
	if len(got.Tracks) != 3 {
		t.Fatalf("got %d tracks, want 3", len(got.Tracks))
	}
	// Results preserve on-disk sort order (CollectAudioFiles sorts).
	names := make([]string, len(got.Tracks))
	for i, tr := range got.Tracks {
		names[i] = filepath.Base(tr.Path)
	}
	if !slices.IsSorted(names) {
		t.Errorf("tracks not sorted: %v", names)
	}
}

func TestArgsRemoteFeedURLGoesToPending(t *testing.T) {
	// .xml URL is recognised as a feed.
	feedURL := "https://example.com/podcast.xml"
	got, err := Args([]string{feedURL})
	if err != nil {
		t.Fatalf("Args: %v", err)
	}
	if len(got.Pending) != 1 || got.Pending[0] != feedURL {
		t.Errorf("Pending = %v, want [%s]", got.Pending, feedURL)
	}
	if len(got.Tracks) != 0 {
		t.Errorf("Tracks should be empty, got %+v", got.Tracks)
	}
}

func TestArgsRemoteAudioURLBecomesTrack(t *testing.T) {
	audioURL := "https://stream.example.com/song.mp3"
	got, err := Args([]string{audioURL})
	if err != nil {
		t.Fatalf("Args: %v", err)
	}
	if len(got.Tracks) != 1 {
		t.Fatalf("got %d tracks, want 1", len(got.Tracks))
	}
	if got.Tracks[0].Path != audioURL {
		t.Errorf("Path = %q, want %q", got.Tracks[0].Path, audioURL)
	}
}

func TestArgsLocalM3U(t *testing.T) {
	dir := t.TempDir()
	// Write a local m3u that points at a dummy audio file.
	audio := filepath.Join(dir, "song.mp3")
	if err := os.WriteFile(audio, []byte(""), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}
	m3u := filepath.Join(dir, "pl.m3u")
	content := `#EXTM3U
#EXTINF:120,My Song
song.mp3
`
	if err := os.WriteFile(m3u, []byte(content), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	got, err := Args([]string{m3u})
	if err != nil {
		t.Fatalf("Args: %v", err)
	}
	if len(got.Tracks) != 1 {
		t.Fatalf("got %d tracks, want 1", len(got.Tracks))
	}
	if got.Tracks[0].Title != "My Song" {
		t.Errorf("Title = %q, want My Song", got.Tracks[0].Title)
	}
}

func TestArgsLocalPLS(t *testing.T) {
	dir := t.TempDir()
	audio := filepath.Join(dir, "song.mp3")
	if err := os.WriteFile(audio, []byte(""), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}
	pls := filepath.Join(dir, "pl.pls")
	content := "[playlist]\nFile1=" + audio + "\nTitle1=Track 1\nLength1=60\nNumberOfEntries=1\nVersion=2\n"
	if err := os.WriteFile(pls, []byte(content), 0o644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	got, err := Args([]string{pls})
	if err != nil {
		t.Fatalf("Args: %v", err)
	}
	if len(got.Tracks) != 1 {
		t.Fatalf("got %d tracks, want 1", len(got.Tracks))
	}
	if got.Tracks[0].Title != "Track 1" {
		t.Errorf("Title = %q, want Track 1", got.Tracks[0].Title)
	}
}

func TestArgsMissingFileErrors(t *testing.T) {
	// CollectAudioFiles stat's the path and Args wraps the os.Stat failure
	// into "scanning X: ..." — missing paths are surfaced, not swallowed.
	path := filepath.Join(t.TempDir(), "ghost.mp3")
	_, err := Args([]string{path})
	if err == nil {
		t.Error("Args for missing file should error")
	}
	if !strings.Contains(err.Error(), "scanning") {
		t.Errorf("error = %q, want to mention 'scanning'", err.Error())
	}
}

func TestScanTracksEmpty(t *testing.T) {
	got := scanTracks(nil)
	if got != nil {
		t.Errorf("scanTracks(nil) = %v, want nil", got)
	}
	got = scanTracks([]string{})
	if got != nil {
		t.Errorf("scanTracks(empty) = %v, want nil", got)
	}
}

func TestScanTracksPreservesOrder(t *testing.T) {
	dir := t.TempDir()
	paths := make([]string, 12) // more than workers = 8
	for i := range paths {
		paths[i] = filepath.Join(dir, "a.mp3")
	}

	tracks := scanTracks(paths)
	if len(tracks) != len(paths) {
		t.Fatalf("len = %d, want %d", len(tracks), len(paths))
	}
	for i, tr := range tracks {
		if tr.Path != paths[i] {
			t.Errorf("tracks[%d].Path = %q, want %q", i, tr.Path, paths[i])
		}
	}
}
