package gateway

import (
	"context"
	"errors"
	"strings"

	"github.com/tech/sendico/chain/gateway/storage/model"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mservice"
	gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func (s *Service) createManagedWalletHandler(ctx context.Context, req *gatewayv1.CreateManagedWalletRequest) gsresponse.Responder[gatewayv1.CreateManagedWalletResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("nil request"))
	}

	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	if idempotencyKey == "" {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("idempotency_key is required"))
	}
	organizationRef := strings.TrimSpace(req.GetOrganizationRef())
	if organizationRef == "" {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("organization_ref is required"))
	}
	ownerRef := strings.TrimSpace(req.GetOwnerRef())
	if ownerRef == "" {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("owner_ref is required"))
	}

	asset := req.GetAsset()
	if asset == nil {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("asset is required"))
	}

	chainKey, _ := chainKeyFromEnum(asset.GetChain())
	if chainKey == "" {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
	}
	networkCfg, ok := s.networks[chainKey]
	if !ok {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
	}

	tokenSymbol := strings.ToUpper(strings.TrimSpace(asset.GetTokenSymbol()))
	if tokenSymbol == "" {
		return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("asset.token_symbol is required"))
	}
	contractAddress := strings.ToLower(strings.TrimSpace(asset.GetContractAddress()))
	if contractAddress == "" {
		contractAddress = resolveContractAddress(networkCfg.TokenConfigs, tokenSymbol)
		if contractAddress == "" {
			return gsresponse.InvalidArgument[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported token for chain"))
		}
	}

	walletRef := generateWalletRef()
	if s.keyManager == nil {
		return gsresponse.Internal[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.Internal("key manager not configured"))
	}

	keyInfo, err := s.keyManager.CreateManagedWalletKey(ctx, walletRef, chainKey)
	if err != nil {
		return gsresponse.Auto[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, err)
	}
	if keyInfo == nil || strings.TrimSpace(keyInfo.Address) == "" {
		return gsresponse.Internal[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.Internal("key manager returned empty address"))
	}

	wallet := &model.ManagedWallet{
		IdempotencyKey:  idempotencyKey,
		WalletRef:       walletRef,
		OrganizationRef: organizationRef,
		OwnerRef:        ownerRef,
		Network:         chainKey,
		TokenSymbol:     tokenSymbol,
		ContractAddress: contractAddress,
		DepositAddress:  strings.ToLower(keyInfo.Address),
		KeyReference:    keyInfo.KeyID,
		Status:          model.ManagedWalletStatusActive,
		Metadata:        cloneMetadata(req.GetMetadata()),
	}

	created, err := s.storage.Wallets().Create(ctx, wallet)
	if err != nil {
		if errors.Is(err, merrors.ErrDataConflict) {
			s.logger.Debug("wallet already exists", zap.String("wallet_ref", walletRef), zap.String("idempotency_key", idempotencyKey))
			return gsresponse.Success(&gatewayv1.CreateManagedWalletResponse{Wallet: s.toProtoManagedWallet(created)})
		}
		return gsresponse.Auto[gatewayv1.CreateManagedWalletResponse](s.logger, mservice.ChainGateway, err)
	}

	return gsresponse.Success(&gatewayv1.CreateManagedWalletResponse{Wallet: s.toProtoManagedWallet(created)})
}

func (s *Service) getManagedWalletHandler(ctx context.Context, req *gatewayv1.GetManagedWalletRequest) gsresponse.Responder[gatewayv1.GetManagedWalletResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[gatewayv1.GetManagedWalletResponse](s.logger, mservice.ChainGateway, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[gatewayv1.GetManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("nil request"))
	}
	walletRef := strings.TrimSpace(req.GetWalletRef())
	if walletRef == "" {
		return gsresponse.InvalidArgument[gatewayv1.GetManagedWalletResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("wallet_ref is required"))
	}
	wallet, err := s.storage.Wallets().Get(ctx, walletRef)
	if err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			return gsresponse.NotFound[gatewayv1.GetManagedWalletResponse](s.logger, mservice.ChainGateway, err)
		}
		return gsresponse.Auto[gatewayv1.GetManagedWalletResponse](s.logger, mservice.ChainGateway, err)
	}
	return gsresponse.Success(&gatewayv1.GetManagedWalletResponse{Wallet: s.toProtoManagedWallet(wallet)})
}

func (s *Service) listManagedWalletsHandler(ctx context.Context, req *gatewayv1.ListManagedWalletsRequest) gsresponse.Responder[gatewayv1.ListManagedWalletsResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[gatewayv1.ListManagedWalletsResponse](s.logger, mservice.ChainGateway, err)
	}
	filter := model.ManagedWalletFilter{}
	if req != nil {
		filter.OrganizationRef = strings.TrimSpace(req.GetOrganizationRef())
		filter.OwnerRef = strings.TrimSpace(req.GetOwnerRef())
		if asset := req.GetAsset(); asset != nil {
			filter.Network, _ = chainKeyFromEnum(asset.GetChain())
			filter.TokenSymbol = strings.TrimSpace(asset.GetTokenSymbol())
		}
		if page := req.GetPage(); page != nil {
			filter.Cursor = strings.TrimSpace(page.GetCursor())
			filter.Limit = page.GetLimit()
		}
	}

	result, err := s.storage.Wallets().List(ctx, filter)
	if err != nil {
		return gsresponse.Auto[gatewayv1.ListManagedWalletsResponse](s.logger, mservice.ChainGateway, err)
	}

	protoWallets := make([]*gatewayv1.ManagedWallet, 0, len(result.Items))
	for _, wallet := range result.Items {
		protoWallets = append(protoWallets, s.toProtoManagedWallet(wallet))
	}

	resp := &gatewayv1.ListManagedWalletsResponse{
		Wallets: protoWallets,
		Page:    &paginationv1.CursorPageResponse{NextCursor: result.NextCursor},
	}
	return gsresponse.Success(resp)
}

func (s *Service) getWalletBalanceHandler(ctx context.Context, req *gatewayv1.GetWalletBalanceRequest) gsresponse.Responder[gatewayv1.GetWalletBalanceResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[gatewayv1.GetWalletBalanceResponse](s.logger, mservice.ChainGateway, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[gatewayv1.GetWalletBalanceResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("nil request"))
	}
	walletRef := strings.TrimSpace(req.GetWalletRef())
	if walletRef == "" {
		return gsresponse.InvalidArgument[gatewayv1.GetWalletBalanceResponse](s.logger, mservice.ChainGateway, merrors.InvalidArgument("wallet_ref is required"))
	}
	balance, err := s.storage.Wallets().GetBalance(ctx, walletRef)
	if err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			return gsresponse.NotFound[gatewayv1.GetWalletBalanceResponse](s.logger, mservice.ChainGateway, err)
		}
		return gsresponse.Auto[gatewayv1.GetWalletBalanceResponse](s.logger, mservice.ChainGateway, err)
	}
	return gsresponse.Success(&gatewayv1.GetWalletBalanceResponse{Balance: toProtoWalletBalance(balance)})
}

func (s *Service) toProtoManagedWallet(wallet *model.ManagedWallet) *gatewayv1.ManagedWallet {
	if wallet == nil {
		return nil
	}
	asset := &gatewayv1.Asset{
		Chain:           chainEnumFromName(wallet.Network),
		TokenSymbol:     wallet.TokenSymbol,
		ContractAddress: wallet.ContractAddress,
	}
	return &gatewayv1.ManagedWallet{
		WalletRef:       wallet.WalletRef,
		OrganizationRef: wallet.OrganizationRef,
		OwnerRef:        wallet.OwnerRef,
		Asset:           asset,
		DepositAddress:  wallet.DepositAddress,
		Status:          managedWalletStatusToProto(wallet.Status),
		Metadata:        cloneMetadata(wallet.Metadata),
		CreatedAt:       timestamppb.New(wallet.CreatedAt.UTC()),
		UpdatedAt:       timestamppb.New(wallet.UpdatedAt.UTC()),
	}
}

func toProtoWalletBalance(balance *model.WalletBalance) *gatewayv1.WalletBalance {
	if balance == nil {
		return nil
	}
	return &gatewayv1.WalletBalance{
		Available:       cloneMoney(balance.Available),
		PendingInbound:  cloneMoney(balance.PendingInbound),
		PendingOutbound: cloneMoney(balance.PendingOutbound),
		CalculatedAt:    timestamppb.New(balance.CalculatedAt.UTC()),
	}
}
