package address

import (
	"context"
	"fmt"

	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/common"
	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/vaa"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
)

type Repository struct {
	db     *mongo.Database
	logger *zap.Logger

	collections struct {
		parsedVaa *mongo.Collection
	}
}

func NewRepository(db *mongo.Database, logger *zap.Logger) *Repository {
	return &Repository{db: db,
		logger: logger.With(zap.String("module", "AddressRepository")),
		collections: struct {
			parsedVaa *mongo.Collection
		}{
			parsedVaa: db.Collection("parsedVaa"),
		},
	}
}

type GetAddressOverviewParams struct {
	Address string
	Skip    int64
	Limit   int64
}

func (r *Repository) GetAddressOverview(ctx context.Context, params *GetAddressOverviewParams) (*AddressOverview, error) {

	ids, err := common.FindVaasIdsByFromAddressOrToAddress(ctx, r.db, params.Address)
	if err != nil {
		return nil, err
	}

	if len(ids) == 0 {
		var result []*vaa.VaaDoc
		return &AddressOverview{Vaas: result}, nil
	}

	// build a query pipeline based on input parameters
	var pipeline mongo.Pipeline
	{
		// filter by list ids
		pipeline = append(pipeline, bson.D{{Key: "$match", Value: bson.D{{Key: "_id", Value: bson.D{{Key: "$in", Value: ids}}}}}})

		// specify sorting criteria
		pipeline = append(pipeline, bson.D{
			{"$sort", bson.D{bson.E{"indexedAt", -1}}},
		})

		// left outer join on the `vaas` collection
		pipeline = append(pipeline, bson.D{
			{"$lookup", bson.D{
				{"from", "vaas"},
				{"localField", "_id"},
				{"foreignField", "_id"},
				{"as", "vaas"},
			}},
		})

		// skip initial results
		if params.Skip != 0 {
			pipeline = append(pipeline, bson.D{
				{"$skip", params.Skip},
			})
		}

		// limit size of results
		pipeline = append(pipeline, bson.D{
			{"$limit", params.Limit},
		})
	}

	// execute the aggregation pipeline
	cur, err := r.collections.parsedVaa.Aggregate(ctx, pipeline)
	if err != nil {
		requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
		r.logger.Error("failed execute Aggregate command to get vaa with payload",
			zap.Error(err),
			zap.Any("params", params),
			zap.String("requestID", requestID),
		)
		return nil, err
	}

	// read results from cursor
	var documents []struct {
		ID   string       `bson:"_id"`
		Vaas []vaa.VaaDoc `bson:"vaas"`
	}
	err = cur.All(ctx, &documents)
	if err != nil {
		requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
		r.logger.Error("failed to decode cursor for account activity",
			zap.Error(err),
			zap.Any("params", params),
			zap.String("requestID", requestID),
		)
		return nil, err
	}

	// build the result and return
	var vaas []*vaa.VaaDoc
	for i := range documents {
		if len(documents[i].Vaas) != 1 {
			r.logger.Warn("expected exactly 1 vaa document",
				zap.Int("numVaas", len(documents[i].Vaas)),
				zap.String("_id", documents[i].ID),
			)
		}
		if len(documents[i].Vaas) > 1 {
			vaas = append(vaas, &documents[i].Vaas[0])
		}
	}
	return &AddressOverview{Vaas: vaas}, nil
}
