package fix

import (
	"log/slog"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/rs/zerolog/log"
	"github.com/shopspring/decimal"

	"quantex.com/qfixdpl/quickfix"
	"quantex.com/qfixdpl/quickfix/gen/enum"
	"quantex.com/qfixdpl/quickfix/gen/field"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/executionack"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/executionreport"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quote"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoteack"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoterequest"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quoteresponse"
	"quantex.com/qfixdpl/quickfix/gen/fix50sp2/quotestatusreport"
	filelog "quantex.com/qfixdpl/quickfix/log/file"
	"quantex.com/qfixdpl/quickfix/store/file"
	"quantex.com/qfixdpl/src/app"
	"quantex.com/qfixdpl/src/common/tracerr"
	"quantex.com/qfixdpl/src/domain"
)

type listTrade struct {
	QuoteReqID    string
	TradeID       string
	ListID        string
	Symbol        string
	SecurityIDSrc enum.SecurityIDSource
	Currency      string
	Side          enum.Side
	OrderQty      decimal.Decimal
	SettlDate     string
	Price         decimal.Decimal
	OwnerTraderID string
	SessionID     quickfix.SessionID
	Status        domain.TradeStatus
}

// Manager wraps the QuickFIX initiator and implements domain.FIXSender.
type Manager struct {
	initiator  *quickfix.Initiator
	app        *application
	sessionsMu sync.RWMutex
	sessions   map[string]quickfix.SessionID
	tradesMu   sync.RWMutex
	trades     map[string]*listTrade
	store      domain.PersistenceStore
	notify     domain.Notifier
	cfg        app.FIXConfig
}

func NewManager(cfg app.FIXConfig, store domain.PersistenceStore, notify domain.Notifier) *Manager {
	return &Manager{
		sessions: make(map[string]quickfix.SessionID),
		trades:   make(map[string]*listTrade),
		store:    store,
		notify:   notify,
		cfg:      cfg,
	}
}

func (m *Manager) Start() error {
	fixApp := newApplication(m.notify)
	fixApp.onLogon = m.onLogon
	fixApp.onLogout = m.onLogout
	fixApp.onQuoteRequest = m.handleQuoteRequest
	fixApp.onQuoteAck = m.handleQuoteAck
	fixApp.onQuoteResponse = m.handleQuoteResponse
	fixApp.onExecutionReport = m.handleExecutionReport
	fixApp.onExecutionAck = m.handleExecutionAck
	fixApp.onRawMessage = m.handleRawMessage
	m.app = fixApp

	if err := m.loadTrades(); err != nil {
		slog.Error("failed to load active trades from DB, starting with empty state", "error", err)
	}

	f, err := os.Open(m.cfg.SettingsFile)
	if err != nil {
		err = tracerr.Errorf("error opening FIX settings file %q: %s", m.cfg.SettingsFile, err)
		log.Error().Msg(err.Error())

		return err
	}
	defer f.Close()

	settings, err := quickfix.ParseSettings(f)
	if err != nil {
		err = tracerr.Errorf("error parsing FIX settings: %s", err)
		log.Error().Msg(err.Error())

		return err
	}

	storeFactory := file.NewStoreFactory(settings)
	logFactory, err := filelog.NewLogFactory(settings)
	if err != nil {
		err = tracerr.Errorf("error creating file log factory: %s", err)
		log.Error().Msg(err.Error())

		return err
	}

	initiator, err := quickfix.NewInitiator(fixApp, storeFactory, settings, logFactory)
	if err != nil {
		err = tracerr.Errorf("error creating FIX initiator: %s", err)
		log.Error().Msg(err.Error())

		return err
	}

	m.initiator = initiator

	if err = m.initiator.Start(); err != nil {
		err = tracerr.Errorf("error starting FIX initiator: %s", err)
		log.Error().Msg(err.Error())

		return err
	}

	slog.Info("FIX initiator started", "settings", m.cfg.SettingsFile)

	return nil
}

func (m *Manager) Stop() {
	if m.initiator != nil {
		m.initiator.Stop()
		slog.Info("FIX initiator stopped")
	}
}

func (m *Manager) onLogon(sessionID quickfix.SessionID) {
	m.sessionsMu.Lock()
	m.sessions[sessionID.String()] = sessionID
	m.sessionsMu.Unlock()

	// Assign the new session to all recovered trades that have no session yet.
	// This covers the case where the service was restarted mid-trade: loadTrades()
	// reconstructs the trade data but cannot recover the SessionID from the DB.
	// Since this is a single-session initiator, all active trades belong to this session.
	m.tradesMu.Lock()
	for _, trade := range m.trades {
		if trade.Status == domain.TradeStatusActive && trade.SessionID == (quickfix.SessionID{}) {
			trade.SessionID = sessionID
		}
	}
	m.tradesMu.Unlock()
}

func (m *Manager) onLogout(sessionID quickfix.SessionID) {
	m.sessionsMu.Lock()
	delete(m.sessions, sessionID.String())
	m.sessionsMu.Unlock()
}

// sendQuoteStatusReport sends a QuoteStatusReport (35=AI) to acknowledge the incoming QuoteRequest.
func (m *Manager) sendQuoteStatusReport(quoteReqID, ownerTraderID string, sessionID quickfix.SessionID) error {
	qsr := quotestatusreport.New(
		field.NewTransactTime(time.Now()),
		field.NewQuoteStatus(enum.QuoteStatus_ACCEPTED),
	)
	qsr.SetQuoteReqID(quoteReqID)
	qsr.SetQuoteID(quoteReqID)
	qsr.SetSymbol("[N/A]")
	if ownerTraderID != "" {
		qsr.SetOwnerTraderID(ownerTraderID)
	}

	return quickfix.SendToTarget(qsr, sessionID)
}

// sendTradeRequestAck sends a QuoteStatusReport (35=AI) to acknowledge a trade request (TRDREQACK).
func (m *Manager) sendTradeRequestAck(quoteReqID, quoteRespID string, sessionID quickfix.SessionID) error {
	qsr := quotestatusreport.New(
		field.NewTransactTime(time.Now()),
		field.NewQuoteStatus(enum.QuoteStatus_ACCEPTED),
	)
	qsr.SetQuoteReqID(quoteReqID)
	qsr.SetQuoteRespID(quoteRespID)
	qsr.SetSymbol("[N/A]")

	return quickfix.SendToTarget(qsr, sessionID)
}

// sendExecutionAck sends an ExecutionAck (35=BN) to acknowledge an incoming ExecutionReport.
func (m *Manager) sendExecutionAck(orderID, clOrdID, execID string, sessionID quickfix.SessionID) error {
	bn := executionack.New(
		field.NewOrderID(orderID),
		field.NewExecID(execID),
		field.NewExecAckStatus(enum.ExecAckStatus_ACCEPTED),
	)
	bn.SetClOrdID(clOrdID)
	bn.SetSymbol("[N/A]")
	bn.SetTransactTime(time.Now())

	return quickfix.SendToTarget(bn, sessionID)
}

// handleQuoteRequest auto-responds to an incoming QuoteRequest with a QuoteStatusReport (acknowledge) followed by a Quote at price 99.6.
func (m *Manager) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID quickfix.SessionID) {
	quoteReqID, err := msg.GetQuoteReqID()
	if err != nil {
		slog.Error("handleQuoteRequest: missing QuoteReqID", "error", err.Error())
		return
	}

	// Validate LST_ prefix for List Trading flow.
	if !strings.HasPrefix(quoteReqID, "LST_") {
		slog.Warn("handleQuoteRequest: QuoteReqID missing LST_ prefix, ignoring", "quoteReqID", quoteReqID)
		return
	}

	var (
		symbol, currency, ownerTraderID, settlDate, listID, negotiationType string
		side                                                                enum.Side
		secIDSource                                                         enum.SecurityIDSource
		orderQty                                                            decimal.Decimal
	)

	relatedSyms, relErr := msg.GetNoRelatedSym()
	if relErr == nil && relatedSyms.Len() > 0 {
		sym := relatedSyms.Get(0)
		symbol, _ = sym.GetSecurityID()
		secIDSource, _ = sym.GetSecurityIDSource()
		currency, _ = sym.GetCurrency()
		side, _ = sym.GetSide()
		ownerTraderID, _ = sym.GetOwnerTraderID()
		orderQty, _ = sym.GetOrderQty()
		settlDate, _ = sym.GetSettlDate()
		listID, _ = sym.GetListID()
		negotiationType, _ = sym.GetNegotiationType()
	}

	if listID == "" {
		slog.Warn("handleQuoteRequest: missing ListID", "quoteReqID", quoteReqID)
		return
	}

	if negotiationType != "RFQ" {
		slog.Warn("handleQuoteRequest: unexpected NegotiationType", "quoteReqID", quoteReqID, "negotiationType", negotiationType)
		return
	}

	// Step 1: Send QuoteStatusReport (35=AI) to acknowledge the inquiry.
	if ackErr := m.sendQuoteStatusReport(quoteReqID, ownerTraderID, sessionID); ackErr != nil {
		slog.Error("handleQuoteRequest: failed to send QuoteStatusReport", "quoteReqID", quoteReqID, "error", ackErr.Error())
		return
	}
	slog.Info("QuoteStatusReport sent", "quoteReqID", quoteReqID)

	// Step 2: Build and send Quote (35=S) with price.
	price := decimal.NewFromFloat(99.6)

	sIDSource := enum.SecurityIDSource_ISIN_NUMBER
	if secIDSource == enum.SecurityIDSource_CUSIP {
		sIDSource = enum.SecurityIDSource_CUSIP
	}

	quoteID := quoteReqID
	q := quote.New(
		field.NewQuoteID(quoteID),
		field.NewQuoteType(enum.QuoteType_SEND_QUOTE),
		field.NewTransactTime(time.Now()),
	)

	q.SetSymbol("[N/A]")
	q.SetSecurityID(symbol)
	q.SetSecurityIDSource(sIDSource)
	q.SetQuoteReqID(quoteReqID)

	if currency != "" {
		q.SetCurrency(currency)
	}

	if !orderQty.IsZero() {
		q.SetOrderQty(orderQty, 0)
	}

	if settlDate != "" {
		q.SetSettlDate(settlDate)
	}

	q.SetPrice(price, 8)

	if side == enum.Side_BUY {
		q.SetOfferPx(price, 8)
		q.SetSide(enum.Side_BUY)
	} else {
		q.SetBidPx(price, 8)
		q.SetSide(enum.Side_SELL)
	}

	q.SetPriceType(enum.PriceType_PERCENTAGE)

	if ownerTraderID != "" {
		q.SetOwnerTraderID(ownerTraderID)
	}

	if sendErr := quickfix.SendToTarget(q, sessionID); sendErr != nil {
		slog.Error("handleQuoteRequest: failed to send quote", "quoteReqID", quoteReqID, "error", sendErr.Error())
		return
	}

	slog.Info("Quote sent", "quoteReqID", quoteReqID, "quoteID", quoteID, "symbol", symbol)

	// Store trade state for subsequent steps.
	m.tradesMu.Lock()
	m.trades[quoteReqID] = &listTrade{
		QuoteReqID:    quoteReqID,
		ListID:        listID,
		Symbol:        symbol,
		SecurityIDSrc: sIDSource,
		Currency:      currency,
		Side:          side,
		OrderQty:      orderQty,
		SettlDate:     settlDate,
		Price:         price,
		OwnerTraderID: ownerTraderID,
		SessionID:     sessionID,
		Status:        domain.TradeStatusActive,
	}
	m.tradesMu.Unlock()

	// Persist structured message (outside mutex).
	m.persistMessage(quoteReqID, parseQuoteRequest(msg))

	// Persist outgoing QuoteStatusReport.
	m.persistMessage(quoteReqID, buildOutgoingMessageJSON("AI", quoteReqID, map[string]interface{}{
		"QuoteReqID":    quoteReqID,
		"QuoteStatus":   string(enum.QuoteStatus_ACCEPTED),
		"OwnerTraderID": ownerTraderID,
	}))

	// Persist outgoing Quote.
	m.persistMessage(quoteReqID, buildOutgoingMessageJSON("S", quoteReqID, map[string]interface{}{
		"QuoteReqID": quoteReqID,
		"QuoteID":    quoteID,
		"Symbol":     symbol,
		"Side":       string(side),
		"Price":      price.String(),
		"OrderQty":   orderQty.String(),
		"Currency":   currency,
		"SettlDate":  settlDate,
	}))
}

// handleQuoteAck handles an incoming QuoteAck (35=CW).
func (m *Manager) handleQuoteAck(msg quoteack.QuoteAck, sessionID quickfix.SessionID) {
	quoteReqID, _ := msg.GetQuoteReqID()
	status, _ := msg.GetQuoteAckStatus()
	text, _ := msg.GetText()

	m.persistMessage(quoteReqID, parseQuoteAck(msg))

	// QuoteAckStatus only has two defined values in TW DPL:
	//   1 = Accepted — quote delivered to client.
	//   2 = Rejected — format error, late quote, viewer busy, or race condition.
	if status == enum.QuoteAckStatus_REJECTED {
		slog.Error("handleQuoteAck: quote rejected by TW", "quoteReqID", quoteReqID, "quoteAckStatus", string(status), "text", text)

		m.tradesMu.Lock()
		if t, ok := m.trades[quoteReqID]; ok {
			t.Status = domain.TradeStatusRejected
		}
		m.tradesMu.Unlock()

		return
	}

	slog.Info("handleQuoteAck: accepted", "quoteReqID", quoteReqID)
}

// handleQuoteResponse handles an incoming QuoteResponse (35=AJ).
// Supports _TRDREQ (trade request), _TRDEND (trade ended), and _TRDSUMM (trade summary) suffixes.
func (m *Manager) handleQuoteResponse(msg quoteresponse.QuoteResponse, sessionID quickfix.SessionID) {
	quoteReqID, _ := msg.GetQuoteReqID()
	quoteRespID, _ := msg.GetQuoteRespID()

	slog.Info("handleQuoteResponse", "quoteReqID", quoteReqID, "quoteRespID", quoteRespID)

	isTrdReq := strings.HasSuffix(quoteRespID, "_TRDREQ")
	isTrdEnd := strings.HasSuffix(quoteRespID, "_TRDEND")
	isTrdSumm := strings.HasSuffix(quoteRespID, "_TRDSUMM")
	isListEnd := strings.HasSuffix(quoteRespID, "_LISTEND")

	if !isTrdReq && !isTrdEnd && !isTrdSumm && !isListEnd {
		slog.Info("handleQuoteResponse: QuoteRespID has unrecognized suffix, ignoring", "quoteRespID", quoteRespID)
		return
	}

	// TODO: handle 694=2 (Counter) for flow 8.5 (Client Countering) in the future.

	// Always send ACK regardless of whether the trade is in our map.
	// TW will keep retrying until it receives an ACK.
	if ackErr := m.sendTradeRequestAck(quoteReqID, quoteRespID, sessionID); ackErr != nil {
		slog.Error("handleQuoteResponse: failed to send ACK", "quoteReqID", quoteReqID, "quoteRespID", quoteRespID, "error", ackErr.Error())
		return
	}
	slog.Info("QuoteResponse ACK sent", "quoteReqID", quoteReqID, "quoteRespID", quoteRespID)

	// Persist incoming QuoteResponse.
	m.persistMessage(quoteReqID, parseQuoteResponse(msg))

	// Persist outgoing ACK.
	m.persistMessage(quoteReqID, buildOutgoingMessageJSON("AI", quoteReqID, map[string]interface{}{
		"QuoteReqID":  quoteReqID,
		"QuoteRespID": quoteRespID,
		"QuoteStatus": string(enum.QuoteStatus_ACCEPTED),
	}))

	// _TRDSUMM is the final message — mark trade as completed.
	if isTrdSumm {
		slog.Info("Trade summary received, marking completed", "quoteReqID", quoteReqID)
		m.tradesMu.Lock()
		if t, ok := m.trades[quoteReqID]; ok {
			t.Status = domain.TradeStatusCompleted
		}
		m.tradesMu.Unlock()
	}
}

// handleExecutionReport handles an incoming ExecutionReport (35=8).
// In flow 8.4 (List Trading), the dealer NEVER sends an ExecutionReport — only TW does.
// In TW DPL, ClOrdID (Tag 11) always equals the original QuoteReqID (Tag 131),
// so we use clOrdID directly as the map lookup key.
func (m *Manager) handleExecutionReport(msg executionreport.ExecutionReport, sessionID quickfix.SessionID) {
	execID, _ := msg.GetExecID()
	orderID, _ := msg.GetOrderID()
	clOrdID, _ := msg.GetClOrdID()
	execType, _ := msg.GetExecType()
	ordStatus, _ := msg.GetOrdStatus()
	listID, _ := msg.GetListID()

	slog.Info("handleExecutionReport received",
		"execID", execID, "orderID", orderID, "clOrdID", clOrdID,
		"execType", string(execType), "ordStatus", string(ordStatus), "listID", listID,
	)

	// Send ExecutionAck (35=BN) for every incoming ExecutionReport from TW.
	if ackErr := m.sendExecutionAck(orderID, clOrdID, execID, sessionID); ackErr != nil {
		slog.Error("handleExecutionReport: failed to send ExecutionAck", "execID", execID, "error", ackErr.Error())
	} else {
		slog.Info("ExecutionAck sent", "execID", execID)
	}

	switch {
	case strings.Contains(execID, "_LISTEND"):
		slog.Info("List ended (due-in closed), awaiting trade result from TW",
			"execID", execID, "clOrdID", clOrdID)

	case strings.Contains(execID, "_TRDEND"):
		slog.Info("Trade ended", "execID", execID, "clOrdID", clOrdID)

	case strings.Contains(execID, "_TRDSUMM"):
		slog.Info("Trade summary received from TW, marking completed",
			"execID", execID, "clOrdID", clOrdID, "ordStatus", string(ordStatus), "listID", listID)

		m.tradesMu.Lock()
		if t, ok := m.trades[clOrdID]; ok {
			t.Status = domain.TradeStatusCompleted
		}
		m.tradesMu.Unlock()

	case execType == enum.ExecType_TRADE:
		slog.Info("Trade result received from TW",
			"execID", execID, "clOrdID", clOrdID, "ordStatus", string(ordStatus), "listID", listID)
	}

	// Persist incoming ExecutionReport.
	m.persistMessage(clOrdID, parseExecutionReport(msg))

	// Persist outgoing ExecutionAck.
	m.persistMessage(clOrdID, buildOutgoingMessageJSON("BN", clOrdID, map[string]interface{}{
		"OrderID":       orderID,
		"ExecID":        execID,
		"ClOrdID":       clOrdID,
		"ExecAckStatus": string(enum.ExecAckStatus_ACCEPTED),
	}))

	// If the ExecutionReport carries a TradeID, persist it in qfixdpl_logs for cross-service correlation.
	tradeID, _ := msg.GetTradeID()
	if tradeID != "" {
		m.tradesMu.Lock()
		if t, ok := m.trades[clOrdID]; ok {
			t.TradeID = tradeID
		}
		m.tradesMu.Unlock()

		if err := m.store.UpdateLogTradeID(clOrdID, tradeID); err != nil {
			slog.Error("handleExecutionReport: failed to update log trade_id", "clOrdID", clOrdID, "tradeID", tradeID, "error", err)
		}
	}
}

// handleExecutionAck handles an incoming ExecutionAck (35=BN) from TW.
func (m *Manager) handleExecutionAck(msg executionack.ExecutionAck, sessionID quickfix.SessionID) {
	// Logged in application.go, no further action needed.
}

// GetTrades returns a snapshot of only active trades.
func (m *Manager) GetTrades() []domain.ListTrade {
	m.tradesMu.RLock()
	defer m.tradesMu.RUnlock()

	trades := make([]domain.ListTrade, 0, len(m.trades))
	for _, t := range m.trades {
		if t.Status != domain.TradeStatusActive {
			continue
		}

		trades = append(trades, toListTrade(t))
	}

	return trades
}

// GetAllTrades returns a snapshot of all trades (active, rejected, completed).
func (m *Manager) GetAllTrades() []domain.ListTrade {
	m.tradesMu.RLock()
	defer m.tradesMu.RUnlock()

	trades := make([]domain.ListTrade, 0, len(m.trades))
	for _, t := range m.trades {
		trades = append(trades, toListTrade(t))
	}

	return trades
}

func toListTrade(t *listTrade) domain.ListTrade {
	return domain.ListTrade{
		QuoteReqID:    t.QuoteReqID,
		TradeID:       t.TradeID,
		ListID:        t.ListID,
		Symbol:        t.Symbol,
		SecurityIDSrc: string(t.SecurityIDSrc),
		Currency:      t.Currency,
		Side:          string(t.Side),
		OrderQty:      t.OrderQty.String(),
		SettlDate:     t.SettlDate,
		Price:         t.Price.String(),
		OwnerTraderID: t.OwnerTraderID,
		Status:        t.Status,
	}
}

// handleRawMessage persists raw FIX message strings to the logs table.
func (m *Manager) handleRawMessage(direction string, msg *quickfix.Message) {
	quoteReqID := extractIdentifier(msg)

	if err := m.store.SaveLog(domain.LogEntry{
		QuoteReqID: quoteReqID,
		RawMsg:     "[" + direction + "] " + msg.String(),
	}); err != nil {
		slog.Error("failed to persist raw log", "error", err)
	}
}

// persistMessage saves a structured FIX message to the messages table.
func (m *Manager) persistMessage(quoteReqID string, fixJSON domain.FixMessageJSON) {
	if err := m.store.SaveMessage(domain.TradeMessage{
		QuoteReqID: quoteReqID,
		JMessage:   fixJSON,
	}); err != nil {
		slog.Error("failed to persist message", "msgType", fixJSON.MsgType, "quoteReqID", quoteReqID, "error", err)
	}
}

// loadTrades reconstructs all trades and their states from today's messages in the database.
func (m *Manager) loadTrades() error {
	messages, err := m.store.GetTodayMessages()
	if err != nil {
		return err
	}

	trades := make(map[string]*listTrade)

	for _, msg := range messages {
		switch msg.JMessage.MsgType {
		case "R": // QuoteRequest -> trade is born
			if !strings.HasPrefix(msg.QuoteReqID, "LST_") {
				continue
			}

			body := msg.JMessage.Body

			nt, _ := body["NegotiationType"].(string)
			if nt != "RFQ" {
				continue
			}

			listID, _ := body["ListID"].(string)
			if listID == "" {
				continue
			}

			trade := &listTrade{
				QuoteReqID: msg.QuoteReqID,
				ListID:     listID,
				Status:     domain.TradeStatusActive,
			}

			if v, ok := body["SecurityID"].(string); ok {
				trade.Symbol = v
			}

			if v, ok := body["Currency"].(string); ok {
				trade.Currency = v
			}

			if v, ok := body["Side"].(string); ok {
				trade.Side = enum.Side(v)
			}

			if v, ok := body["OrderQty"].(string); ok {
				trade.OrderQty, _ = decimal.NewFromString(v)
			}

			if v, ok := body["SettlDate"].(string); ok {
				trade.SettlDate = v
			}

			if v, ok := body["OwnerTraderID"].(string); ok {
				trade.OwnerTraderID = v
			}

			trades[msg.QuoteReqID] = trade

		case "CW": // QuoteAck — only status "2" (Rejected) marks the trade as rejected
			body := msg.JMessage.Body
			quoteAckStatus, _ := body["QuoteAckStatus"].(string)

			if quoteAckStatus == string(enum.QuoteAckStatus_REJECTED) {
				if t, ok := trades[msg.QuoteReqID]; ok {
					t.Status = domain.TradeStatusRejected
				}
			}

		case "AJ": // QuoteResponse — _TRDSUMM means trade is done (flow 8.6)
			body := msg.JMessage.Body
			quoteRespID, _ := body["QuoteRespID"].(string)

			if strings.HasSuffix(quoteRespID, "_TRDSUMM") {
				if t, ok := trades[msg.QuoteReqID]; ok {
					t.Status = domain.TradeStatusCompleted
				}
			}

		case "8": // ExecutionReport — _TRDSUMM means trade is done (flow 8.4)
			body := msg.JMessage.Body
			execID, _ := body["ExecID"].(string)
			clOrdID, _ := body["ClOrdID"].(string)

			if tid, ok := body["TradeID"].(string); ok && tid != "" {
				if t, ok := trades[clOrdID]; ok {
					t.TradeID = tid
				}
			}

			if strings.Contains(execID, "_TRDSUMM") {
				if t, ok := trades[clOrdID]; ok {
					t.Status = domain.TradeStatusCompleted
				}
			}
		}
	}

	active := 0
	for _, t := range trades {
		if t.Status == domain.TradeStatusActive {
			active++
		}
	}

	m.trades = trades
	slog.Info("recovery completed", "totalTrades", len(trades), "activeTrades", active)

	return nil
}
