// Copyright (c) 2014-2020 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcjson

import (
	"encoding/json"
	"fmt"

	"github.com/btcsuite/btcd/txscript"
)

// CreateWalletResult models the result of the createwallet command.
type CreateWalletResult struct {
	Name    string `json:"name"`
	Warning string `json:"warning"`
}

// embeddedAddressInfo includes all getaddressinfo output fields, excluding
// metadata and relation to the wallet.
//
// It represents the non-metadata/non-wallet fields for GetAddressInfo, as well
// as the precise fields for an embedded P2SH or P2WSH address.
type embeddedAddressInfo struct {
	Address             string                `json:"address"`
	ScriptPubKey        string                `json:"scriptPubKey"`
	Solvable            bool                  `json:"solvable"`
	Descriptor          *string               `json:"desc,omitempty"`
	IsScript            bool                  `json:"isscript"`
	IsChange            bool                  `json:"ischange"`
	IsWitness           bool                  `json:"iswitness"`
	WitnessVersion      int                   `json:"witness_version,omitempty"`
	WitnessProgram      *string               `json:"witness_program,omitempty"`
	ScriptType          *txscript.ScriptClass `json:"script,omitempty"`
	Hex                 *string               `json:"hex,omitempty"`
	PubKeys             *[]string             `json:"pubkeys,omitempty"`
	SignaturesRequired  *int                  `json:"sigsrequired,omitempty"`
	PubKey              *string               `json:"pubkey,omitempty"`
	IsCompressed        *bool                 `json:"iscompressed,omitempty"`
	HDMasterFingerprint *string               `json:"hdmasterfingerprint,omitempty"`
	Labels              []string              `json:"labels"`
}

// GetAddressInfoResult models the result of the getaddressinfo command. It
// contains information about a bitcoin address.
//
// Reference: https://bitcoincore.org/en/doc/0.20.0/rpc/wallet/getaddressinfo
//
// The GetAddressInfoResult has three segments:
//  1. General information about the address.
//  2. Metadata (Timestamp, HDKeyPath, HDSeedID) and wallet fields
//     (IsMine, IsWatchOnly).
//  3. Information about the embedded address in case of P2SH or P2WSH.
//     Same structure as (1).
type GetAddressInfoResult struct {
	embeddedAddressInfo
	IsMine      bool                 `json:"ismine"`
	IsWatchOnly bool                 `json:"iswatchonly"`
	Timestamp   *int                 `json:"timestamp,omitempty"`
	HDKeyPath   *string              `json:"hdkeypath,omitempty"`
	HDSeedID    *string              `json:"hdseedid,omitempty"`
	Embedded    *embeddedAddressInfo `json:"embedded,omitempty"`
}

// UnmarshalJSON provides a custom unmarshaller for GetAddressInfoResult.
// It is adapted to avoid creating a duplicate raw struct for unmarshalling
// the JSON bytes into.
//
// Reference: http://choly.ca/post/go-json-marshalling
func (e *GetAddressInfoResult) UnmarshalJSON(data []byte) error {
	// Step 1: Create type aliases of the original struct, including the
	// embedded one.
	type Alias GetAddressInfoResult
	type EmbeddedAlias embeddedAddressInfo

	// Step 2: Create an anonymous struct with raw replacements for the special
	// fields.
	aux := &struct {
		ScriptType *string `json:"script,omitempty"`
		Embedded   *struct {
			ScriptType *string `json:"script,omitempty"`
			*EmbeddedAlias
		} `json:"embedded,omitempty"`
		*Alias
	}{
		Alias: (*Alias)(e),
	}

	// Step 3: Unmarshal the data into the anonymous struct.
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	// Step 4: Convert the raw fields to the desired types
	var (
		sc  *txscript.ScriptClass
		err error
	)

	if aux.ScriptType != nil {
		sc, err = txscript.NewScriptClass(*aux.ScriptType)
		if err != nil {
			return err
		}
	}

	e.ScriptType = sc

	if aux.Embedded != nil {
		var (
			embeddedSc *txscript.ScriptClass
			err        error
		)

		if aux.Embedded.ScriptType != nil {
			embeddedSc, err = txscript.NewScriptClass(*aux.Embedded.ScriptType)
			if err != nil {
				return err
			}
		}

		e.Embedded = (*embeddedAddressInfo)(aux.Embedded.EmbeddedAlias)
		e.Embedded.ScriptType = embeddedSc
	}

	return nil
}

// GetTransactionDetailsResult models the details data from the gettransaction command.
//
// This models the "short" version of the ListTransactionsResult type, which
// excludes fields common to the transaction.  These common fields are instead
// part of the GetTransactionResult.
type GetTransactionDetailsResult struct {
	Account           string   `json:"account"`
	Address           string   `json:"address,omitempty"`
	Amount            float64  `json:"amount"`
	Category          string   `json:"category"`
	InvolvesWatchOnly bool     `json:"involveswatchonly,omitempty"`
	Fee               *float64 `json:"fee,omitempty"`
	Vout              uint32   `json:"vout"`
}

// GetTransactionResult models the data from the gettransaction command.
type GetTransactionResult struct {
	Amount          float64                       `json:"amount"`
	Fee             float64                       `json:"fee,omitempty"`
	Confirmations   int64                         `json:"confirmations"`
	BlockHash       string                        `json:"blockhash"`
	BlockIndex      int64                         `json:"blockindex"`
	BlockTime       int64                         `json:"blocktime"`
	TxID            string                        `json:"txid"`
	WalletConflicts []string                      `json:"walletconflicts"`
	Time            int64                         `json:"time"`
	TimeReceived    int64                         `json:"timereceived"`
	Details         []GetTransactionDetailsResult `json:"details"`
	Hex             string                        `json:"hex"`
}

type ScanningOrFalse struct {
	Value interface{}
}

type ScanProgress struct {
	Duration int     `json:"duration"`
	Progress float64 `json:"progress"`
}

// MarshalJSON implements the json.Marshaler interface
func (h ScanningOrFalse) MarshalJSON() ([]byte, error) {
	return json.Marshal(h.Value)
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (h *ScanningOrFalse) UnmarshalJSON(data []byte) error {
	var unmarshalled interface{}
	if err := json.Unmarshal(data, &unmarshalled); err != nil {
		return err
	}

	switch v := unmarshalled.(type) {
	case bool:
		h.Value = v
	case map[string]interface{}:
		h.Value = ScanProgress{
			Duration: int(v["duration"].(float64)),
			Progress: v["progress"].(float64),
		}
	default:
		return fmt.Errorf("invalid scanning value: %v", unmarshalled)
	}

	return nil
}

// GetWalletInfoResult models the result of the getwalletinfo command.
type GetWalletInfoResult struct {
	WalletName            string          `json:"walletname"`
	WalletVersion         int             `json:"walletversion"`
	TransactionCount      int             `json:"txcount"`
	KeyPoolOldest         int             `json:"keypoololdest"`
	KeyPoolSize           int             `json:"keypoolsize"`
	KeyPoolSizeHDInternal *int            `json:"keypoolsize_hd_internal,omitempty"`
	UnlockedUntil         *int            `json:"unlocked_until,omitempty"`
	PayTransactionFee     float64         `json:"paytxfee"`
	HDSeedID              *string         `json:"hdseedid,omitempty"`
	PrivateKeysEnabled    bool            `json:"private_keys_enabled"`
	AvoidReuse            bool            `json:"avoid_reuse"`
	Scanning              ScanningOrFalse `json:"scanning"`
}

// InfoWalletResult models the data returned by the wallet server getinfo
// command.
type InfoWalletResult struct {
	Version         int32   `json:"version"`
	ProtocolVersion int32   `json:"protocolversion"`
	WalletVersion   int32   `json:"walletversion"`
	Balance         float64 `json:"balance"`
	Blocks          int32   `json:"blocks"`
	TimeOffset      int64   `json:"timeoffset"`
	Connections     int32   `json:"connections"`
	Proxy           string  `json:"proxy"`
	Difficulty      float64 `json:"difficulty"`
	TestNet         bool    `json:"testnet"`
	KeypoolOldest   int64   `json:"keypoololdest"`
	KeypoolSize     int32   `json:"keypoolsize"`
	UnlockedUntil   int64   `json:"unlocked_until"`
	PaytxFee        float64 `json:"paytxfee"`
	RelayFee        float64 `json:"relayfee"`
	Errors          string  `json:"errors"`
}

// ListTransactionsResult models the data from the listtransactions command.
type ListTransactionsResult struct {
	Abandoned         bool     `json:"abandoned"`
	Account           string   `json:"account"`
	Address           string   `json:"address,omitempty"`
	Amount            float64  `json:"amount"`
	BIP125Replaceable string   `json:"bip125-replaceable,omitempty"`
	BlockHash         string   `json:"blockhash,omitempty"`
	BlockHeight       *int32   `json:"blockheight,omitempty"`
	BlockIndex        *int64   `json:"blockindex,omitempty"`
	BlockTime         int64    `json:"blocktime,omitempty"`
	Category          string   `json:"category"`
	Confirmations     int64    `json:"confirmations"`
	Fee               *float64 `json:"fee,omitempty"`
	Generated         bool     `json:"generated,omitempty"`
	InvolvesWatchOnly bool     `json:"involveswatchonly,omitempty"`
	Label             *string  `json:"label,omitempty"`
	Time              int64    `json:"time"`
	TimeReceived      int64    `json:"timereceived"`
	Trusted           bool     `json:"trusted"`
	TxID              string   `json:"txid"`
	Vout              uint32   `json:"vout"`
	WalletConflicts   []string `json:"walletconflicts"`
	Comment           string   `json:"comment,omitempty"`
	OtherAccount      string   `json:"otheraccount,omitempty"`
}

// ListReceivedByAccountResult models the data from the listreceivedbyaccount
// command.
type ListReceivedByAccountResult struct {
	Account       string  `json:"account"`
	Amount        float64 `json:"amount"`
	Confirmations uint64  `json:"confirmations"`
}

// ListReceivedByAddressResult models the data from the listreceivedbyaddress
// command.
type ListReceivedByAddressResult struct {
	Account           string   `json:"account"`
	Address           string   `json:"address"`
	Amount            float64  `json:"amount"`
	Confirmations     uint64   `json:"confirmations"`
	TxIDs             []string `json:"txids,omitempty"`
	InvolvesWatchonly bool     `json:"involvesWatchonly,omitempty"`
}

// ListSinceBlockResult models the data from the listsinceblock command.
type ListSinceBlockResult struct {
	Transactions []ListTransactionsResult `json:"transactions"`
	LastBlock    string                   `json:"lastblock"`
}

// ListUnspentResult models a successful response from the listunspent request.
type ListUnspentResult struct {
	TxID          string  `json:"txid"`
	Vout          uint32  `json:"vout"`
	Address       string  `json:"address"`
	Account       string  `json:"account"`
	ScriptPubKey  string  `json:"scriptPubKey"`
	RedeemScript  string  `json:"redeemScript,omitempty"`
	Amount        float64 `json:"amount"`
	Confirmations int64   `json:"confirmations"`
	Spendable     bool    `json:"spendable"`
}

// SignRawTransactionError models the data that contains script verification
// errors from the signrawtransaction request.
type SignRawTransactionError struct {
	TxID      string `json:"txid"`
	Vout      uint32 `json:"vout"`
	ScriptSig string `json:"scriptSig"`
	Sequence  uint32 `json:"sequence"`
	Error     string `json:"error"`
}

// SignRawTransactionResult models the data from the signrawtransaction
// command.
type SignRawTransactionResult struct {
	Hex      string                    `json:"hex"`
	Complete bool                      `json:"complete"`
	Errors   []SignRawTransactionError `json:"errors,omitempty"`
}

// SignRawTransactionWithWalletResult models the data from the
// signrawtransactionwithwallet command.
type SignRawTransactionWithWalletResult struct {
	Hex      string                    `json:"hex"`
	Complete bool                      `json:"complete"`
	Errors   []SignRawTransactionError `json:"errors,omitempty"`
}

// ValidateAddressWalletResult models the data returned by the wallet server
// validateaddress command.
type ValidateAddressWalletResult struct {
	IsValid      bool     `json:"isvalid"`
	Address      string   `json:"address,omitempty"`
	IsMine       bool     `json:"ismine,omitempty"`
	IsWatchOnly  bool     `json:"iswatchonly,omitempty"`
	IsScript     bool     `json:"isscript,omitempty"`
	PubKey       string   `json:"pubkey,omitempty"`
	IsCompressed bool     `json:"iscompressed,omitempty"`
	Account      string   `json:"account,omitempty"`
	Addresses    []string `json:"addresses,omitempty"`
	Hex          string   `json:"hex,omitempty"`
	Script       string   `json:"script,omitempty"`
	SigsRequired int32    `json:"sigsrequired,omitempty"`
}

// GetBestBlockResult models the data from the getbestblock command.
type GetBestBlockResult struct {
	Hash   string `json:"hash"`
	Height int32  `json:"height"`
}

// BalanceDetailsResult models the details data from the `getbalances` command.
type BalanceDetailsResult struct {
	Trusted          float64  `json:"trusted"`
	UntrustedPending float64  `json:"untrusted_pending"`
	Immature         float64  `json:"immature"`
	Used             *float64 `json:"used"`
}

// GetBalancesResult models the data returned from the getbalances command.
type GetBalancesResult struct {
	Mine      BalanceDetailsResult  `json:"mine"`
	WatchOnly *BalanceDetailsResult `json:"watchonly"`
}

// ImportMultiResults is a slice that models the result of the importmulti command.
//
// Each item in the slice contains the execution result corresponding to the input
// requests of type btcjson.ImportMultiRequest, passed to the ImportMulti[Async]
// function.
type ImportMultiResults []struct {
	Success  bool      `json:"success"`
	Error    *RPCError `json:"error,omitempty"`
	Warnings *[]string `json:"warnings,omitempty"`
}

// WalletCreateFundedPsbtResult models the data returned from the
// walletcreatefundedpsbtresult command.
type WalletCreateFundedPsbtResult struct {
	Psbt      string  `json:"psbt"`
	Fee       float64 `json:"fee"`
	ChangePos int64   `json:"changepos"`
}

// WalletProcessPsbtResult models the data returned from the
// walletprocesspsbtresult command.
type WalletProcessPsbtResult struct {
	Psbt     string `json:"psbt"`
	Complete bool   `json:"complete"`
}
