package innerring

import (
	"context"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"go.uber.org/zap"
)

type (
	notaryConfig struct {
		disabled bool // true if notary disabled on chain
	}
)

const (
	// gasMultiplier defines how many times more the notary
	// balance must be compared to the GAS balance of the IR:
	//     notaryBalance = GASBalance * gasMultiplier.
	gasMultiplier = 3

	// gasDivisor defines what part of GAS balance (1/gasDivisor)
	// should be transferred to the notary service.
	gasDivisor = 2
)

func (s *Server) depositMainNotary(ctx context.Context) (tx util.Uint256, err error) {
	depositAmount, err := client.CalculateNotaryDepositAmount(s.mainnetClient, gasMultiplier, gasDivisor)
	if err != nil {
		return util.Uint256{}, fmt.Errorf("could not calculate main notary deposit amount: %w", err)
	}

	return s.mainnetClient.DepositNotary(
		ctx,
		depositAmount,
		uint32(s.epochDuration.Load())+notaryExtraBlocks,
	)
}

func (s *Server) depositSideNotary(ctx context.Context) (util.Uint256, error) {
	depositAmount, err := client.CalculateNotaryDepositAmount(s.morphClient, gasMultiplier, gasDivisor)
	if err != nil {
		return util.Uint256{}, fmt.Errorf("could not calculate side notary deposit amount: %w", err)
	}

	tx, _, err := s.morphClient.DepositEndlessNotary(ctx, depositAmount)
	return tx, err
}

func (s *Server) notaryHandler(ctx context.Context, _ event.Event) {
	if !s.mainNotaryConfig.disabled {
		_, err := s.depositMainNotary(ctx)
		if err != nil {
			s.log.Error(ctx, logs.InnerringCantMakeNotaryDepositInMainChain, zap.Error(err))
		}
	}

	if _, err := s.depositSideNotary(ctx); err != nil {
		s.log.Error(ctx, logs.InnerringCantMakeNotaryDepositInSideChain, zap.Error(err))
	}
}

func (s *Server) awaitMainNotaryDeposit(ctx context.Context, tx util.Uint256) error {
	return awaitNotaryDepositInClient(ctx, s.mainnetClient, tx)
}

func (s *Server) awaitSideNotaryDeposit(ctx context.Context, tx util.Uint256) error {
	return awaitNotaryDepositInClient(ctx, s.morphClient, tx)
}

func (s *Server) initNotary(ctx context.Context, deposit depositor, await awaiter, msg string) error {
	tx, err := deposit(ctx)
	if err != nil {
		return err
	}

	if tx.Equals(util.Uint256{}) {
		// non-error deposit with an empty TX hash means
		// that the deposit has already been made; no
		// need to wait it.
		s.log.Info(ctx, logs.InnerringNotaryDepositHasAlreadyBeenMade)
		return nil
	}

	s.log.Info(ctx, msg)

	return await(ctx, tx)
}

func awaitNotaryDepositInClient(ctx context.Context, cli *client.Client, txHash util.Uint256) error {
	for range notaryDepositTimeout {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		ok, err := cli.TxHalt(txHash)
		if err == nil {
			if ok {
				return nil
			}

			return errDepositFail
		}

		_ = cli.Wait(ctx, 1)
	}

	return errDepositTimeout
}

func notaryConfigs(withMainNotary bool) (main *notaryConfig) {
	main = new(notaryConfig)

	main.disabled = !withMainNotary

	return
}