package fix

import (
	"time"

	"quantex.com/qfixpt/quickfix"
	"quantex.com/qfixpt/quickfix/gen/enum"
	"quantex.com/qfixpt/quickfix/gen/fix50sp2/allocationreport"
	"quantex.com/qfixpt/quickfix/gen/fix50sp2/confirmation"
	"quantex.com/qfixpt/quickfix/gen/fix50sp2/tradecapturereport"
	"quantex.com/qfixpt/quickfix/gen/tag"
	"quantex.com/qfixpt/src/domain"
)

func getCustomString(body *quickfix.FieldMap, t int) string {
	v, err := body.GetString(quickfix.Tag(t))
	if err != nil {
		return ""
	}

	return v
}

func getCustomInt(body *quickfix.FieldMap, t int) int {
	v, err := body.GetInt(quickfix.Tag(t))
	if err != nil {
		return 0
	}

	return v
}

func getCustomBool(body *quickfix.FieldMap, t int) bool {
	v, err := body.GetBool(quickfix.Tag(t))
	if err != nil {
		return false
	}

	return v
}

func extractHeader(msg *quickfix.Message) map[string]interface{} {
	h := make(map[string]interface{})

	if v, err := msg.Header.GetBytes(tag.BeginString); err == nil {
		h["BeginString"] = string(v)
	}

	if v, err := msg.Header.GetBytes(tag.MsgType); err == nil {
		h["MsgType"] = string(v)
	}

	if v, err := msg.Header.GetBytes(tag.SenderCompID); err == nil {
		h["SenderCompID"] = string(v)
	}

	if v, err := msg.Header.GetBytes(tag.TargetCompID); err == nil {
		h["TargetCompID"] = string(v)
	}

	if v, err := msg.Header.GetBytes(tag.SendingTime); err == nil {
		h["SendingTime"] = string(v)
	}

	return h
}

func parseTradeCaptureReportJSON(msg tradecapturereport.TradeCaptureReport) domain.FixMessageJSON {
	tradeID, _ := msg.GetTradeID()

	body := map[string]interface{}{}

	if v, _ := msg.GetTradeReportID(); v != "" {
		body["TradeReportID"] = v
	}

	body["TradeID"] = tradeID

	if v, _ := msg.GetExecID(); v != "" {
		body["ExecID"] = v
	}

	if v, _ := msg.GetTradeReportType(); v != "" {
		body["TradeReportType"] = string(v)
	}

	if v, _ := msg.GetLastQty(); !v.IsZero() {
		body["LastQty"] = v.String()
	}

	if v, _ := msg.GetLastPx(); !v.IsZero() {
		body["LastPx"] = v.String()
	}

	if v, _ := msg.GetSpread(); !v.IsZero() {
		body["Spread"] = v.String()
	}

	if v, _ := msg.GetSettlDate(); v != "" {
		body["SettlDate"] = v
	}

	if v, _ := msg.GetTradeDate(); v != "" {
		body["TradeDate"] = v
	}

	if v, _ := msg.GetGrossTradeAmt(); !v.IsZero() {
		body["GrossTradeAmt"] = v.String()
	}

	if v, _ := msg.GetTradeReportRefID(); v != "" {
		body["TradeReportRefID"] = v
	}

	// Multi-leg support.
	if v, _ := msg.GetTradeLegRefID(); v != "" {
		body["TradeLegRefID"] = v
	}

	if v := getCustomString(&msg.Message.Body.FieldMap, tagTradingSystemID); v != "" {
		body["TradingSystemID"] = v
	}

	// Custom fields.
	if v := getCustomString(&msg.Message.Body.FieldMap, tagTWTradeID); v != "" {
		body["TWTradeID"] = v
	}

	if v := getCustomString(&msg.Message.Body.FieldMap, tagTWOrigTradeID); v != "" {
		body["TWOrigTradeID"] = v
	}

	// Side-level fields.
	sides, sideErr := msg.GetNoSides()
	if sideErr == nil && sides.Len() > 0 {
		s := sides.Get(0)

		if v, e := s.GetSide(); e == nil {
			body["Side"] = string(v)
		}

		if v, e := s.GetAccruedInterestAmt(); e == nil {
			body["AccruedInterestAmt"] = v.String()
		}

		if v, e := s.GetNetMoney(); e == nil {
			body["NetMoney"] = v.String()
		}

		if v := extractPartyByRole(s, enum.PartyRole_CLEARING_FIRM); v != "" {
			body["ClearingFirm"] = v
		}

		if v := extractPartyByRole(s, "1007"); v != "" {
			body["TradewebTraderID"] = v
		}
	}

	return domain.FixMessageJSON{
		Direction:   "IN",
		MsgType:     "AE",
		TradeID:     tradeID,
		Header:      extractHeader(msg.Message),
		Body:        body,
		ReceiveTime: time.Now(),
	}
}

func parseAllocationReportJSON(msg allocationreport.AllocationReport) domain.FixMessageJSON {
	// Primary: tag 1003 directly on the body.
	tradeID := getCustomString(&msg.Message.Body.FieldMap, 1003)

	// Fallback: SecondaryOrderID (198) from the NoOrders repeating group.
	if tradeID == "" {
		if orders, err := msg.GetNoOrders(); err == nil && orders.Len() > 0 {
			if v, e := orders.Get(0).GetSecondaryOrderID(); e == nil && v != "" {
				tradeID = v
			}
		}
	}

	body := map[string]interface{}{}

	if v, _ := msg.GetAllocReportID(); v != "" {
		body["AllocReportID"] = v
	}

	if v, _ := msg.GetAllocTransType(); v != "" {
		body["AllocTransType"] = string(v)
	}

	body["TradeID"] = tradeID

	// TradingSystemID (6731) — for multi-leg, differentiates each leg.
	if v := getCustomString(&msg.Message.Body.FieldMap, tagTradingSystemID); v != "" {
		body["TradingSystemID"] = v
	}

	// SecondaryOrderID (198) from NoOrders — explicit reference to original trade.
	if orders, err := msg.GetNoOrders(); err == nil && orders.Len() > 0 {
		if v, e := orders.Get(0).GetSecondaryOrderID(); e == nil && v != "" {
			body["SecondaryOrderID"] = v
		}
	}

	allocs, allocErr := msg.GetNoAllocs()
	if allocErr == nil {
		var entries []map[string]interface{}

		for i := 0; i < allocs.Len(); i++ {
			entry := allocs.Get(i)
			e := map[string]interface{}{}

			if v, err := entry.GetAllocAccount(); err == nil {
				e["AllocAccount"] = v
			}

			if v, err := entry.GetAllocQty(); err == nil {
				e["AllocQty"] = v.String()
			}

			if v, err := entry.GetIndividualAllocID(); err == nil {
				e["IndividualAllocID"] = v
			}

			if v, err := entry.GetAllocPrice(); err == nil {
				e["AllocPrice"] = v.String()
			}

			entries = append(entries, e)
		}

		body["Allocs"] = entries
	}

	return domain.FixMessageJSON{
		Direction:   "IN",
		MsgType:     "AS",
		TradeID:     tradeID,
		Header:      extractHeader(msg.Message),
		Body:        body,
		ReceiveTime: time.Now(),
	}
}

func parseConfirmationJSON(msg confirmation.Confirmation) domain.FixMessageJSON {
	// Try TradeID first, fall back to ConfirmID.
	tradeID := getCustomString(&msg.Message.Body.FieldMap, 1003)
	if tradeID == "" {
		tradeID, _ = msg.GetConfirmID()
	}

	body := map[string]interface{}{}

	if v, _ := msg.GetConfirmID(); v != "" {
		body["ConfirmID"] = v
	}

	if v, _ := msg.GetConfirmStatus(); v != "" {
		body["ConfirmStatus"] = string(v)
	}

	if v, _ := msg.GetConfirmTransType(); v != "" {
		body["ConfirmTransType"] = string(v)
	}

	if v, _ := msg.GetIndividualAllocID(); v != "" {
		body["IndividualAllocID"] = v
	}

	if v, _ := msg.GetTradeDate(); v != "" {
		body["TradeDate"] = v
	}

	if v := getCustomString(&msg.Message.Body.FieldMap, tagTWClrID); v != "" {
		body["TWClrID"] = v
	}

	if v := getCustomString(&msg.Message.Body.FieldMap, tagDlrClrID); v != "" {
		body["DlrClrID"] = v
	}

	if v := getCustomString(&msg.Message.Body.FieldMap, tagClearingStatus); v != "" {
		body["ClearingStatus"] = v
	}

	return domain.FixMessageJSON{
		Direction:   "IN",
		MsgType:     "AK",
		TradeID:     tradeID,
		Header:      extractHeader(msg.Message),
		Body:        body,
		ReceiveTime: time.Now(),
	}
}

func extractTradeIdentifier(msg *quickfix.Message) string {
	// Primary: TradeID (1003).
	var tradeID quickfix.FIXString
	if err := msg.Body.GetField(tag.TradeID, &tradeID); err == nil && string(tradeID) != "" {
		return string(tradeID)
	}

	// Fallback: SecondaryOrderID (198) — used in AllocationReport to reference the trade.
	var secOrderID quickfix.FIXString
	if err := msg.Body.GetField(tag.SecondaryOrderID, &secOrderID); err == nil && string(secOrderID) != "" {
		return string(secOrderID)
	}

	// Last resort: ConfirmID (664) — for Confirmation messages without TradeID.
	var confirmID quickfix.FIXString
	if err := msg.Body.GetField(tag.ConfirmID, &confirmID); err == nil && string(confirmID) != "" {
		return string(confirmID)
	}

	return ""
}

func buildOutgoingMessageJSON(msgType, tradeID string, body map[string]interface{}) domain.FixMessageJSON {
	return domain.FixMessageJSON{
		Direction:   "OUT",
		MsgType:     msgType,
		TradeID:     tradeID,
		Body:        body,
		ReceiveTime: time.Now(),
	}
}

// extractPartyByRole iterates the Parties repeating group inside a NoSides entry
// and returns the PartyID for the given target role.
func extractPartyByRole(side tradecapturereport.NoSides, targetRole enum.PartyRole) string {
	parties, err := side.GetNoPartyIDs()
	if err != nil {
		return ""
	}

	for i := 0; i < parties.Len(); i++ {
		party := parties.Get(i)

		role, roleErr := party.GetPartyRole()
		if roleErr != nil {
			continue
		}

		if role == targetRole {
			id, idErr := party.GetPartyID()
			if idErr != nil {
				return ""
			}

			return id
		}
	}

	return ""
}
