package walletapiimp

import (
	"context"
	"fmt"
	"os"
	"strings"
	"time"

	chaingatewayclient "github.com/tech/sendico/gateway/chain/client"
	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/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	eapi "github.com/tech/sendico/server/interface/api"
	mutil "github.com/tech/sendico/server/internal/mutil/param"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.uber.org/zap"
)

type WalletAPI struct {
	logger                mlogger.Logger
	chainGateway          chainWalletClient
	enf                   auth.Enforcer
	oph                   mutil.ParamHelper
	wph                   mutil.ParamHelper
	walletsPermissionRef  primitive.ObjectID
	balancesPermissionRef primitive.ObjectID
	assets                chainassets.DB
}

type chainWalletClient interface {
	CreateManagedWallet(ctx context.Context, req *chainv1.CreateManagedWalletRequest) (*chainv1.CreateManagedWalletResponse, error)
	ListManagedWallets(ctx context.Context, req *chainv1.ListManagedWalletsRequest) (*chainv1.ListManagedWalletsResponse, error)
	GetWalletBalance(ctx context.Context, req *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error)
	Close() error
}

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

func (a *WalletAPI) Finish(ctx context.Context) error {
	if a.chainGateway != nil {
		if err := a.chainGateway.Close(); err != nil {
			a.logger.Warn("Failed to close chain gateway client", zap.Error(err))
		}
	}
	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),
	}

	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
	}

	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")
	}
	if err := p.initChainGateway(cfg.ChainGateway); err != nil {
		p.logger.Error("Failed to initialize chain gateway client", zap.Error(err))
		return nil, err
	}

	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) initChainGateway(cfg *eapi.ChainGatewayConfig) error {
	if cfg == nil {
		return merrors.InvalidArgument("chain gateway configuration is not provided")
	}

	cfg.Address = strings.TrimSpace(cfg.Address)
	if cfg.Address == "" {
		cfg.Address = strings.TrimSpace(os.Getenv(cfg.AddressEnv))
	}
	if cfg.Address == "" {
		return merrors.InvalidArgument(fmt.Sprintf("chain gateway address is not specified and address env %s is empty", cfg.AddressEnv))
	}

	clientCfg := chaingatewayclient.Config{
		Address:     cfg.Address,
		DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
		CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
		Insecure:    cfg.Insecure,
	}

	client, err := chaingatewayclient.New(context.Background(), clientCfg)
	if err != nil {
		return err
	}

	a.chainGateway = client
	return nil
}
