// Package fix implements the QuickFIX initiator application.
package fix

import (
	"log/slog"

	"quantex.com/qfixdpl/quickfix"
	"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/tag"
	"quantex.com/qfixdpl/src/domain"
)

type application struct {
	router           *quickfix.MessageRouter
	notifier         domain.Notifier
	onLogon           func(quickfix.SessionID)
	onLogout          func(quickfix.SessionID)
	onQuote           func(quote.Quote, quickfix.SessionID)
	onQuoteRequest    func(quoterequest.QuoteRequest, quickfix.SessionID)
	onQuoteAck        func(quoteack.QuoteAck, quickfix.SessionID)
	onQuoteResponse   func(quoteresponse.QuoteResponse, quickfix.SessionID)
	onExecutionReport func(executionreport.ExecutionReport, quickfix.SessionID)
	onExecutionAck    func(executionack.ExecutionAck, quickfix.SessionID)
	onRawMessage      func(direction string, msg *quickfix.Message)
}

func newApplication(n domain.Notifier) *application {
	app := &application{
		router:   quickfix.NewMessageRouter(),
		notifier: n,
	}

	app.router.AddRoute(quote.Route(app.handleQuote))
	app.router.AddRoute(quoteack.Route(app.handleQuoteAck))
	app.router.AddRoute(quoterequest.Route(app.handleQuoteRequest))
	app.router.AddRoute(quoteresponse.Route(app.handleQuoteResponse))
	app.router.AddRoute(executionack.Route(app.handleExecutionAck))
	app.router.AddRoute(executionreport.Route(app.handleExecutionReport))

	return app
}

func (a *application) OnCreate(sessionID quickfix.SessionID) {
	slog.Info("FIX session created", "session", sessionID.String())
}

func (a *application) OnLogon(sessionID quickfix.SessionID) {
	slog.Info("FIX session logged on", "session", sessionID.String())
	if a.onLogon != nil {
		a.onLogon(sessionID)
	}
}

func (a *application) OnLogout(sessionID quickfix.SessionID) {
	slog.Info("FIX session logged out", "session", sessionID.String())

	go a.notifier.SendMsg(domain.MessageChannelError, "Logout", domain.MessageStatusWarning, nil)

	if a.onLogout != nil {
		a.onLogout(sessionID)
	}
}

func (a *application) ToAdmin(_ *quickfix.Message, _ quickfix.SessionID) {}

func (a *application) ToApp(msg *quickfix.Message, _ quickfix.SessionID) error {
	if a.onRawMessage != nil {
		a.onRawMessage("OUT", msg)
	}

	return nil
}

func (a *application) FromAdmin(_ *quickfix.Message, _ quickfix.SessionID) quickfix.MessageRejectError {
	return nil
}

func (a *application) FromApp(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	beginString, _ := msg.Header.GetBytes(tag.BeginString)
	msgType, _ := msg.Header.GetBytes(tag.MsgType)

	var applVerID quickfix.FIXString
	msg.Header.GetField(tag.ApplVerID, &applVerID)

	slog.Info("FIX FromApp received",
		"beginString", string(beginString),
		"msgType", string(msgType),
		"applVerID", string(applVerID),
		"session", sessionID.String(),
		"rawMsg", msg.String(),
	)

	if a.onRawMessage != nil {
		a.onRawMessage("IN", msg)
	}

	rejErr := a.router.Route(msg, sessionID)
	if rejErr != nil {
		slog.Error("FIX FromApp routing failed",
			"msgType", string(msgType),
			"error", rejErr.Error(),
			"isBusinessReject", rejErr.IsBusinessReject(),
		)
	}

	return rejErr
}

func (a *application) handleQuoteRequest(msg quoterequest.QuoteRequest, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	quoteReqID, err := msg.GetQuoteReqID()
	if err != nil {
		slog.Error("QuoteRequest missing QuoteReqID", "error", err.Error())
		return err
	}

	slog.Info("QuoteRequest received",
		"quoteReqID", quoteReqID,
		"session", sessionID.String(),
	)

	if a.onQuoteRequest != nil {
		a.onQuoteRequest(msg, sessionID)
	}

	return nil
}

func (a *application) handleQuoteAck(msg quoteack.QuoteAck, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	quoteReqID, _ := msg.GetQuoteReqID()
	quoteID, _ := msg.GetQuoteID()
	status, _ := msg.GetQuoteAckStatus()
	text, _ := msg.GetText()

	slog.Info("QuoteAck received",
		"quoteReqID", quoteReqID,
		"quoteID", quoteID,
		"quoteAckStatus", status,
		"text", text,
		"session", sessionID.String(),
	)

	if a.onQuoteAck != nil {
		a.onQuoteAck(msg, sessionID)
	}

	return nil
}

func (a *application) handleQuote(msg quote.Quote, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	quoteID, err := msg.GetQuoteID()
	if err != nil {
		return err
	}

	symbol, _ := msg.GetSymbol()

	slog.Info("Quote received", "quoteID", quoteID, "symbol", symbol, "session", sessionID.String())

	if a.onQuote != nil {
		a.onQuote(msg, sessionID)
	}

	return nil
}

func (a *application) handleQuoteResponse(msg quoteresponse.QuoteResponse, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	quoteRespID, _ := msg.GetQuoteRespID()
	quoteReqID, _ := msg.GetQuoteReqID()
	quoteRespType, _ := msg.GetQuoteRespType()

	slog.Info("QuoteResponse received",
		"quoteRespID", quoteRespID,
		"quoteReqID", quoteReqID,
		"quoteRespType", quoteRespType,
		"session", sessionID.String(),
	)

	if a.onQuoteResponse != nil {
		a.onQuoteResponse(msg, sessionID)
	}

	return nil
}

func (a *application) handleExecutionAck(msg executionack.ExecutionAck, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	execID, _ := msg.GetExecID()
	orderID, _ := msg.GetOrderID()
	status, _ := msg.GetExecAckStatus()

	slog.Info("ExecutionAck received",
		"execID", execID,
		"orderID", orderID,
		"execAckStatus", status,
		"session", sessionID.String(),
	)

	if a.onExecutionAck != nil {
		a.onExecutionAck(msg, sessionID)
	}

	return nil
}

func (a *application) handleExecutionReport(msg executionreport.ExecutionReport, sessionID quickfix.SessionID) quickfix.MessageRejectError {
	execID, _ := msg.GetExecID()
	orderID, _ := msg.GetOrderID()
	listID, _ := msg.GetListID()
	execType, _ := msg.GetExecType()
	ordStatus, _ := msg.GetOrdStatus()

	slog.Info("ExecutionReport received",
		"execID", execID,
		"orderID", orderID,
		"listID", listID,
		"execType", execType,
		"ordStatus", ordStatus,
		"session", sessionID.String(),
	)

	if a.onExecutionReport != nil {
		a.onExecutionReport(msg, sessionID)
	}

	return nil
}
