package api

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/ipfs/go-cid"
	"github.com/ipfs/go-graphsync"
	"github.com/ipld/go-ipld-prime"
	"github.com/ipld/go-ipld-prime/codec/dagjson"
	pubsub "github.com/libp2p/go-libp2p-pubsub"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
	ma "github.com/multiformats/go-multiaddr"

	"github.com/filecoin-project/go-address"
	datatransfer "github.com/filecoin-project/go-data-transfer/v2"
	"github.com/filecoin-project/go-fil-markets/retrievalmarket"
	"github.com/filecoin-project/go-state-types/abi"

	"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/filecoin-project/lotus/node/modules/dtypes"
)

type MultiaddrSlice []ma.Multiaddr

func (m *MultiaddrSlice) UnmarshalJSON(raw []byte) (err error) {
	var temp []string
	if err := json.Unmarshal(raw, &temp); err != nil {
		return err
	}

	res := make([]ma.Multiaddr, len(temp))
	for i, str := range temp {
		res[i], err = ma.NewMultiaddr(str)
		if err != nil {
			return err
		}
	}
	*m = res
	return nil
}

var _ json.Unmarshaler = new(MultiaddrSlice)

type ObjStat struct {
	Size  uint64
	Links uint64
}

type PubsubScore struct {
	ID    peer.ID
	Score *pubsub.PeerScoreSnapshot
}

// MessageSendSpec contains optional fields which modify message sending behavior
type MessageSendSpec struct {
	// MaxFee specifies a cap on network fees related to this message
	MaxFee abi.TokenAmount

	// MsgUuid specifies a unique message identifier which can be used on node (or node cluster)
	// level to prevent double-sends of messages even when nonce generation is not handled by sender
	MsgUuid uuid.UUID

	// MaximizeFeeCap makes message FeeCap be based entirely on MaxFee
	MaximizeFeeCap bool
}

type MpoolMessageWhole struct {
	Msg  *types.Message
	Spec *MessageSendSpec
}

// GraphSyncDataTransfer provides diagnostics on a data transfer happening over graphsync
type GraphSyncDataTransfer struct {
	// GraphSync request id for this transfer
	RequestID *graphsync.RequestID
	// Graphsync state for this transfer
	RequestState string
	// If a channel ID is present, indicates whether this is the current graphsync request for this channel
	// (could have changed in a restart)
	IsCurrentChannelRequest bool
	// Data transfer channel ID for this transfer
	ChannelID *datatransfer.ChannelID
	// Data transfer state for this transfer
	ChannelState *DataTransferChannel
	// Diagnostic information about this request -- and unexpected inconsistencies in
	// request state
	Diagnostics []string
}

// TransferDiagnostics give current information about transfers going over graphsync that may be helpful for debugging
type TransferDiagnostics struct {
	ReceivingTransfers []*GraphSyncDataTransfer
	SendingTransfers   []*GraphSyncDataTransfer
}

type DataTransferChannel struct {
	TransferID  datatransfer.TransferID
	Status      datatransfer.Status
	BaseCID     cid.Cid
	IsInitiator bool
	IsSender    bool
	Voucher     string
	Message     string
	OtherPeer   peer.ID
	Transferred uint64
	Stages      *datatransfer.ChannelStages
}

// NewDataTransferChannel constructs an API DataTransferChannel type from full channel state snapshot and a host id
func NewDataTransferChannel(hostID peer.ID, channelState datatransfer.ChannelState) DataTransferChannel {
	channel := DataTransferChannel{
		TransferID: channelState.TransferID(),
		Status:     channelState.Status(),
		BaseCID:    channelState.BaseCID(),
		IsSender:   channelState.Sender() == hostID,
		Message:    channelState.Message(),
	}
	voucher := channelState.Voucher()
	voucherJSON, err := ipld.Encode(voucher.Voucher, dagjson.Encode)
	if err != nil {
		channel.Voucher = fmt.Errorf("Voucher Serialization: %w", err).Error()
	} else {
		channel.Voucher = string(voucherJSON)
	}
	if channel.IsSender {
		channel.IsInitiator = !channelState.IsPull()
		channel.Transferred = channelState.Sent()
		channel.OtherPeer = channelState.Recipient()
	} else {
		channel.IsInitiator = channelState.IsPull()
		channel.Transferred = channelState.Received()
		channel.OtherPeer = channelState.Sender()
	}
	return channel
}

type NetStat struct {
	System    *network.ScopeStat           `json:",omitempty"`
	Transient *network.ScopeStat           `json:",omitempty"`
	Services  map[string]network.ScopeStat `json:",omitempty"`
	Protocols map[string]network.ScopeStat `json:",omitempty"`
	Peers     map[string]network.ScopeStat `json:",omitempty"`
}

type NetLimit struct {
	Memory int64 `json:",omitempty"`

	Streams, StreamsInbound, StreamsOutbound int
	Conns, ConnsInbound, ConnsOutbound       int
	FD                                       int
}

type NetBlockList struct {
	Peers     []peer.ID
	IPAddrs   []string
	IPSubnets []string
}

type ExtendedPeerInfo struct {
	ID          peer.ID
	Agent       string
	Addrs       []string
	Protocols   []string
	ConnMgrMeta *ConnMgrInfo
}

type ConnMgrInfo struct {
	FirstSeen time.Time
	Value     int
	Tags      map[string]int
	Conns     map[string]time.Time
}

type NodeStatus struct {
	SyncStatus  NodeSyncStatus
	PeerStatus  NodePeerStatus
	ChainStatus NodeChainStatus
}

type NodeSyncStatus struct {
	Epoch  uint64
	Behind uint64
}

type NodePeerStatus struct {
	PeersToPublishMsgs   int
	PeersToPublishBlocks int
}

type NodeChainStatus struct {
	BlocksPerTipsetLast100      float64
	BlocksPerTipsetLastFinality float64
}

type CheckStatusCode int

//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckStatusCode -trimprefix=CheckStatus
const (
	_ CheckStatusCode = iota
	// Message Checks
	CheckStatusMessageSerialize
	CheckStatusMessageSize
	CheckStatusMessageValidity
	CheckStatusMessageMinGas
	CheckStatusMessageMinBaseFee
	CheckStatusMessageBaseFee
	CheckStatusMessageBaseFeeLowerBound
	CheckStatusMessageBaseFeeUpperBound
	CheckStatusMessageGetStateNonce
	CheckStatusMessageNonce
	CheckStatusMessageGetStateBalance
	CheckStatusMessageBalance
)

type CheckStatus struct {
	Code CheckStatusCode
	OK   bool
	Err  string
	Hint map[string]interface{}
}

type MessageCheckStatus struct {
	Cid cid.Cid
	CheckStatus
}

type MessagePrototype struct {
	Message    types.Message
	ValidNonce bool
}

type RetrievalInfo struct {
	PayloadCID   cid.Cid
	ID           retrievalmarket.DealID
	PieceCID     *cid.Cid
	PricePerByte abi.TokenAmount
	UnsealPrice  abi.TokenAmount

	Status        retrievalmarket.DealStatus
	Message       string // more information about deal state, particularly errors
	Provider      peer.ID
	BytesReceived uint64
	BytesPaidFor  uint64
	TotalPaid     abi.TokenAmount

	TransferChannelID *datatransfer.ChannelID
	DataTransfer      *DataTransferChannel

	// optional event if part of ClientGetRetrievalUpdates
	Event *retrievalmarket.ClientEvent
}

type RestrievalRes struct {
	DealID retrievalmarket.DealID
}

// Selector specifies ipld selector string
//   - if the string starts with '{', it's interpreted as json selector string
//     see https://ipld.io/specs/selectors/ and https://ipld.io/specs/selectors/fixtures/selector-fixtures-1/
//   - otherwise the string is interpreted as ipld-selector-text-lite (simple ipld path)
//     see https://github.com/ipld/go-ipld-selector-text-lite
type Selector string

type DagSpec struct {
	// DataSelector matches data to be retrieved
	// - when using textselector, the path specifies subtree
	// - the matched graph must have a single root
	DataSelector *Selector

	// ExportMerkleProof is applicable only when exporting to a CAR file via a path textselector
	// When true, in addition to the selection target, the resulting CAR will contain every block along the
	// path back to, and including the original root
	// When false the resulting CAR contains only the blocks of the target subdag
	ExportMerkleProof bool
}

type ExportRef struct {
	Root cid.Cid

	// DAGs array specifies a list of DAGs to export
	// - If exporting into unixfs files, only one DAG is supported, DataSelector is only used to find the targeted root node
	// - If exporting into a car file
	//   - When exactly one text-path DataSelector is specified exports the subgraph and its full merkle-path from the original root
	//   - Otherwise ( multiple paths and/or JSON selector specs) determines each individual subroot and exports the subtrees as a multi-root car
	// - When not specified defaults to a single DAG:
	//   - Data - the entire DAG: `{"R":{"l":{"none":{}},":>":{"a":{">":{"@":{}}}}}}`
	DAGs []DagSpec

	FromLocalCAR string // if specified, get data from a local CARv2 file.
	DealID       retrievalmarket.DealID
}

type MinerInfo struct {
	Owner                      address.Address   // Must be an ID-address.
	Worker                     address.Address   // Must be an ID-address.
	NewWorker                  address.Address   // Must be an ID-address.
	ControlAddresses           []address.Address // Must be an ID-addresses.
	WorkerChangeEpoch          abi.ChainEpoch
	PeerId                     *peer.ID
	Multiaddrs                 []abi.Multiaddrs
	WindowPoStProofType        abi.RegisteredPoStProof
	SectorSize                 abi.SectorSize
	WindowPoStPartitionSectors uint64
	ConsensusFaultElapsed      abi.ChainEpoch
	PendingOwnerAddress        *address.Address
	Beneficiary                address.Address
	BeneficiaryTerm            *miner.BeneficiaryTerm
	PendingBeneficiaryTerm     *miner.PendingBeneficiaryChange
}

type NetworkParams struct {
	NetworkName             dtypes.NetworkName
	BlockDelaySecs          uint64
	ConsensusMinerMinPower  abi.StoragePower
	SupportedProofTypes     []abi.RegisteredSealProof
	PreCommitChallengeDelay abi.ChainEpoch
	ForkUpgradeParams       ForkUpgradeParams
	Eip155ChainID           int
}

type ForkUpgradeParams struct {
	UpgradeSmokeHeight       abi.ChainEpoch
	UpgradeBreezeHeight      abi.ChainEpoch
	UpgradeIgnitionHeight    abi.ChainEpoch
	UpgradeLiftoffHeight     abi.ChainEpoch
	UpgradeAssemblyHeight    abi.ChainEpoch
	UpgradeRefuelHeight      abi.ChainEpoch
	UpgradeTapeHeight        abi.ChainEpoch
	UpgradeKumquatHeight     abi.ChainEpoch
	BreezeGasTampingDuration abi.ChainEpoch
	UpgradeCalicoHeight      abi.ChainEpoch
	UpgradePersianHeight     abi.ChainEpoch
	UpgradeOrangeHeight      abi.ChainEpoch
	UpgradeClausHeight       abi.ChainEpoch
	UpgradeTrustHeight       abi.ChainEpoch
	UpgradeNorwegianHeight   abi.ChainEpoch
	UpgradeTurboHeight       abi.ChainEpoch
	UpgradeHyperdriveHeight  abi.ChainEpoch
	UpgradeChocolateHeight   abi.ChainEpoch
	UpgradeOhSnapHeight      abi.ChainEpoch
	UpgradeSkyrHeight        abi.ChainEpoch
	UpgradeSharkHeight       abi.ChainEpoch
	UpgradeHyggeHeight       abi.ChainEpoch
	UpgradeLightningHeight   abi.ChainEpoch
	UpgradeThunderHeight     abi.ChainEpoch
	UpgradeWatermelonHeight  abi.ChainEpoch
}

type NonceMapType map[address.Address]uint64
type MsgUuidMapType map[uuid.UUID]*types.SignedMessage

type RaftStateData struct {
	NonceMap NonceMapType
	MsgUuids MsgUuidMapType
}

func (n *NonceMapType) MarshalJSON() ([]byte, error) {
	marshalled := make(map[string]uint64)
	for a, n := range *n {
		marshalled[a.String()] = n
	}
	return json.Marshal(marshalled)
}

func (n *NonceMapType) UnmarshalJSON(b []byte) error {
	unmarshalled := make(map[string]uint64)
	err := json.Unmarshal(b, &unmarshalled)
	if err != nil {
		return err
	}
	*n = make(map[address.Address]uint64)
	for saddr, nonce := range unmarshalled {
		a, err := address.NewFromString(saddr)
		if err != nil {
			return err
		}
		(*n)[a] = nonce
	}
	return nil
}

func (m *MsgUuidMapType) MarshalJSON() ([]byte, error) {
	marshalled := make(map[string]*types.SignedMessage)
	for u, msg := range *m {
		marshalled[u.String()] = msg
	}
	return json.Marshal(marshalled)
}

func (m *MsgUuidMapType) UnmarshalJSON(b []byte) error {
	unmarshalled := make(map[string]*types.SignedMessage)
	err := json.Unmarshal(b, &unmarshalled)
	if err != nil {
		return err
	}
	*m = make(map[uuid.UUID]*types.SignedMessage)
	for suid, msg := range unmarshalled {
		u, err := uuid.Parse(suid)
		if err != nil {
			return err
		}
		(*m)[u] = msg
	}
	return nil
}

// ChainExportConfig holds configuration for chain ranged exports.
type ChainExportConfig struct {
	WriteBufferSize   int
	NumWorkers        int
	IncludeMessages   bool
	IncludeReceipts   bool
	IncludeStateRoots bool
}