// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
//     https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// SPDX-License-Identifier: EUPL-1.2

package wallet

import (
	"dappco.re/go/core/blockchain/crypto"
	"dappco.re/go/core/blockchain/types"
)

// Scanner detects outputs belonging to a wallet within a transaction.
type Scanner interface {
	ScanTransaction(tx *types.Transaction, txHash types.Hash,
		blockHeight uint64, extra *TxExtra) ([]Transfer, error)
}

// V1Scanner implements Scanner for v0/v1 transactions using ECDH derivation.
// For each output it performs: derivation = viewSecret * txPubKey, then checks
// whether DerivePublicKey(derivation, i, spendPub) matches the output key.
type V1Scanner struct {
	account *Account
}

// NewV1Scanner returns a scanner bound to the given account.
func NewV1Scanner(acc *Account) *V1Scanner {
	return &V1Scanner{account: acc}
}

// ScanTransaction examines every output in tx and returns a Transfer for each
// output that belongs to the scanner's account. The caller must supply a
// pre-parsed TxExtra so that the tx public key is available.
func (s *V1Scanner) ScanTransaction(tx *types.Transaction, txHash types.Hash,
	blockHeight uint64, extra *TxExtra) ([]Transfer, error) {

	if extra.TxPublicKey.IsZero() {
		return nil, nil
	}

	derivation, err := crypto.GenerateKeyDerivation(
		[32]byte(extra.TxPublicKey),
		[32]byte(s.account.ViewSecretKey))
	if err != nil {
		return nil, nil
	}

	isCoinbase := len(tx.Vin) > 0 && tx.Vin[0].InputType() == types.InputTypeGenesis

	var transfers []Transfer
	for i, output := range tx.Vout {
		bare, ok := output.(types.TxOutputBare)
		if !ok {
			continue
		}

		expectedPublicKey, err := crypto.DerivePublicKey(
			derivation, uint64(i), [32]byte(s.account.SpendPublicKey))
		if err != nil {
			continue
		}

		targetKey, ok := bare.SpendKey()
		if !ok {
			continue
		}
		if types.PublicKey(expectedPublicKey) != targetKey {
			continue
		}

		ephemeralSecretKey, err := crypto.DeriveSecretKey(
			derivation, uint64(i), [32]byte(s.account.SpendSecretKey))
		if err != nil {
			continue
		}

		keyImage, err := crypto.GenerateKeyImage(expectedPublicKey, ephemeralSecretKey)
		if err != nil {
			continue
		}

		transfers = append(transfers, Transfer{
			TxHash:      txHash,
			OutputIndex: uint32(i),
			Amount:      bare.Amount,
			BlockHeight: blockHeight,
			EphemeralKey: KeyPair{
				Public: types.PublicKey(expectedPublicKey),
				Secret: types.SecretKey(ephemeralSecretKey),
			},
			KeyImage:   types.KeyImage(keyImage),
			Coinbase:   isCoinbase,
			UnlockTime: extra.UnlockTime,
		})
	}

	return transfers, nil
}
