package fix

import (
	"fmt"

	"quantex.com/qfixdpl/quickfix"
	"quantex.com/qfixdpl/quickfix/datadictionary"
)

// BuildFieldMap walks a quickfix.FieldMap and produces an enriched FieldMap
// keyed by FixField (Name + Tag + Type) with values typed per the FIX
// data dictionary. Repeating groups become []FieldMap; nested groups recurse.
func BuildFieldMap(qfMap quickfix.FieldMap, dd *datadictionary.DataDictionary, fieldsByTag map[int]*datadictionary.FieldDef) FieldMap {
	out := FieldMap{}

	for _, t := range qfMap.Tags() {
		tagInt := int(t)
		rawValues := qfMap.RawValues(t)
		if len(rawValues) == 0 {
			continue
		}

		ff, fd := resolveField(dd, fieldsByTag, tagInt)

		if ff.Type == "NUMINGROUP" {
			if fd != nil && fd.IsGroup() {
				out[ff] = buildGroup(rawValues[1:], dd, fd.Fields)
			} else {
				// Count tag with no group structure available (dictionary lookup
				// failed, or quickfix didn't nest the inner fields). Emit an empty
				// group rather than an int so the consumer's type expectation holds.
				out[ff] = []FieldMap{}
			}
			continue
		}

		out[ff] = parseScalar(rawValues[0].Value(), ff.Type)
	}

	return out
}

// buildGroup splits a flat slice of TagValues (the inner values of a NUMINGROUP,
// excluding the count) into per-repetition FieldMaps. The first element of
// innerFields is the delimiter that marks the start of each repetition.
func buildGroup(tvs []quickfix.TagValue, dd *datadictionary.DataDictionary, innerFields []*datadictionary.FieldDef) []FieldMap {
	if len(innerFields) == 0 || len(tvs) == 0 {
		return nil
	}

	delimiter := innerFields[0].Tag()
	fdByTag := make(map[int]*datadictionary.FieldDef, len(innerFields))
	for _, f := range innerFields {
		fdByTag[f.Tag()] = f
	}

	var (
		repetitions []FieldMap
		current     FieldMap
	)

	i := 0
	for i < len(tvs) {
		tv := tvs[i]
		tagInt := int(tv.Tag())

		if tagInt == delimiter {
			current = FieldMap{}
			repetitions = append(repetitions, current)
		}

		if current == nil {
			// Field appeared before the first delimiter; skip.
			i++
			continue
		}

		fd, known := fdByTag[tagInt]
		if !known {
			current[FixField{Name: fmt.Sprintf("tag_%d", tagInt), Tag: tagInt, Type: "STRING"}] = string(tv.Value())
			i++
			continue
		}

		if fd.IsGroup() {
			nestedAllowed := allowedTags(fd.Fields)
			j := i + 1
			for j < len(tvs) && nestedAllowed[int(tvs[j].Tag())] {
				j++
			}
			nested := buildGroup(tvs[i+1:j], dd, fd.Fields)
			current[FixField{Name: fd.Name(), Tag: tagInt, Type: "NUMINGROUP"}] = nested
			i = j
			continue
		}

		current[FixField{Name: fd.Name(), Tag: tagInt, Type: fd.Type}] = parseScalar(tv.Value(), fd.Type)
		i++
	}

	return repetitions
}

// resolveField looks up the FixField metadata (name + type) for a tag.
// Prefers the message-level FieldDef so group structure is preserved;
// falls back to the global FieldTypeByTag, then to a synthetic "tag_<N>" STRING.
func resolveField(dd *datadictionary.DataDictionary, fieldsByTag map[int]*datadictionary.FieldDef, tagInt int) (FixField, *datadictionary.FieldDef) {
	if fieldsByTag != nil {
		if fd, ok := fieldsByTag[tagInt]; ok {
			return FixField{Name: fd.Name(), Tag: tagInt, Type: fd.Type}, fd
		}
	}

	if dd != nil {
		if ft, ok := dd.FieldTypeByTag[tagInt]; ok {
			return FixField{Name: ft.Name(), Tag: tagInt, Type: ft.Type}, nil
		}
	}

	return FixField{Name: fmt.Sprintf("tag_%d", tagInt), Tag: tagInt, Type: "STRING"}, nil
}

// parseScalar converts raw FIX bytes into the Go type expected by the consumer
// (GetKeyValue): bool for BOOLEAN, int for INT/SEQNUM, time.Time for UTCTIMESTAMP,
// string for everything else.
func parseScalar(raw []byte, fixType string) interface{} {
	s := string(raw)

	switch fixType {
	case "INT", "SEQNUM", "LENGTH":
		var v quickfix.FIXInt
		if err := v.Read(raw); err == nil {
			return int(v)
		}
		return s
	case "BOOLEAN":
		var v quickfix.FIXBoolean
		if err := v.Read(raw); err == nil {
			return bool(v)
		}
		return s
	case "UTCTIMESTAMP":
		var v quickfix.FIXUTCTimestamp
		if err := v.Read(raw); err == nil {
			return v.Time
		}
		return s
	default:
		return s
	}
}

// allowedTags returns the set of all tags valid inside a group (including
// nested-group counts and their descendants), used to detect where a flat
// nested-group slice ends within its parent.
func allowedTags(fields []*datadictionary.FieldDef) map[int]bool {
	out := make(map[int]bool, len(fields))

	var visit func(fs []*datadictionary.FieldDef)
	visit = func(fs []*datadictionary.FieldDef) {
		for _, f := range fs {
			out[f.Tag()] = true
			if f.IsGroup() {
				visit(f.Fields)
			}
		}
	}

	visit(fields)

	return out
}
