package chainwalletroutesdb

import (
	"context"
	"errors"

	"github.com/tech/sendico/pkg/db/repository"
	ri "github.com/tech/sendico/pkg/db/repository/index"
	"github.com/tech/sendico/pkg/db/template"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.uber.org/zap"
)

type ChainWalletRoutesDB struct {
	template.DBImp[*model.ChainWalletRoute]
}

func Create(logger mlogger.Logger, db *mongo.Database) (*ChainWalletRoutesDB, error) {
	p := &ChainWalletRoutesDB{
		DBImp: *template.Create[*model.ChainWalletRoute](logger, mservice.WalletRoutes, db),
	}

	if err := p.Repository.CreateIndex(&ri.Definition{
		Name:   "idx_org_wallet_unique",
		Unique: true,
		Keys: []ri.Key{
			{Field: "organizationRef", Sort: ri.Asc},
			{Field: "walletRef", Sort: ri.Asc},
		},
	}); err != nil {
		p.Logger.Error("Failed to create unique organization/wallet route index", zap.Error(err))
		return nil, err
	}

	if err := p.Repository.CreateIndex(&ri.Definition{
		Name: "idx_wallet_ref",
		Keys: []ri.Key{
			{Field: "walletRef", Sort: ri.Asc},
		},
	}); err != nil {
		p.Logger.Error("Failed to create wallet route lookup index", zap.Error(err))
		return nil, err
	}

	return p, nil
}

func (db *ChainWalletRoutesDB) Get(ctx context.Context, organizationRef string, walletRef string) (*model.ChainWalletRoute, error) {
	org := model.ChainWalletRoute{OrganizationRef: organizationRef, WalletRef: walletRef}
	org.Normalize()
	if org.OrganizationRef == "" || org.WalletRef == "" {
		return nil, merrors.InvalidArgument("wallet route requires organizationRef and walletRef")
	}

	var route model.ChainWalletRoute
	query := repository.Query().
		Filter(repository.Field("organizationRef"), org.OrganizationRef).
		Filter(repository.Field("walletRef"), org.WalletRef)
	return &route, db.FindOne(ctx, query, &route)
}

func (db *ChainWalletRoutesDB) Upsert(ctx context.Context, route *model.ChainWalletRoute) error {
	if route == nil {
		return merrors.InvalidArgument("wallet route is nil")
	}
	route.Normalize()
	if route.OrganizationRef == "" || route.WalletRef == "" {
		return merrors.InvalidArgument("wallet route requires organizationRef and walletRef")
	}
	if route.Network == "" && route.GatewayID == "" {
		return merrors.InvalidArgument("wallet route requires network or gatewayId")
	}

	existing, err := db.Get(ctx, route.OrganizationRef, route.WalletRef)
	if err != nil {
		if !errors.Is(err, merrors.ErrNoData) {
			return err
		}
		if createErr := db.Create(ctx, route); createErr != nil {
			if errors.Is(createErr, merrors.ErrDataConflict) {
				existing, err = db.Get(ctx, route.OrganizationRef, route.WalletRef)
				if err != nil {
					return err
				}
			} else {
				return createErr
			}
		} else {
			return nil
		}
	}

	changed := false
	if route.Network != "" && existing.Network != route.Network {
		existing.Network = route.Network
		changed = true
	}
	if route.GatewayID != "" && existing.GatewayID != route.GatewayID {
		existing.GatewayID = route.GatewayID
		changed = true
	}
	if !changed {
		return nil
	}

	return db.Update(ctx, existing)
}
