package exmonitor

import (
	"context"
	"github.com/exchain/go-exchain/op-supervisor/config"
	"log"
	"math/big"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

const (
	mongodbName = "exmarket"
	klinePrefix = "exchange_kline_"
	tradePrefix = "exchange_trade_"
)

type marketService struct {
	mongoClient *mongo.Client
}

func NewMarketService(conf *config.Config) MarketService {
	// Initialize MongoDB client
	mongoURI := "" // conf.MongoDBURI
	clientOptions := options.Client().ApplyURI(mongoURI)
	client, err := mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		log.Fatalf("Failed to connect to MongoDB: %v", err)
	}
	return &marketService{mongoClient: client}
}

func (s *marketService) FindAllKLine(symbol, period string) []*KLine {
	collection := s.mongoClient.Database(mongodbName).Collection(klinePrefix + symbol + "_" + period)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	opts := options.Find().SetSort(bson.D{{Key: "time", Value: -1}}).SetLimit(1000)
	cursor, err := collection.Find(ctx, bson.M{}, opts)
	if err != nil {
		log.Fatalf("Error finding KLine: %v", err)
	}
	defer cursor.Close(ctx)

	var kLines []*KLine
	if err := cursor.All(ctx, &kLines); err != nil {
		log.Fatalf("Error decoding KLine: %v", err)
	}
	return kLines
}

func (s *marketService) FindAllKLineByTimeRange(symbol string, fromTime, toTime int64, period string) []*KLine {
	collection := s.mongoClient.Database(mongodbName).Collection(klinePrefix + symbol + "_" + period)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	filter := bson.M{
		"time": bson.M{
			"$gte": fromTime,
			"$lte": toTime,
		},
	}
	opts := options.Find().SetSort(bson.D{{Key: "time", Value: 1}})
	cursor, err := collection.Find(ctx, filter, opts)
	if err != nil {
		log.Fatalf("Error finding KLine by time range: %v", err)
	}
	defer cursor.Close(ctx)

	var kLines []*KLine
	if err := cursor.All(ctx, &kLines); err != nil {
		log.Fatalf("Error decoding KLine: %v", err)
	}
	return kLines
}

func (s *marketService) FindFirstTrade(symbol string, fromTime, toTime int64) *ExchangeTrade {
	return s.findTrade(symbol, fromTime, toTime, bson.D{{Key: "time", Value: 1}})
}

func (s *marketService) FindLastTrade(symbol string, fromTime, toTime int64) *ExchangeTrade {
	return s.findTrade(symbol, fromTime, toTime, bson.D{{Key: "time", Value: -1}})
}

func (s *marketService) findTrade(symbol string, fromTime, toTime int64, sortOrder bson.D) *ExchangeTrade {
	collection := s.mongoClient.Database(mongodbName).Collection(tradePrefix + symbol)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	filter := bson.M{
		"time": bson.M{
			"$gte": fromTime,
			"$lte": toTime,
		},
	}
	opts := options.FindOne().SetSort(sortOrder)
	var trade ExchangeTrade
	err := collection.FindOne(ctx, filter, opts).Decode(&trade)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			return nil
		}
		log.Fatalf("Error finding trade: %v", err)
	}
	return &trade
}

func (s *marketService) FindTradeByTimeRange(symbol string, timeStart, timeEnd int64) []*ExchangeTrade {
	collection := s.mongoClient.Database(mongodbName).Collection(tradePrefix + symbol)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	filter := bson.M{
		"time": bson.M{
			"$gte": timeStart,
			"$lt":  timeEnd,
		},
	}
	opts := options.Find().SetSort(bson.D{{Key: "time", Value: 1}})
	cursor, err := collection.Find(ctx, filter, opts)
	if err != nil {
		log.Fatalf("Error finding trades by time range: %v", err)
	}
	defer cursor.Close(ctx)

	var trades []*ExchangeTrade
	if err := cursor.All(ctx, &trades); err != nil {
		log.Fatalf("Error decoding trades: %v", err)
	}
	return trades
}

func (s *marketService) SaveKLine(symbol string, kLine *KLine) {
	collection := s.mongoClient.Database(mongodbName).Collection(klinePrefix + symbol + "_" + kLine.Period)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	_, err := collection.InsertOne(ctx, kLine)
	if err != nil {
		log.Fatalf("Error saving KLine: %v", err)
	}
}

func (s *marketService) FindTradeVolume(symbol string, timeStart, timeEnd int64) *big.Float {
	collection := s.mongoClient.Database(mongodbName).Collection(klinePrefix + symbol + "_1min")
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	filter := bson.M{
		"time": bson.M{
			"$gt":  timeStart,
			"$lte": timeEnd,
		},
	}
	opts := options.Find().SetSort(bson.D{{Key: "time", Value: 1}})
	cursor, err := collection.Find(ctx, filter, opts)
	if err != nil {
		log.Fatalf("Error finding trade volume: %v", err)
	}
	defer cursor.Close(ctx)

	totalVolume := big.NewFloat(0)
	for cursor.Next(ctx) {
		var kLine KLine
		if err := cursor.Decode(&kLine); err != nil {
			log.Fatalf("Error decoding KLine: %v", err)
		}
		totalVolume = totalVolume.Add(totalVolume, kLine.Volume)
	}
	return totalVolume
}

func (s *marketService) NewHandler(symbol string) MarketHandler {
	return &mongoMarketHandler{
		client: s.mongoClient,
		db:     s.mongoClient.Database(mongodbName),
	}
}
