package wickra

import (
	"bufio"
	"math"
	"os"
	"strconv"
	"strings"
	"testing"
)

// Golden-fixture parity: replay the shared testdata/golden input series through
// the Go FFI and assert every value matches the Rust reference output. Where the
// archetype test only checks finiteness, this pins exact values, catching wiring
// bugs (swapped params, wrong multi-output field). Fixtures are generated by
// `cargo run -p wickra-examples --bin gen_golden`.

const goldenTol = 1e-6

func readGolden(t *testing.T, name string) [][]string {
	t.Helper()
	f, err := os.Open("../../testdata/golden/" + name + ".csv")
	if err != nil {
		t.Fatalf("open %s: %v", name, err)
	}
	defer f.Close()
	var rows [][]string
	sc := bufio.NewScanner(f)
	first := true
	for sc.Scan() {
		line := sc.Text()
		if first {
			first = false
			continue
		}
		if line == "" {
			continue
		}
		rows = append(rows, strings.Split(line, ","))
	}
	return rows
}

func goldenCell(s string) float64 {
	if s == "nan" {
		return math.NaN()
	}
	v, _ := strconv.ParseFloat(s, 64)
	return v
}

func goldenInput(t *testing.T) [][]float64 {
	rows := readGolden(t, "input")
	out := make([][]float64, len(rows))
	for i, r := range rows {
		vals := make([]float64, len(r))
		for j, c := range r {
			vals[j] = goldenCell(c)
		}
		out[i] = vals
	}
	return out
}

func assertGoldenClose(t *testing.T, got, want float64, row int, field string) {
	t.Helper()
	if math.IsNaN(want) {
		if !math.IsNaN(got) {
			t.Errorf("row %d %s: expected warmup/NaN, got %v", row, field, got)
		}
		return
	}
	tol := goldenTol * math.Max(1.0, math.Abs(want))
	if math.Abs(got-want) > tol {
		t.Errorf("row %d %s: got %v want %v", row, field, got, want)
	}
}

func TestGoldenScalar(t *testing.T) {
	input := goldenInput(t)
	sma, err := NewSma(14)
	if err != nil {
		t.Fatal(err)
	}
	defer sma.Close()
	ema, err := NewEma(14)
	if err != nil {
		t.Fatal(err)
	}
	defer ema.Close()
	rsi, err := NewRsi(14)
	if err != nil {
		t.Fatal(err)
	}
	defer rsi.Close()

	cases := []struct {
		name string
		upd  func(close float64) float64
	}{
		{"sma", sma.Update},
		{"ema", ema.Update},
		{"rsi", rsi.Update},
	}
	for _, tc := range cases {
		exp := readGolden(t, tc.name)
		for i := range input {
			assertGoldenClose(t, tc.upd(input[i][3]), goldenCell(exp[i][0]), i, tc.name)
		}
	}
}

func TestGoldenAtr(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "atr")
	atr, err := NewAtr(14)
	if err != nil {
		t.Fatal(err)
	}
	defer atr.Close()
	for i := range input {
		got := atr.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
		assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "atr")
	}
}

func TestGoldenBeta(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "beta")
	beta, err := NewBeta(20)
	if err != nil {
		t.Fatal(err)
	}
	defer beta.Close()
	for i := range input {
		// generator fed (close, open)
		assertGoldenClose(t, beta.Update(input[i][3], input[i][0]), goldenCell(exp[i][0]), i, "beta")
	}
}

func TestGoldenMacd(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "macd")
	macd, err := NewMacdIndicator(12, 26, 9)
	if err != nil {
		t.Fatal(err)
	}
	defer macd.Close()
	for i := range input {
		out, ok := macd.Update(input[i][3])
		if exp[i][0] == "nan" {
			if ok {
				t.Errorf("row %d macd: expected warmup, got %+v", i, out)
			}
			continue
		}
		if !ok {
			t.Errorf("row %d macd: expected value, got warmup", i)
			continue
		}
		assertGoldenClose(t, out.Macd, goldenCell(exp[i][0]), i, "macd.macd")
		assertGoldenClose(t, out.Signal, goldenCell(exp[i][1]), i, "macd.signal")
		assertGoldenClose(t, out.Histogram, goldenCell(exp[i][2]), i, "macd.histogram")
	}
}

func TestGoldenAdx(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "adx")
	adx, err := NewAdx(14)
	if err != nil {
		t.Fatal(err)
	}
	defer adx.Close()
	for i := range input {
		out, ok := adx.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
		if exp[i][0] == "nan" {
			if ok {
				t.Errorf("row %d adx: expected warmup, got %+v", i, out)
			}
			continue
		}
		if !ok {
			t.Errorf("row %d adx: expected value, got warmup", i)
			continue
		}
		assertGoldenClose(t, out.PlusDi, goldenCell(exp[i][0]), i, "adx.plus_di")
		assertGoldenClose(t, out.MinusDi, goldenCell(exp[i][1]), i, "adx.minus_di")
		assertGoldenClose(t, out.Adx, goldenCell(exp[i][2]), i, "adx.adx")
	}
}

// The four de-duplicated indicators: pin their corrected definitions against
// the Rust reference so the Go FFI stays bit-identical.

func TestGoldenAdOscillator(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "ad_oscillator")
	ad, err := NewAdOscillator()
	if err != nil {
		t.Fatal(err)
	}
	defer ad.Close()
	for i := range input {
		got := ad.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
		assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "ad_oscillator")
	}
}

func TestGoldenIntradayIntensity(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "intraday_intensity")
	ii, err := NewIntradayIntensity()
	if err != nil {
		t.Fatal(err)
	}
	defer ii.Close()
	for i := range input {
		got := ii.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
		assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "intraday_intensity")
	}
}

func TestGoldenAwesomeOscillatorHistogram(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "awesome_oscillator_histogram")
	aoh, err := NewAwesomeOscillatorHistogram(5, 34, 1)
	if err != nil {
		t.Fatal(err)
	}
	defer aoh.Close()
	for i := range input {
		got := aoh.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
		assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "awesome_oscillator_histogram")
	}
}

func TestGoldenAverageDrawdown(t *testing.T) {
	input := goldenInput(t)
	exp := readGolden(t, "average_drawdown")
	avg, err := NewAverageDrawdown(20)
	if err != nil {
		t.Fatal(err)
	}
	defer avg.Close()
	for i := range input {
		// generator fed the close column as the equity-curve sample.
		assertGoldenClose(t, avg.Update(input[i][3]), goldenCell(exp[i][0]), i, "average_drawdown")
	}
}
