package gateway

import (
	"context"
	"strings"

	"github.com/tech/sendico/chain/gateway/internal/keymanager"
	"github.com/tech/sendico/chain/gateway/storage"
	"github.com/tech/sendico/chain/gateway/storage/model"
	"github.com/tech/sendico/pkg/api/routers"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	clockpkg "github.com/tech/sendico/pkg/clock"
	msg "github.com/tech/sendico/pkg/messaging"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"google.golang.org/grpc"
)

type serviceError string

func (e serviceError) Error() string {
	return string(e)
}

var (
	errStorageUnavailable = serviceError("chain_gateway: storage not initialised")
)

// Service implements the ChainGatewayService RPC contract.
type Service struct {
	logger   mlogger.Logger
	storage  storage.Repository
	producer msg.Producer
	clock    clockpkg.Clock

	networks      map[string]Network
	serviceWallet ServiceWallet
	keyManager    keymanager.Manager
	executor      TransferExecutor

	gatewayv1.UnimplementedChainGatewayServiceServer
}

// NewService constructs the chain gateway service skeleton.
func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Producer, opts ...Option) *Service {
	svc := &Service{
		logger:   logger.Named("chain_gateway"),
		storage:  repo,
		producer: producer,
		clock:    clockpkg.System{},
		networks: map[string]Network{},
	}

	initMetrics()

	for _, opt := range opts {
		if opt != nil {
			opt(svc)
		}
	}

	if svc.clock == nil {
		svc.clock = clockpkg.System{}
	}
	if svc.networks == nil {
		svc.networks = map[string]Network{}
	}

	return svc
}

// Register wires the service onto the provided gRPC router.
func (s *Service) Register(router routers.GRPC) error {
	return router.Register(func(reg grpc.ServiceRegistrar) {
		gatewayv1.RegisterChainGatewayServiceServer(reg, s)
	})
}

func (s *Service) CreateManagedWallet(ctx context.Context, req *gatewayv1.CreateManagedWalletRequest) (*gatewayv1.CreateManagedWalletResponse, error) {
	return executeUnary(ctx, s, "CreateManagedWallet", s.createManagedWalletHandler, req)
}

func (s *Service) GetManagedWallet(ctx context.Context, req *gatewayv1.GetManagedWalletRequest) (*gatewayv1.GetManagedWalletResponse, error) {
	return executeUnary(ctx, s, "GetManagedWallet", s.getManagedWalletHandler, req)
}

func (s *Service) ListManagedWallets(ctx context.Context, req *gatewayv1.ListManagedWalletsRequest) (*gatewayv1.ListManagedWalletsResponse, error) {
	return executeUnary(ctx, s, "ListManagedWallets", s.listManagedWalletsHandler, req)
}

func (s *Service) GetWalletBalance(ctx context.Context, req *gatewayv1.GetWalletBalanceRequest) (*gatewayv1.GetWalletBalanceResponse, error) {
	return executeUnary(ctx, s, "GetWalletBalance", s.getWalletBalanceHandler, req)
}

func (s *Service) SubmitTransfer(ctx context.Context, req *gatewayv1.SubmitTransferRequest) (*gatewayv1.SubmitTransferResponse, error) {
	return executeUnary(ctx, s, "SubmitTransfer", s.submitTransferHandler, req)
}

func (s *Service) GetTransfer(ctx context.Context, req *gatewayv1.GetTransferRequest) (*gatewayv1.GetTransferResponse, error) {
	return executeUnary(ctx, s, "GetTransfer", s.getTransferHandler, req)
}

func (s *Service) ListTransfers(ctx context.Context, req *gatewayv1.ListTransfersRequest) (*gatewayv1.ListTransfersResponse, error) {
	return executeUnary(ctx, s, "ListTransfers", s.listTransfersHandler, req)
}

func (s *Service) EstimateTransferFee(ctx context.Context, req *gatewayv1.EstimateTransferFeeRequest) (*gatewayv1.EstimateTransferFeeResponse, error) {
	return executeUnary(ctx, s, "EstimateTransferFee", s.estimateTransferFeeHandler, req)
}

func (s *Service) ensureRepository(ctx context.Context) error {
	if s.storage == nil {
		return errStorageUnavailable
	}
	return s.storage.Ping(ctx)
}

func executeUnary[TReq any, TResp any](ctx context.Context, svc *Service, method string, handler func(context.Context, *TReq) gsresponse.Responder[TResp], req *TReq) (*TResp, error) {
	start := svc.clock.Now()
	resp, err := gsresponse.Unary(svc.logger, mservice.ChainGateway, handler)(ctx, req)
	observeRPC(method, err, svc.clock.Now().Sub(start))
	return resp, err
}

func resolveContractAddress(tokens []TokenContract, symbol string) string {
	upper := strings.ToUpper(symbol)
	for _, token := range tokens {
		if strings.EqualFold(token.Symbol, upper) && token.ContractAddress != "" {
			return strings.ToLower(token.ContractAddress)
		}
	}
	return ""
}

func generateWalletRef() string {
	return primitive.NewObjectID().Hex()
}

func generateTransferRef() string {
	return primitive.NewObjectID().Hex()
}

func chainKeyFromEnum(chain gatewayv1.ChainNetwork) (string, gatewayv1.ChainNetwork) {
	if name, ok := gatewayv1.ChainNetwork_name[int32(chain)]; ok {
		key := strings.ToLower(strings.TrimPrefix(name, "CHAIN_NETWORK_"))
		return key, chain
	}
	return "", gatewayv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
}

func chainEnumFromName(name string) gatewayv1.ChainNetwork {
	if name == "" {
		return gatewayv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
	}
	upper := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, " ", "_"), "-", "_"))
	key := "CHAIN_NETWORK_" + upper
	if val, ok := gatewayv1.ChainNetwork_value[key]; ok {
		return gatewayv1.ChainNetwork(val)
	}
	return gatewayv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
}

func managedWalletStatusToProto(status model.ManagedWalletStatus) gatewayv1.ManagedWalletStatus {
	switch status {
	case model.ManagedWalletStatusActive:
		return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_ACTIVE
	case model.ManagedWalletStatusSuspended:
		return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_SUSPENDED
	case model.ManagedWalletStatusClosed:
		return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_CLOSED
	default:
		return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_STATUS_UNSPECIFIED
	}
}

func transferStatusToModel(status gatewayv1.TransferStatus) model.TransferStatus {
	switch status {
	case gatewayv1.TransferStatus_TRANSFER_PENDING:
		return model.TransferStatusPending
	case gatewayv1.TransferStatus_TRANSFER_SIGNING:
		return model.TransferStatusSigning
	case gatewayv1.TransferStatus_TRANSFER_SUBMITTED:
		return model.TransferStatusSubmitted
	case gatewayv1.TransferStatus_TRANSFER_CONFIRMED:
		return model.TransferStatusConfirmed
	case gatewayv1.TransferStatus_TRANSFER_FAILED:
		return model.TransferStatusFailed
	case gatewayv1.TransferStatus_TRANSFER_CANCELLED:
		return model.TransferStatusCancelled
	default:
		return ""
	}
}

func transferStatusToProto(status model.TransferStatus) gatewayv1.TransferStatus {
	switch status {
	case model.TransferStatusPending:
		return gatewayv1.TransferStatus_TRANSFER_PENDING
	case model.TransferStatusSigning:
		return gatewayv1.TransferStatus_TRANSFER_SIGNING
	case model.TransferStatusSubmitted:
		return gatewayv1.TransferStatus_TRANSFER_SUBMITTED
	case model.TransferStatusConfirmed:
		return gatewayv1.TransferStatus_TRANSFER_CONFIRMED
	case model.TransferStatusFailed:
		return gatewayv1.TransferStatus_TRANSFER_FAILED
	case model.TransferStatusCancelled:
		return gatewayv1.TransferStatus_TRANSFER_CANCELLED
	default:
		return gatewayv1.TransferStatus_TRANSFER_STATUS_UNSPECIFIED
	}
}
