package fix

import (
	"log/slog"
	"os"
	"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/quote"
	"quantex.com/qfixdpl/quickfix/store/file"
	"quantex.com/qfixdpl/src/app"
	"quantex.com/qfixdpl/src/common/tracerr"
	"quantex.com/qfixdpl/src/domain"
)

// 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
	orderStore domain.OrderStore
	notify     domain.Notifier
	cfg        app.FIXConfig
}

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

func (m *Manager) Start() error {
	fixApp := newApplication(m.notify)
	fixApp.onLogon = m.onLogon
	fixApp.onLogout = m.onLogout
	m.app = fixApp

	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 := quickfix.NewFileLogFactory(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()
}

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

// SendQuote implements domain.FIXSender.
func (m *Manager) SendQuote(clOrdID, quoteID, symbol, currency string, bidPx, offerPx, bidSize, offerSize decimal.Decimal) error {
	m.sessionsMu.RLock()
	var sessionID quickfix.SessionID
	var ok bool
	for _, sid := range m.sessions {
		sessionID = sid
		ok = true
		break
	}
	m.sessionsMu.RUnlock()

	if !ok {
		err := tracerr.Errorf("error sending quote: no active FIX session")
		log.Error().Msg(err.Error())

		return err
	}

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

	q.SetSymbol(symbol)
	q.SetQuoteID(quoteID)

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

	q.SetBidPx(bidPx, 8)
	q.SetOfferPx(offerPx, 8)

	if !bidSize.IsZero() {
		q.SetBidSize(bidSize, 8)
	}

	if !offerSize.IsZero() {
		q.SetOfferSize(offerSize, 8)
	}

	if err := quickfix.SendToTarget(q, sessionID); err != nil {
		err = tracerr.Errorf("error sending FIX quote: %s", err)
		log.Error().Msg(err.Error())

		return err
	}

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

	return nil
}
