package walletapiimp

import (
	"context"
	"time"

	api "github.com/tech/sendico/pkg/api/http"
	"github.com/tech/sendico/pkg/auth"
	"github.com/tech/sendico/pkg/db/chainassets"
	"github.com/tech/sendico/pkg/db/chainwalletroutes"
	"github.com/tech/sendico/pkg/discovery"
	"github.com/tech/sendico/pkg/merrors"
	msg "github.com/tech/sendico/pkg/messaging"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	eapi "github.com/tech/sendico/server/interface/api"
	mutil "github.com/tech/sendico/server/internal/mutil/param"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

const (
	cryptoRail             = "CRYPTO"
	defaultDialTimeout     = 5 * time.Second
	defaultCallTimeout     = 10 * time.Second
	discoveryLookupTimeout = 3 * time.Second
)

type WalletAPI struct {
	logger                mlogger.Logger
	discovery             *discovery.Client
	enf                   auth.Enforcer
	oph                   mutil.ParamHelper
	wph                   mutil.ParamHelper
	walletsPermissionRef  bson.ObjectID
	balancesPermissionRef bson.ObjectID
	assets                chainassets.DB
	routes                chainwalletroutes.DB

	// Gateway connection settings
	dialTimeout time.Duration
	callTimeout time.Duration
	insecure    bool
}

func (a *WalletAPI) Name() mservice.Type { return mservice.ChainWallets }

func (a *WalletAPI) Finish(ctx context.Context) error {
	if a.discovery != nil {
		a.discovery.Close()
	}
	return nil
}

func CreateAPI(apiCtx eapi.API) (*WalletAPI, error) {
	p := &WalletAPI{
		logger:      apiCtx.Logger().Named(mservice.Wallets),
		enf:         apiCtx.Permissions().Enforcer(),
		oph:         mutil.CreatePH(mservice.Organizations),
		wph:         mutil.CreatePH(mservice.Wallets),
		dialTimeout: defaultDialTimeout,
		callTimeout: defaultCallTimeout,
		insecure:    true,
	}

	var err error
	if p.assets, err = apiCtx.DBFactory().NewChainAsstesDB(); err != nil {
		p.logger.Warn("Failed to create asstes db", zap.Error(err))
		return nil, err
	}
	if p.routes, err = apiCtx.DBFactory().NewChainWalletRoutesDB(); err != nil {
		p.logger.Warn("Failed to create chain wallet routes db", zap.Error(err))
		return nil, err
	}

	walletsPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWallets)
	if err != nil {
		p.logger.Warn("Failed to fetch chain wallets permission policy description", zap.Error(err))
		return nil, err
	}
	p.walletsPermissionRef = walletsPolicy.ID

	balancesPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWalletBalances)
	if err != nil {
		p.logger.Warn("Failed to fetch chain wallet balances permission policy description", zap.Error(err))
		return nil, err
	}
	p.balancesPermissionRef = balancesPolicy.ID

	cfg := apiCtx.Config()
	if cfg == nil {
		p.logger.Error("Failed to fetch service configuration")
		return nil, merrors.InvalidArgument("No configuration provided")
	}

	// Apply gateway connection settings from config
	if gatewayCfg := cfg.ChainGateway; gatewayCfg != nil {
		if gatewayCfg.DialTimeoutSeconds > 0 {
			p.dialTimeout = time.Duration(gatewayCfg.DialTimeoutSeconds) * time.Second
		}
		if gatewayCfg.CallTimeoutSeconds > 0 {
			p.callTimeout = time.Duration(gatewayCfg.CallTimeoutSeconds) * time.Second
		}
		p.insecure = gatewayCfg.Insecure
	}

	// Initialize discovery client
	if err := p.initDiscoveryClient(cfg); err != nil {
		p.logger.Warn("Failed to initialize discovery client", zap.Error(err))
		// Not fatal - we can still work without discovery
	}

	apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listWallets)
	apiCtx.Register().AccountHandler(p.Name(), p.wph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getWalletBalance)
	apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Post, p.create)

	return p, nil
}

func (a *WalletAPI) initDiscoveryClient(cfg *eapi.Config) error {
	if cfg == nil || cfg.Mw == nil {
		return nil
	}
	msgCfg := cfg.Mw.Messaging
	if msgCfg.Driver == "" {
		return nil
	}
	broker, err := msg.CreateMessagingBroker(a.logger.Named("discovery_bus"), &msgCfg)
	if err != nil {
		return err
	}
	client, err := discovery.NewClient(a.logger, broker, nil, a.Name())
	if err != nil {
		return err
	}
	a.discovery = client
	return nil
}
