package adif

import (
	"bufio"
	"bytes"
	"strings"
	"testing"
	"time"

	"hamlog/internal/qso"
)

// TestPromotedFieldsRoundTrip writes a QSO carrying the ADIF 3.1.7 promoted
// fields, parses it back, and checks they survive — guarding the export
// writeRecord ↔ import recordToQSO field-name mapping against typos.
func TestPromotedFieldsRoundTrip(t *testing.T) {
	dist := 1234.5
	rxp := 5.0
	a := 12.0
	in := qso.QSO{
		Callsign: "EA8ABC", Band: "20m", Mode: "SSB",
		QSODate: time.Date(2026, 6, 6, 12, 0, 0, 0, time.UTC),
		SIG:     "POTA", SIGInfo: "US-0001", MySIG: "WWFF", MySIGInfo: "ONFF-0001",
		WWFFRef: "ONFF-0001", MyWWFFRef: "F-FFF-0001",
		Distance: &dist, RXPower: &rxp, AIndex: &a,
		SKCC: "12345S", FISTS: "999", TenTen: "55555",
		ContactedOp: "EA8XYZ", EqCall: "EA8OLD", PFX: "EA8", MyName: "Greg",
		Class: "1A", DarcDOK: "A01", MyDarcDOK: "B02", Region: "IV",
		SilentKey: "N", SWL: "N", QSOComplete: "Y", QSORandom: "Y",
		CreditGranted: "DXCC", CreditSubmitted: "WAS",
		MyARRLSect: "EMA", MyVUCCGrids: "FN20,FN21",
	}

	var buf bytes.Buffer
	bw := bufio.NewWriter(&buf)
	bw.WriteString("<EOH>\n")
	writeRecord(bw, in, true, nil)
	bw.Flush()

	var rec Record
	if err := Parse(strings.NewReader(buf.String()), func(r Record) error { rec = r; return nil }); err != nil {
		t.Fatalf("parse: %v", err)
	}
	out, ok := recordToQSO(rec)
	if !ok {
		t.Fatal("recordToQSO returned !ok")
	}

	checks := map[string]struct{ got, want string }{
		"SIG":              {out.SIG, in.SIG},
		"SIG_INFO":         {out.SIGInfo, in.SIGInfo},
		"MY_SIG":           {out.MySIG, in.MySIG},
		"MY_SIG_INFO":      {out.MySIGInfo, in.MySIGInfo},
		"WWFF_REF":         {out.WWFFRef, in.WWFFRef},
		"MY_WWFF_REF":      {out.MyWWFFRef, in.MyWWFFRef},
		"SKCC":             {out.SKCC, in.SKCC},
		"FISTS":            {out.FISTS, in.FISTS},
		"TEN_TEN":          {out.TenTen, in.TenTen},
		"CONTACTED_OP":     {out.ContactedOp, in.ContactedOp},
		"EQ_CALL":          {out.EqCall, in.EqCall},
		"PFX":              {out.PFX, in.PFX},
		"MY_NAME":          {out.MyName, in.MyName},
		"CLASS":            {out.Class, in.Class},
		"DARC_DOK":         {out.DarcDOK, in.DarcDOK},
		"MY_DARC_DOK":      {out.MyDarcDOK, in.MyDarcDOK},
		"REGION":           {out.Region, in.Region},
		"SILENT_KEY":       {out.SilentKey, in.SilentKey},
		"SWL":              {out.SWL, in.SWL},
		"QSO_COMPLETE":     {out.QSOComplete, in.QSOComplete},
		"QSO_RANDOM":       {out.QSORandom, in.QSORandom},
		"CREDIT_GRANTED":   {out.CreditGranted, in.CreditGranted},
		"CREDIT_SUBMITTED": {out.CreditSubmitted, in.CreditSubmitted},
		"MY_ARRL_SECT":     {out.MyARRLSect, in.MyARRLSect},
		"MY_VUCC_GRIDS":    {out.MyVUCCGrids, in.MyVUCCGrids},
	}
	for tag, c := range checks {
		if c.got != c.want {
			t.Errorf("%s round-trip = %q, want %q", tag, c.got, c.want)
		}
	}
	if out.Distance == nil || *out.Distance != dist {
		t.Errorf("DISTANCE round-trip = %v, want %v", out.Distance, dist)
	}
	if out.RXPower == nil || *out.RXPower != rxp {
		t.Errorf("RX_PWR round-trip = %v, want %v", out.RXPower, rxp)
	}
	if out.AIndex == nil || *out.AIndex != a {
		t.Errorf("A_INDEX round-trip = %v, want %v", out.AIndex, a)
	}
}

// TestStandardExportDropsNonStandard verifies that standard mode strips
// vendor/APP tags while full mode keeps them.
func TestStandardExportDropsNonStandard(t *testing.T) {
	q := qso.QSO{
		Callsign: "F4BPO", Band: "20m", Mode: "CW",
		Extras: map[string]string{
			"APP_LOG4OM_FOO": "x",
			"DARC_DOK":       "A01", // standard → kept in both
			"MY_VENDOR_TAG":  "y",   // non-standard → dropped in standard mode
		},
	}

	standard := renderRecord(q, false)
	if strings.Contains(standard, "APP_LOG4OM_FOO") || strings.Contains(standard, "MY_VENDOR_TAG") {
		t.Errorf("standard export should drop non-standard tags:\n%s", standard)
	}

	full := renderRecord(q, true)
	if !strings.Contains(full, "APP_LOG4OM_FOO") || !strings.Contains(full, "MY_VENDOR_TAG") {
		t.Errorf("full export should keep all extras:\n%s", full)
	}
}

func renderRecord(q qso.QSO, includeApp bool) string {
	var buf bytes.Buffer
	bw := bufio.NewWriter(&buf)
	writeRecord(bw, q, includeApp, nil)
	bw.Flush()
	return buf.String()
}

// TestQSLViaFieldsRoundTrip covers the case reported as issue #16: a log
// exported by another logger carries a manager in QSL_VIA and a routing method
// in QSL_SENT_VIA, and the two must stay apart.
//
// Before this, QSL_SENT_VIA was folded into QSL_VIA whenever QSL_VIA was empty
// — so a Log4OM log, which defaults QSL_SENT_VIA to E, showed "E" wherever
// OpsLog displays the manager — and QSL_RCVD_VIA was thrown away outright: it
// was listed as a promoted field with no column behind it, so it was not even
// kept among the extras. Neither was ever exported, which made an import
// followed by an export destroy both.
func TestQSLViaFieldsRoundTrip(t *testing.T) {
	in := qso.QSO{
		Callsign: "3B9FR", Band: "20m", Mode: "CW",
		QSODate:    time.Date(2026, 6, 6, 12, 0, 0, 0, time.UTC),
		QSLVia:     "M0OXO", // the manager
		QSLSentVia: "D",     // sent direct
		QSLRcvdVia: "B",     // came back via the bureau
	}
	var buf bytes.Buffer
	bw := bufio.NewWriter(&buf)
	bw.WriteString("<EOH>\n")
	writeRecord(bw, in, true, nil)
	bw.Flush()

	var rec Record
	if err := Parse(strings.NewReader(buf.String()), func(r Record) error { rec = r; return nil }); err != nil {
		t.Fatalf("parse: %v", err)
	}
	out, ok := recordToQSO(rec)
	if !ok {
		t.Fatal("recordToQSO returned !ok")
	}
	for name, c := range map[string]struct{ got, want string }{
		"QSL_VIA":      {out.QSLVia, in.QSLVia},
		"QSL_SENT_VIA": {out.QSLSentVia, in.QSLSentVia},
		"QSL_RCVD_VIA": {out.QSLRcvdVia, in.QSLRcvdVia},
	} {
		if c.got != c.want {
			t.Errorf("%s: got %q, want %q", name, c.got, c.want)
		}
	}
}

// TestQSLSentViaDoesNotBecomeManager pins the exact shape a Log4OM export has:
// no QSL_VIA at all, QSL_SENT_VIA defaulted to E. The manager field must come
// back empty rather than holding "E".
func TestQSLSentViaDoesNotBecomeManager(t *testing.T) {
	const rec = "<CALL:5>OE6CLD<QSO_DATE:8>20260606<TIME_ON:4>1200<BAND:3>20m<MODE:2>CW" +
		"<QSL_SENT_VIA:1>E<EOR>\n"
	var got Record
	if err := Parse(strings.NewReader("<EOH>\n"+rec), func(r Record) error { got = r; return nil }); err != nil {
		t.Fatalf("parse: %v", err)
	}
	q, ok := recordToQSO(got)
	if !ok {
		t.Fatal("recordToQSO returned !ok")
	}
	if q.QSLVia != "" {
		t.Errorf("QSL_VIA = %q — the routing method leaked into the manager field again", q.QSLVia)
	}
	if q.QSLSentVia != "E" {
		t.Errorf("QSL_SENT_VIA = %q, want %q", q.QSLSentVia, "E")
	}
}

// M is import-only in the ADIF QSL Via enumeration: keep it when given, never
// write it back out.
func TestQSLViaManagerIsImportOnly(t *testing.T) {
	in := qso.QSO{
		Callsign: "3B9FR", Band: "20m", Mode: "CW",
		QSODate:    time.Date(2026, 6, 6, 12, 0, 0, 0, time.UTC),
		QSLSentVia: "M", QSLRcvdVia: "M",
	}
	var buf bytes.Buffer
	bw := bufio.NewWriter(&buf)
	writeRecord(bw, in, true, nil)
	bw.Flush()
	if s := buf.String(); strings.Contains(s, "QSL_SENT_VIA") || strings.Contains(s, "QSL_RCVD_VIA") {
		t.Errorf("exported an import-only value:\n%s", s)
	}
}
