// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only

package xmrtaker

import (
	"testing"
	"time"

	ethtypes "github.com/ethereum/go-ethereum/core/types"
	"github.com/stretchr/testify/require"

	"github.com/athanorlabs/atomic-swap/coins"
	"github.com/athanorlabs/atomic-swap/common"
	"github.com/athanorlabs/atomic-swap/common/types"
	mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
	contracts "github.com/athanorlabs/atomic-swap/ethereum"
	"github.com/athanorlabs/atomic-swap/ethereum/watcher"
	"github.com/athanorlabs/atomic-swap/monero"
	"github.com/athanorlabs/atomic-swap/net/message"
	pcommon "github.com/athanorlabs/atomic-swap/protocol"
)

func lockXMRAndCheckForReadyLog(t *testing.T, s *swapState, xmrAddr *mcrypto.Address) {
	// backend simulates the xmrmaker's instance
	backend := newBackend(t)
	monero.MineMinXMRBalance(t, backend.XMRClient(), coins.MoneroToPiconero(coins.StrToDecimal("1")))

	amt := s.expectedPiconeroAmount()
	amtu64, err := amt.Uint64()
	require.NoError(t, err)
	// lock xmr
	transfer, err := backend.XMRClient().Transfer(s.ctx, xmrAddr, 0, amt, monero.MinSpendConfirmations)
	require.NoError(t, err)
	require.Equal(t, transfer.Amount, amtu64)
	t.Logf("Transferred %d pico XMR (fees %d) to account %s", transfer.Amount, transfer.Fee, xmrAddr)
	t.Logf("Transfer was mined at block=%d with %d confirmations", transfer.Height, transfer.Confirmations)

	// assert that ready() is called, setup contract watcher
	ethHeader, err := backend.ETHClient().Raw().HeaderByNumber(backend.Ctx(), nil)
	require.NoError(t, err)
	logReadyCh := make(chan ethtypes.Log)

	readyTopic := common.GetTopic(contracts.ReadyEventSignature)
	readyWatcher := watcher.NewEventFilter(
		s.Backend.Ctx(),
		s.Backend.ETHClient().Raw(),
		s.Backend.SwapCreatorAddr(),
		ethHeader.Number,
		readyTopic,
		logReadyCh,
	)
	err = readyWatcher.Start()
	require.NoError(t, err)

	// goroutine in SendKeysMessage handler should handle the NotifyXMRLock message
	select {
	case log := <-logReadyCh:
		err = pcommon.CheckSwapID(&log, readyTopic, s.contractSwapID)
		require.NoError(t, err)
	case <-time.After(time.Second * 5):
		t.Fatalf("didn't get ready logs in time")
	}
}

func TestSwapState_handleEvent_EventETHClaimed(t *testing.T) {
	s, net := newTestSwapStateAndNet(t)
	defer s.cancel()
	s.SetSwapTimeout(time.Minute * 2)

	// invalid SendKeysMessage should result in an error
	msg := &message.SendKeysMessage{}
	err := s.HandleProtocolMessage(msg)
	require.ErrorIs(t, err, errMissingProvidedAmount)

	// handle valid SendKeysMessage
	msg = s.SendKeysMessage().(*message.SendKeysMessage)
	msg.PrivateViewKey = s.privkeys.ViewKey()
	msg.EthAddress = s.ETHClient().Address()
	msg.ProvidedAmount = s.providedAmount.AsStd()

	err = s.HandleProtocolMessage(msg)
	require.NoError(t, err)

	resp := net.LastSentMessage()
	require.NotNil(t, resp)
	require.Equal(t, message.NotifyETHLockedType, resp.Type())
	require.Equal(t, time.Minute*2, s.t2.Sub(s.t1))
	require.Equal(t, msg.PublicSpendKey.Hex(), s.xmrmakerPublicSpendKey.Hex())
	require.Equal(t, msg.PrivateViewKey.Hex(), s.xmrmakerPrivateViewKey.Hex())

	// simulate xmrmaker locking xmr
	kp := mcrypto.SumSpendAndViewKeys(s.pubkeys, s.pubkeys)
	xmrAddr := kp.Address(common.Mainnet)
	lockXMRAndCheckForReadyLog(t, s, xmrAddr)
	// give handleNotifyXMRLock some time to return, since the event watcher
	// sees the Ready event before swapState.ready() returns
	time.Sleep(time.Second * 2)
	require.Equal(t, EventETHClaimedType, s.nextExpectedEvent)
	require.Equal(t, types.ContractReady, s.info.Status)

	// simulate xmrmaker calling claim
	// call swap.Swap.Claim() w/ b.privkeys.sk, revealing XMRMaker's secret spend key
	secret := s.privkeys.SpendKeyBytes()
	sk, err := mcrypto.NewPrivateSpendKey(secret[:])
	require.NoError(t, err)

	// handled the claimed message should result in the monero wallet being created
	event := newEventETHClaimed(sk)
	s.eventCh <- event
	err = <-event.errCh
	require.NoError(t, err)
	require.Equal(t, types.CompletedSuccess, s.info.Status)
}
