package astro_test

import (
	"encoding/json"
	"math"
	"os"
	"testing"

	"b612.me/astro/basic"
	"b612.me/astro/planet"
)

type lowPrecisionSunSnapshot struct {
	LoBits          uint64 `json:"lo_bits"`
	MBits           uint64 `json:"m_bits"`
	EccBits         uint64 `json:"ecc_bits"`
	PeriBits        uint64 `json:"peri_bits"`
	MidBits         uint64 `json:"mid_bits"`
	TrueLoBits      uint64 `json:"true_lo_bits"`
	ApparentLoBits  uint64 `json:"apparent_lo_bits"`
	ApparentRaBits  uint64 `json:"apparent_ra_bits"`
	ApparentDecBits uint64 `json:"apparent_dec_bits"`
	TrueRaBits      uint64 `json:"true_ra_bits"`
	TrueDecBits     uint64 `json:"true_dec_bits"`
	DistanceBits    uint64 `json:"distance_bits"`
}

type lowPrecisionMoonSnapshot struct {
	LoBits         uint64 `json:"lo_bits"`
	SunAngleBits   uint64 `json:"sun_angle_bits"`
	MBits          uint64 `json:"m_bits"`
	LonXBits       uint64 `json:"lonx_bits"`
	IBits          uint64 `json:"i_bits"`
	RBits          uint64 `json:"r_bits"`
	BBits          uint64 `json:"b_bits"`
	TrueLoBits     uint64 `json:"true_lo_bits"`
	TrueBoBits     uint64 `json:"true_bo_bits"`
	AwayBits       uint64 `json:"away_bits"`
	ApparentLoBits uint64 `json:"apparent_lo_bits"`
	TrueRaBits     uint64 `json:"true_ra_bits"`
	TrueDecBits    uint64 `json:"true_dec_bits"`
}

type lowPrecisionSample struct {
	UTC  string                   `json:"utc"`
	TTJD float64                  `json:"tt_jd"`
	Sun  lowPrecisionSunSnapshot  `json:"sun"`
	Moon lowPrecisionMoonSnapshot `json:"moon"`
}

func loadLowPrecisionSamples(t *testing.T) []lowPrecisionSample {
	t.Helper()

	data, err := os.ReadFile("testdata/low_precision_sun_moon_baseline.json")
	if err != nil {
		t.Fatal(err)
	}

	var samples []lowPrecisionSample
	if err := json.Unmarshal(data, &samples); err != nil {
		t.Fatal(err)
	}
	if len(samples) == 0 {
		t.Fatal("empty low precision baseline samples")
	}
	return samples
}

func TestLowPrecisionSunMoonBaselineRegression(t *testing.T) {
	samples := loadLowPrecisionSamples(t)

	assertBits := func(t *testing.T, name, utc string, got float64, want uint64) {
		t.Helper()
		if math.Float64bits(got) != want {
			t.Fatalf("%s regression at %s", name, utc)
		}
	}

	for _, sample := range samples {
		jd := sample.TTJD

		assertBits(t, "planet.SunLo", sample.UTC, planet.SunLo(jd), sample.Sun.LoBits)
		assertBits(t, "basic.SunLo", sample.UTC, basic.SunLo(jd), sample.Sun.LoBits)
		assertBits(t, "planet.SunM", sample.UTC, planet.SunM(jd), sample.Sun.MBits)
		assertBits(t, "basic.SunM", sample.UTC, basic.SunM(jd), sample.Sun.MBits)
		assertBits(t, "planet.Earthe", sample.UTC, planet.Earthe(jd), sample.Sun.EccBits)
		assertBits(t, "basic.Earthe", sample.UTC, basic.Earthe(jd), sample.Sun.EccBits)
		assertBits(t, "planet.EarthPI", sample.UTC, planet.EarthPI(jd), sample.Sun.PeriBits)
		assertBits(t, "basic.EarthPI", sample.UTC, basic.EarthPI(jd), sample.Sun.PeriBits)
		assertBits(t, "planet.SunMidFun", sample.UTC, planet.SunMidFun(jd), sample.Sun.MidBits)
		assertBits(t, "basic.SunMidFun", sample.UTC, basic.SunMidFun(jd), sample.Sun.MidBits)
		assertBits(t, "planet.SunTrueLo", sample.UTC, planet.SunTrueLo(jd), sample.Sun.TrueLoBits)
		assertBits(t, "basic.SunTrueLo", sample.UTC, basic.SunTrueLo(jd), sample.Sun.TrueLoBits)
		assertBits(t, "planet.SunApparentLo", sample.UTC, planet.SunApparentLo(jd), sample.Sun.ApparentLoBits)
		assertBits(t, "basic.SunApparentLo", sample.UTC, basic.SunApparentLo(jd), sample.Sun.ApparentLoBits)
		assertBits(t, "basic.SunApparentRa", sample.UTC, basic.SunApparentRa(jd), sample.Sun.ApparentRaBits)
		assertBits(t, "basic.SunApparentDec", sample.UTC, basic.SunApparentDec(jd), sample.Sun.ApparentDecBits)
		assertBits(t, "basic.SunTrueRa", sample.UTC, basic.SunTrueRa(jd), sample.Sun.TrueRaBits)
		assertBits(t, "basic.SunTrueDec", sample.UTC, basic.SunTrueDec(jd), sample.Sun.TrueDecBits)
		assertBits(t, "planet.Distance", sample.UTC, planet.Distance(jd), sample.Sun.DistanceBits)
		assertBits(t, "basic.Distance", sample.UTC, basic.Distance(jd), sample.Sun.DistanceBits)
		assertBits(t, "planet.MoonLo", sample.UTC, planet.MoonLo(jd), sample.Moon.LoBits)
		assertBits(t, "basic.MoonLo", sample.UTC, basic.MoonLo(jd), sample.Moon.LoBits)
		assertBits(t, "planet.SunMoonAngle", sample.UTC, planet.SunMoonAngle(jd), sample.Moon.SunAngleBits)
		assertBits(t, "basic.SunMoonAngle", sample.UTC, basic.SunMoonAngle(jd), sample.Moon.SunAngleBits)
		assertBits(t, "planet.MoonM", sample.UTC, planet.MoonM(jd), sample.Moon.MBits)
		assertBits(t, "basic.MoonM", sample.UTC, basic.MoonM(jd), sample.Moon.MBits)
		assertBits(t, "planet.MoonLonX", sample.UTC, planet.MoonLonX(jd), sample.Moon.LonXBits)
		assertBits(t, "basic.MoonLonX", sample.UTC, basic.MoonLonX(jd), sample.Moon.LonXBits)
		assertBits(t, "planet.MoonI", sample.UTC, planet.MoonI(jd), sample.Moon.IBits)
		assertBits(t, "basic.MoonI", sample.UTC, basic.MoonI(jd), sample.Moon.IBits)
		assertBits(t, "planet.MoonR", sample.UTC, planet.MoonR(jd), sample.Moon.RBits)
		assertBits(t, "basic.MoonR", sample.UTC, basic.MoonR(jd), sample.Moon.RBits)
		assertBits(t, "planet.MoonB", sample.UTC, planet.MoonB(jd), sample.Moon.BBits)
		assertBits(t, "basic.MoonB", sample.UTC, basic.MoonB(jd), sample.Moon.BBits)
		assertBits(t, "planet.MoonTrueLo", sample.UTC, planet.MoonTrueLo(jd), sample.Moon.TrueLoBits)
		assertBits(t, "basic.MoonTrueLo", sample.UTC, basic.MoonTrueLo(jd), sample.Moon.TrueLoBits)
		assertBits(t, "planet.MoonTrueBo", sample.UTC, planet.MoonTrueBo(jd), sample.Moon.TrueBoBits)
		assertBits(t, "basic.MoonTrueBo", sample.UTC, basic.MoonTrueBo(jd), sample.Moon.TrueBoBits)
		assertBits(t, "planet.MoonAway", sample.UTC, planet.MoonAway(jd), sample.Moon.AwayBits)
		assertBits(t, "basic.MoonAway", sample.UTC, basic.MoonAway(jd), sample.Moon.AwayBits)
		assertBits(t, "basic.MoonApparentLo", sample.UTC, basic.MoonApparentLo(jd), sample.Moon.ApparentLoBits)
		assertBits(t, "basic.MoonTrueRa", sample.UTC, basic.MoonTrueRa(jd), sample.Moon.TrueRaBits)
		assertBits(t, "basic.MoonTrueDec", sample.UTC, basic.MoonTrueDec(jd), sample.Moon.TrueDecBits)
	}
}

func TestDerivedHighPrecisionTruncationFullMatchesDefault(t *testing.T) {
	jd := 2469808.7654321
	lon := 116.391
	lat := 39.907
	tz := 8.0

	assertSame := func(name string, got, want float64) {
		t.Helper()
		if math.Float64bits(got) != math.Float64bits(want) {
			t.Fatalf("%s full-n mismatch", name)
		}
	}
	assertSamePair := func(name string, got1, got2, want1, want2 float64) {
		t.Helper()
		assertSame(name+".1", got1, want1)
		assertSame(name+".2", got2, want2)
	}

	assertSame("HSunTrueLo", basic.HSunTrueLo(jd), basic.HSunTrueLoN(jd, -1))
	assertSame("HSunTrueBo", basic.HSunTrueBo(jd), basic.HSunTrueBoN(jd, -1))
	assertSame("HSunApparentLo", basic.HSunApparentLo(jd), basic.HSunApparentLoN(jd, -1))
	assertSame("SunLoGXC", basic.SunLoGXC(jd), basic.SunLoGXCN(jd, -1))
	assertSame("EarthAway", basic.EarthAway(jd), basic.EarthAwayN(jd, -1))
	assertSame("HSunApparentRa", basic.HSunApparentRa(jd), basic.HSunApparentRaN(jd, -1))
	assertSame("HSunTrueRa", basic.HSunTrueRa(jd), basic.HSunTrueRaN(jd, -1))
	assertSame("HSunApparentDec", basic.HSunApparentDec(jd), basic.HSunApparentDecN(jd, -1))
	assertSame("HSunTrueDec", basic.HSunTrueDec(jd), basic.HSunTrueDecN(jd, -1))
	ra1, dec1 := basic.HSunApparentRaDec(jd)
	ra2, dec2 := basic.HSunApparentRaDecN(jd, -1)
	assertSamePair("HSunApparentRaDec", ra1, dec1, ra2, dec2)

	assertSame("HMoonTrueLo", basic.HMoonTrueLo(jd), basic.HMoonTrueLoN(jd, -1))
	assertSame("HMoonTrueBo", basic.HMoonTrueBo(jd), basic.HMoonTrueBoN(jd, -1))
	assertSame("HMoonAway", basic.HMoonAway(jd), basic.HMoonAwayN(jd, -1))
	assertSame("HMoonApparentLo", basic.HMoonApparentLo(jd), basic.HMoonApparentLoN(jd, -1))
	assertSame("HMoonTrueRa", basic.HMoonTrueRa(jd), basic.HMoonTrueRaN(jd, -1))
	assertSame("HMoonTrueDec", basic.HMoonTrueDec(jd), basic.HMoonTrueDecN(jd, -1))
	ra1, dec1 = basic.HMoonTrueRaDec(jd)
	ra2, dec2 = basic.HMoonTrueRaDecN(jd, -1)
	assertSamePair("HMoonTrueRaDec", ra1, dec1, ra2, dec2)
	ra1, dec1 = basic.HMoonApparentRaDec(jd, lon, lat, tz)
	ra2, dec2 = basic.HMoonApparentRaDecN(jd, lon, lat, tz, -1)
	assertSamePair("HMoonApparentRaDec", ra1, dec1, ra2, dec2)
	assertSame("HMoonApparentRa", basic.HMoonApparentRa(jd, lon, lat, tz), basic.HMoonApparentRaN(jd, lon, lat, tz, -1))
	assertSame("HMoonApparentDec", basic.HMoonApparentDec(jd, lon, lat, tz), basic.HMoonApparentDecN(jd, lon, lat, tz, -1))
	assertSame("HMoonAzimuth", basic.HMoonAzimuth(jd, lon, lat, tz), basic.HMoonAzimuthN(jd, lon, lat, tz, -1))
	assertSame("HMoonHeight", basic.HMoonHeight(jd, lon, lat, tz), basic.HMoonHeightN(jd, lon, lat, tz, -1))
}
