package wallet

import (
	"context"
	"errors"
	"strings"

	"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
	"github.com/tech/sendico/gateway/chain/storage/model"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	"github.com/tech/sendico/pkg/merrors"
	pkgmodel "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.uber.org/zap"
)

type createManagedWalletCommand struct {
	deps Deps
}

func NewCreateManagedWallet(deps Deps) *createManagedWalletCommand {
	return &createManagedWalletCommand{deps: deps}
}

func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.CreateManagedWalletRequest) gsresponse.Responder[chainv1.CreateManagedWalletResponse] {
	if err := c.deps.EnsureRepository(ctx); err != nil {
		c.deps.Logger.Warn("Repository unavailable", zap.Error(err))
		return gsresponse.Unavailable[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
	}
	if req == nil {
		c.deps.Logger.Warn("Nil request")
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("nil request"))
	}

	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	if idempotencyKey == "" {
		c.deps.Logger.Warn("Missing idempotency key")
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("idempotency_key is required"))
	}
	organizationRef := strings.TrimSpace(req.GetOrganizationRef())
	if organizationRef == "" {
		c.deps.Logger.Warn("Missing organization ref")
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("organization_ref is required"))
	}
	ownerRef := strings.TrimSpace(req.GetOwnerRef())

	asset := req.GetAsset()
	if asset == nil {
		c.deps.Logger.Warn("Missing asset")
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("asset is required"))
	}

	chainKey := shared.ChainKeyFromEnum(asset.GetChain())
	networkCfg, ok := c.deps.Networks.Network(chainKey)
	if !ok {
		c.deps.Logger.Warn("Unsupported chain in config", zap.String("chain", string(chainKey)))
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
	}
	if c.deps.Drivers == nil {
		c.deps.Logger.Warn("Chain drivers missing", zap.String("chain", string(chainKey)))
		return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("chain drivers not configured"))
	}
	chainDriver, err := c.deps.Drivers.Driver(chainKey)
	if err != nil {
		c.deps.Logger.Warn("Unsupported chain driver", zap.String("chain", string(chainKey)), zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
	}

	tokenSymbol := strings.ToUpper(strings.TrimSpace(asset.GetTokenSymbol()))
	if tokenSymbol == "" {
		c.deps.Logger.Warn("Missing token symbol")
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("asset.token_symbol is required"))
	}
	contractAddress := strings.ToLower(strings.TrimSpace(asset.GetContractAddress()))
	if contractAddress == "" {
		if !strings.EqualFold(tokenSymbol, networkCfg.NativeToken) {
			contractAddress = shared.ResolveContractAddress(networkCfg.TokenConfigs, tokenSymbol)
			if contractAddress == "" {
				c.deps.Logger.Warn("Unsupported token", zap.String("token", tokenSymbol), zap.String("chain", string(chainKey)))
				return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported token for chain"))
			}
		}
	}

	walletRef := shared.GenerateWalletRef()
	if c.deps.KeyManager == nil {
		c.deps.Logger.Warn("Key manager missing")
		return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("key manager not configured"))
	}

	keyInfo, err := c.deps.KeyManager.CreateManagedWalletKey(ctx, walletRef, chainKey)
	if err != nil {
		c.deps.Logger.Warn("Key manager error", zap.Error(err))
		return gsresponse.Auto[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
	}
	if keyInfo == nil || strings.TrimSpace(keyInfo.Address) == "" {
		c.deps.Logger.Warn("Key manager returned empty address")
		return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("key manager returned empty address"))
	}
	depositAddress, err := chainDriver.FormatAddress(keyInfo.Address)
	if err != nil {
		c.deps.Logger.Warn("Invalid derived deposit address", zap.String("wallet_ref", walletRef), zap.Error(err))
		return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

	metadata := shared.CloneMetadata(req.GetMetadata())
	desc := req.GetDescribable()
	name := strings.TrimSpace(desc.GetName())
	if name == "" {
		name = strings.TrimSpace(metadata["name"])
	}
	var description *string
	if desc != nil && desc.Description != nil {
		if trimmed := strings.TrimSpace(desc.GetDescription()); trimmed != "" {
			description = &trimmed
		}
	}
	if description == nil {
		if trimmed := strings.TrimSpace(metadata["description"]); trimmed != "" {
			description = &trimmed
		}
	}
	if name == "" {
		name = walletRef
	}

	wallet := &model.ManagedWallet{
		Describable: pkgmodel.Describable{
			Name:        name,
			Description: description,
		},
		IdempotencyKey:  idempotencyKey,
		WalletRef:       walletRef,
		OrganizationRef: organizationRef,
		OwnerRef:        ownerRef,
		Network:         chainKey,
		TokenSymbol:     tokenSymbol,
		ContractAddress: contractAddress,
		DepositAddress:  depositAddress,
		KeyReference:    keyInfo.KeyID,
		Status:          model.ManagedWalletStatusActive,
		Metadata:        metadata,
	}
	if description != nil {
		wallet.Description = description
	}

	created, err := c.deps.Storage.Wallets().Create(ctx, wallet)
	if err != nil {
		if errors.Is(err, merrors.ErrDataConflict) {
			c.deps.Logger.Debug("Wallet already exists", zap.String("wallet_ref", walletRef), zap.String("idempotency_key", idempotencyKey))
			return gsresponse.Success(&chainv1.CreateManagedWalletResponse{Wallet: toProtoManagedWallet(created)})
		}
		c.deps.Logger.Warn("Storage create failed", zap.Error(err), zap.String("wallet_ref", walletRef))
		return gsresponse.Auto[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
	}

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