package adif

import (
	"strings"
	"testing"
)

func TestParseSimple(t *testing.T) {
	src := `Header text here
Generated by HamLog
<EOH>
<CALL:5>F4XYZ<BAND:3>20m<MODE:3>SSB<QSO_DATE:8>20240101<TIME_ON:6>123456<EOR>
<call:4>K1AB<band:3>40m<mode:2>CW<qso_date:8>20240102<time_on:4>0930<eor>
`
	var got []Record
	err := Parse(strings.NewReader(src), func(r Record) error {
		got = append(got, r)
		return nil
	})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(got) != 2 {
		t.Fatalf("want 2 records, got %d", len(got))
	}
	if got[0]["call"] != "F4XYZ" || got[0]["band"] != "20m" || got[0]["mode"] != "SSB" {
		t.Errorf("record 0 mismatch: %+v", got[0])
	}
	if got[1]["call"] != "K1AB" || got[1]["time_on"] != "0930" {
		t.Errorf("record 1 mismatch: %+v", got[1])
	}
}

func TestParseValueWithAngleBracket(t *testing.T) {
	// Length-prefixed value can contain '<' and '>' bytes.
	src := `<EOH><CALL:5>F4XYZ<COMMENT:7>a<b>c<d<EOR>`
	var got []Record
	err := Parse(strings.NewReader(src), func(r Record) error {
		got = append(got, r)
		return nil
	})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("want 1, got %d", len(got))
	}
	if got[0]["comment"] != "a<b>c<d" {
		t.Errorf("comment mismatch: %q", got[0]["comment"])
	}
}

func TestParseNoHeader(t *testing.T) {
	// A file without a header is IMPORTED, not discarded.
	//
	// This test asserted the opposite until 2026-07-29, when an operator pasted a
	// dozen lines out of wsjtx_log.adi into a new .adi and got "0 imported,
	// 0 ignored, 0 total". Discarding records before <EOH> was a defensible
	// reading of the spec, but the file it rejects is one people really make, and
	// the rejection is silent: the count says the file was empty, not that a tag
	// was missing. An <eor> is proof enough that a record ended.
	src := `<CALL:5>F4XYZ<EOR>`
	var got []Record
	err := Parse(strings.NewReader(src), func(r Record) error {
		got = append(got, r)
		return nil
	})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("expected the record to be imported without <EOH>, got %d", len(got))
	}
	if got[0]["call"] != "F4XYZ" {
		t.Errorf("call = %q, want F4XYZ", got[0]["call"])
	}
}

func TestParseTypedField(t *testing.T) {
	// <FIELD:LEN:TYPE> form (e.g. <FREQ:6:N>).
	src := `<EOH><CALL:5>F4XYZ<FREQ:6:N>14.250<EOR>`
	var got Record
	err := Parse(strings.NewReader(src), func(r Record) error {
		got = r
		return nil
	})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if got["freq"] != "14.250" {
		t.Errorf("freq mismatch: %q", got["freq"])
	}
}

// A file with NO header must still import.
//
// Reported from the field: an operator lifted a dozen lines out of wsjtx_log.adi
// into a new .adi and got "0 imported, 0 ignored, 0 total" — which reads as an
// empty file rather than as a missing header. Records were only emitted after
// <EOH>, so a hand-assembled file was invisible in its entirety.
func TestParseHeaderlessFile(t *testing.T) {
	// The operator's exact shape: records straight from a WSJT-X log, no header.
	in := "<call:5>PY2VE <gridsquare:4>GG67 <mode:3>FT8 <rst_sent:3>-18 <rst_rcvd:3>-15 " +
		"<qso_date:8>20260728 <time_on:6>194200 <band:3>15m <eor>\n" +
		"<call:5>W8OBX <gridsquare:4>FM25 <mode:3>FT8 <rst_sent:3>-19 <rst_rcvd:3>-12 " +
		"<qso_date:8>20260728 <time_on:6>194500 <band:3>15m <eor>\n"

	var got []Record
	if err := Parse(strings.NewReader(in), func(r Record) error {
		got = append(got, r)
		return nil
	}); err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(got) != 2 {
		t.Fatalf("parsed %d records from a headerless file, want 2", len(got))
	}
	if got[0]["call"] != "PY2VE" || got[1]["call"] != "W8OBX" {
		t.Errorf("callsigns = %q, %q — want PY2VE, W8OBX", got[0]["call"], got[1]["call"])
	}
	if got[0]["gridsquare"] != "GG67" {
		t.Errorf("gridsquare = %q, want GG67", got[0]["gridsquare"])
	}
}

// And a normal file must be unaffected: its header is still discarded, so header
// fields never leak into the first record.
func TestParseHeaderStillDiscarded(t *testing.T) {
	in := "Generated by SomeLogger\n<adif_ver:5>3.1.4 <programid:6>OpsLog <eoh>\n" +
		"<call:4>F4AA <mode:2>CW <eor>\n"
	var got []Record
	if err := Parse(strings.NewReader(in), func(r Record) error {
		got = append(got, r)
		return nil
	}); err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if len(got) != 1 {
		t.Fatalf("parsed %d records, want 1", len(got))
	}
	if _, leaked := got[0]["programid"]; leaked {
		t.Error("a header field leaked into the record")
	}
	if got[0]["call"] != "F4AA" {
		t.Errorf("call = %q, want F4AA", got[0]["call"])
	}
}
