package qso

import (
	"strings"
	"testing"
)

// "2 m or 70 cm, in FT8, since January" could not be asked before: the filter
// joins every condition with ONE AND or OR, so AND killed the two bands and OR
// let every FT8 QSO through. The OR now lives inside a single condition.
func TestInConditionExpressesSeveralValuesForOneField(t *testing.T) {
	sql, args, err := conditionSQL(Condition{Field: "band", Op: "in", Value: "2m, 70cm"})
	if err != nil {
		t.Fatalf("in: %v", err)
	}
	if !strings.Contains(sql, "IN (?,?)") {
		t.Errorf("sql = %q, want an IN with two placeholders", sql)
	}
	if len(args) != 2 || args[0] != "2m" || args[1] != "70cm" {
		t.Errorf("args = %v, want [2m 70cm] trimmed", args)
	}
}

// A trailing comma while typing must not add an empty value that matches nothing.
func TestInIgnoresBlanks(t *testing.T) {
	_, args, err := conditionSQL(Condition{Field: "mode", Op: "in", Value: "FT8, ,FT4,"})
	if err != nil {
		t.Fatalf("in: %v", err)
	}
	if len(args) != 2 {
		t.Errorf("args = %v, want the two real values only", args)
	}
}

// An empty list matches nothing. Dropping the condition instead would WIDEN the
// result set — the opposite of what someone typing a filter expects.
func TestEmptyInMatchesNothing(t *testing.T) {
	sql, _, err := conditionSQL(Condition{Field: "band", Op: "in", Value: "  , "})
	if err != nil {
		t.Fatalf("in: %v", err)
	}
	if sql != "1=0" {
		t.Errorf("sql = %q, want 1=0", sql)
	}
}

// NOT IN must keep rows whose column is NULL: the row is not one of the listed
// values, so it belongs in the answer. Raw SQL NOT IN drops them.
func TestNotInKeepsNulls(t *testing.T) {
	sql, _, err := conditionSQL(Condition{Field: "band", Op: "notin", Value: "2m"})
	if err != nil {
		t.Fatalf("notin: %v", err)
	}
	if !strings.HasPrefix(sql, "IFNULL(") {
		t.Errorf("sql = %q, want the column wrapped in IFNULL", sql)
	}
}

// Every HAMLOG field an operator can bulk-edit must also be filterable: the
// question "which contacts have never gone to HAMLOG" is what you ask BEFORE
// uploading a backlog, and a field that can be written but not read back is
// half a feature.
func TestHamlogFieldsAreFilterable(t *testing.T) {
	filterable := map[string]bool{}
	for _, f := range FilterableFields() {
		filterable[f] = true
	}
	for _, f := range []string{"hamlog_sent", "hamlog_sent_date", "hamlog_rcvd", "hamlog_rcvd_date"} {
		if !filterable[f] {
			t.Errorf("%s is bulk-editable but not filterable", f)
		}
	}
	// The received key must be THEIR name, or an imported log filters differently
	// from a stamped one — the exact split the confirmation source exists to avoid.
	if got := filterableExtras["hamlog_rcvd"]; got != "APP_HAMLOG_QSO_CFM" {
		t.Errorf("hamlog_rcvd filters on %q, want APP_HAMLOG_QSO_CFM", got)
	}
}
