package watcher

import (
	"context"
	"encoding/hex"
	"strconv"
	"strings"
	"time"

	"github.com/wormhole-foundation/wormhole-explorer/common/domain"
	"github.com/wormhole-foundation/wormhole-explorer/common/utils"
	"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/config"
	"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
	"github.com/wormhole-foundation/wormhole/sdk/vaa"
	"go.uber.org/zap"
)

const (
	//Transaction status
	TxStatusSuccess      = "0x1"
	TxStatusFailReverted = "0x0"
)

type EVMParams struct {
	ChainID          vaa.ChainID
	Blockchain       string
	SizeBlocks       uint8
	WaitSeconds      uint16
	InitialBlock     int64
	MethodsByAddress map[string][]config.BlockchainMethod
}

type EVMAddressesParams struct {
	ChainID      vaa.ChainID
	Blockchain   string
	SizeBlocks   uint8
	WaitSeconds  uint16
	InitialBlock int64
}

type EvmGetStatusFunc func() (string, error)

type EvmTransaction struct {
	Hash           string
	From           string
	To             string
	Status         EvmGetStatusFunc
	BlockNumber    string
	BlockTimestamp string
	Input          string
}

// get gloabal transaction status from evm blockchain status code.
func getTxStatus(status string) string {
	switch status {
	case TxStatusSuccess:
		return domain.DstTxStatusConfirmed
	case TxStatusFailReverted:
		return domain.DstTxStatusFailedToProcess
	default:
		return domain.DstTxStatusUnkonwn
	}
}

// get method ID from transaction input
func getMethodIDByInput(input string) string {
	if len(input) < 10 {
		return config.MethodUnkown
	}
	return input[0:10]
}

// get the input and extract the method signature and VAA
func parseInput(input string) (*vaa.VAA, error) {
	// remove the first 64 characters plus 0x
	input = input[138:]
	vaaBytes, err := hex.DecodeString(input)
	if err != nil {
		return nil, err
	}

	vaa, err := vaa.Unmarshal(vaaBytes)
	if err != nil {
		return nil, err
	}

	return vaa, nil
}

func getBlockNumber(s string, logger *zap.Logger) string {
	value, err := strconv.ParseInt(utils.Remove0x(s), 16, 64)
	if err != nil {
		logger.Error("cannot convert to int", zap.Error(err))
		return s
	}
	return strconv.FormatInt(value, 10)
}

func getTimestamp(s string, logger *zap.Logger) *time.Time {
	value, err := strconv.ParseInt(utils.Remove0x(s), 16, 64)
	if err != nil {
		logger.Error("cannot convert to timestamp", zap.Error(err))
		return nil
	}
	tm := time.Unix(value, 0)
	return &tm
}

func processTransaction(ctx context.Context, chainID vaa.ChainID, tx *EvmTransaction, methodsByAddress map[string][]config.BlockchainMethod, repository *storage.Repository, logger *zap.Logger) {
	// get methodID from the transaction.
	txMethod := getMethodIDByInput(tx.Input)

	log := logger.With(
		zap.String("txHash", tx.Hash),
		zap.String("method", txMethod),
		zap.String("block", tx.BlockNumber))
	log.Debug("new tx")

	// get methods by address.
	methods, ok := methodsByAddress[strings.ToLower(tx.To)]
	if !ok {
		log.Debug("method unkown")
		return
	}

	for _, method := range methods {
		if method.ID == txMethod {
			// get vaa from transaction input
			vaa, err := parseInput(tx.Input)
			if err != nil {
				log.Error("cannot parse VAA", zap.Error(err))
				return

			}

			// get evm blockchain status code
			txStatusCode, err := tx.Status()
			if err != nil {
				log.Error("cannot get tx status", zap.Error(err))
				return
			}

			updatedAt := time.Now()
			globalTx := storage.TransactionUpdate{
				ID: vaa.MessageID(),
				Destination: storage.DestinationTx{
					ChainID:     chainID,
					Status:      getTxStatus(txStatusCode),
					Method:      method.Name,
					TxHash:      utils.Remove0x(tx.Hash),
					To:          tx.To,
					From:        tx.From,
					BlockNumber: getBlockNumber(tx.BlockNumber, log),
					Timestamp:   getTimestamp(tx.BlockTimestamp, log),
					UpdatedAt:   &updatedAt,
				},
			}

			// update global transaction and check if it should be updated.
			updateGlobalTransaction(ctx, chainID, globalTx, repository, log)
			break
		}
	}
}
