package sat

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

// Both shapes of the same file: three lines per satellite, and the two-line
// form some feeds still serve. A reader that only understood one of them would
// come back empty from a mirror and look like a network fault.
func TestParseTLESetReadsBothShapes(t *testing.T) {
	three := issName + "\n" + issLine1 + "\n" + issLine2 + "\n"
	els, skipped, err := ParseTLESet(strings.NewReader(three))
	if err != nil || len(els) != 1 || skipped != 0 {
		t.Fatalf("three-line: %d sats, %d skipped, err %v", len(els), skipped, err)
	}
	if els[0].Name != issName {
		t.Errorf("name %q", els[0].Name)
	}

	two := issLine1 + "\n" + issLine2 + "\n"
	els, _, err = ParseTLESet(strings.NewReader(two))
	if err != nil || len(els) != 1 {
		t.Fatalf("two-line: %d sats, err %v", len(els), err)
	}
	if els[0].NORAD != 25544 {
		t.Errorf("a nameless entry lost its identity: %+v", els[0])
	}
}

// One bad satellite must not cost the operator the other hundred and
// ninety-nine — but the count of what was dropped has to come back, or a
// silently shorter list reads as a complete one.
func TestParseTLESetSkipsWhatItCannotRead(t *testing.T) {
	feed := strings.Join([]string{
		"JUNK SATELLITE",
		"1 99999U 00000A   24298.00000000  .00000000  00000+0  00000+0 0  0000", // bad checksum
		"2 99999  00.0000 000.0000 0000000 000.0000 000.0000 00.00000000000000",
		"",
		issName, issLine1, issLine2,
	}, "\n")
	els, skipped, err := ParseTLESet(strings.NewReader(feed))
	if err != nil {
		t.Fatalf("the whole feed was refused for one bad entry: %v", err)
	}
	if len(els) != 1 || els[0].Name != issName {
		t.Errorf("kept %d satellites: %+v", len(els), els)
	}
	if skipped != 1 {
		t.Errorf("skipped = %d, want 1 — a silently shorter list reads as a complete one", skipped)
	}
	// Nothing usable at all IS an error: an error page or a captive-portal login
	// parses as zero satellites, and that must never replace a good set.
	if _, _, err := ParseTLESet(strings.NewReader("<html>login required</html>")); err == nil {
		t.Error("an HTML error page was accepted as an element set")
	}
}

// The cache is what makes the first screen after a launch a full one — on a
// train, or on a shack PC with no internet.
func TestCacheRoundTrip(t *testing.T) {
	dir := t.TempDir()
	f := NewFetcher(dir)
	f.Logf = func(string, ...any) {}

	body := issName + "\n" + issLine1 + "\n" + issLine2 + "\n"
	if err := f.writeCache([]byte(body)); err != nil {
		t.Fatalf("write: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dir, CacheName)); err != nil {
		t.Fatalf("the cache file is not where an operator would look for it: %v", err)
	}
	// And no leftovers: the temp file is renamed, not copied.
	if _, err := os.Stat(filepath.Join(dir, CacheName+".tmp")); err == nil {
		t.Error("the half-written file was left behind")
	}

	els, at, err := f.LoadCache()
	if err != nil || len(els) != 1 {
		t.Fatalf("load: %d sats, err %v", len(els), err)
	}
	if at.IsZero() {
		t.Error("the cache has no age, so nothing can say whether to trust it")
	}
}
