package api

import (
	"encoding/json"

	"github.com/aptos-labs/aptos-go-sdk/internal/types"
)

// WriteSetVariant is the type of [WriteSet]
type WriteSetVariant string

const (
	WriteSetVariantDirect  WriteSetVariant = "direct_write_set" // WriteSetVariantDirect maps to DirectWriteSet
	WriteSetVariantScript  WriteSetVariant = "script_write_set" // WriteSetVariantScript maps to ScriptWriteSet
	WriteSetVariantUnknown WriteSetVariant = "unknown"          // WriteSetVariantUnknown maps to UnknownWriteSet for unknown types
)

// WriteSet is an enum of all possible write sets
type WriteSet struct {
	Type  WriteSetVariant // Type of the write set, if the write set isn't recognized, it will be [WriteSetVariantUnknown]
	Inner WriteSetImpl    // Inner is the actual write set
}

// UnmarshalJSON unmarshals the [WriteSet] from JSON handling conversion between types
func (o *WriteSet) UnmarshalJSON(b []byte) error {
	type inner struct {
		Type string `json:"type"`
	}
	data := &inner{}
	err := json.Unmarshal(b, &data)
	if err != nil {
		return err
	}
	o.Type = WriteSetVariant(data.Type)
	switch o.Type {
	case WriteSetVariantDirect:
		o.Inner = &DirectWriteSet{}
	case WriteSetVariantScript:
		o.Inner = &ScriptWriteSet{}
	default:
		writeSet := &UnknownWriteSet{Type: string(o.Type)}
		o.Inner = writeSet
		o.Type = WriteSetVariantUnknown
		return json.Unmarshal(b, &writeSet.Payload)
	}
	return json.Unmarshal(b, o.Inner)
}

// WriteSetImpl is an interface for all write sets
type WriteSetImpl interface{}

// UnknownWriteSet is a placeholder for unknown write sets
type UnknownWriteSet struct {
	Type    string         `json:"type"`    // Type is the type of the unknown write set
	Payload map[string]any `json:"payload"` // Payload is the raw JSON data for the unknown write set
}

// DirectWriteSet is a raw set of changes and events.  This is also used for the [GenesisTransaction]
type DirectWriteSet struct {
	Changes []*WriteSetChange `json:"changes"` // Changes is the list of changes in the write set, can be empty
	Events  []*Event          `json:"events"`  // Events is the list of events in the write set, can be empty
}

// ScriptWriteSet is a set of changes and events that are generated by a script
//
// This hasn't been used in practice yet, but it's here for completeness
type ScriptWriteSet struct {
	ExecuteAs *types.AccountAddress     `json:"execute_as"` // ExecuteAs is the address that the script was executed as
	Script    *TransactionPayloadScript `json:"script"`     // Script is the script that generated the write set
}

// WriteSetChangeVariant is the type of [WriteSetChange]
type WriteSetChangeVariant string

const (
	WriteSetChangeVariantWriteResource   WriteSetChangeVariant = "write_resource"    // WriteSetChangeVariantWriteResource maps to WriteSetChangeWriteResource
	WriteSetChangeVariantDeleteResource  WriteSetChangeVariant = "delete_resource"   // WriteSetChangeVariantDeleteResource maps to WriteSetChangeDeleteResource
	WriteSetChangeVariantWriteModule     WriteSetChangeVariant = "write_module"      // WriteSetChangeVariantWriteModule maps to WriteSetChangeWriteModule
	WriteSetChangeVariantDeleteModule    WriteSetChangeVariant = "delete_module"     // WriteSetChangeVariantDeleteModule maps to WriteSetChangeDeleteModule
	WriteSetChangeVariantWriteTableItem  WriteSetChangeVariant = "write_table_item"  // WriteSetChangeVariantWriteTableItem maps to WriteSetChangeWriteTableItem
	WriteSetChangeVariantDeleteTableItem WriteSetChangeVariant = "delete_table_item" // WriteSetChangeVariantDeleteTableItem maps to WriteSetChangeDeleteTableItem
	WriteSetChangeVariantUnknown         WriteSetChangeVariant = "unknown"           // WriteSetChangeVariantUnknown maps to WriteSetChangeUnknown for unknown types
)

// WriteSetChange is an enum of all possible write set changes
type WriteSetChange struct {
	Type  WriteSetChangeVariant // Type of the write set change, if the write set change isn't recognized, it will be [WriteSetChangeVariantUnknown]
	Inner WriteSetChangeImpl    // Inner is the actual write set change
}

// UnmarshalJSON unmarshals the [WriteSetChange] from JSON handling conversion between types
func (o *WriteSetChange) UnmarshalJSON(b []byte) error {
	type inner struct {
		Type string `json:"type"`
	}
	data := &inner{}
	err := json.Unmarshal(b, &data)
	if err != nil {
		return err
	}
	o.Type = WriteSetChangeVariant(data.Type)
	switch o.Type {
	case WriteSetChangeVariantWriteResource:
		o.Inner = &WriteSetChangeWriteResource{}
	case WriteSetChangeVariantDeleteResource:
		o.Inner = &WriteSetChangeDeleteResource{}
	case WriteSetChangeVariantWriteModule:
		o.Inner = &WriteSetChangeWriteModule{}
	case WriteSetChangeVariantDeleteModule:
		o.Inner = &WriteSetChangeDeleteModule{}
	case WriteSetChangeVariantWriteTableItem:
		o.Inner = &WriteSetChangeWriteTableItem{}
	case WriteSetChangeVariantDeleteTableItem:
		o.Inner = &WriteSetChangeDeleteTableItem{}
	default:
		changeSet := &WriteSetChangeUnknown{Type: string(o.Type)}
		o.Inner = changeSet
		o.Type = WriteSetChangeVariantUnknown
		return json.Unmarshal(b, &changeSet.Payload)
	}
	return json.Unmarshal(b, o.Inner)
}

// WriteSetChangeImpl is an interface for all write set changes
type WriteSetChangeImpl interface{}

// WriteSetChangeUnknown is a placeholder for unknown write set changes
type WriteSetChangeUnknown struct {
	Type    string         `json:"type"`    // Type is the type of the unknown write set change
	Payload map[string]any `json:"payload"` // Payload is the raw JSON data for the unknown write set change
}

// WriteSetChangeWriteResource is a change that writes a resource to an account
type WriteSetChangeWriteResource struct {
	Address      *types.AccountAddress `json:"address"`        // Address is the address the resource is stored
	StateKeyHash Hash                  `json:"state_key_hash"` // StateKeyHash is the hash of the state key
	Data         *MoveResource         `json:"data"`           // Data is the resource data matching the on-chain struct data
}

// WriteSetChangeDeleteResource is a change that deletes a resource from an account
type WriteSetChangeDeleteResource struct {
	Address      *types.AccountAddress `json:"address"`        // Address is the address the resource is deleted
	StateKeyHash Hash                  `json:"state_key_hash"` // StateKeyHash is the hash of the state key
	Resource     string                `json:"resource"`       // Resource is the struct name of the resource deleted
}

// WriteSetChangeWriteModule is a change that writes a module to an account
type WriteSetChangeWriteModule struct {
	Address      *types.AccountAddress `json:"address"`        // Address is the address the module is stored
	StateKeyHash Hash                  `json:"state_key_hash"` // StateKeyHash is the hash of the state key
	Data         *MoveBytecode         `json:"data"`           // Data is the module bytecode
}

// WriteSetChangeDeleteModule is a change that deletes a module from an account
//
// Note: There is no way to delete a module today, but this is here for completeness
type WriteSetChangeDeleteModule struct {
	Address      *types.AccountAddress `json:"address"`        // Address is the address the module is deleted
	StateKeyHash Hash                  `json:"state_key_hash"` // StateKeyHash is the hash of the state key
	Module       string                `json:"module"`         // Module is the module bytecode
}

// WriteSetChangeWriteTableItem is a change that writes a table item
type WriteSetChangeWriteTableItem struct {
	StateKeyHash Hash              `json:"state_key_hash"` // StateKeyHash is the hash of the state key
	Handle       string            `json:"handle"`         // Handle is the handle of the table, this will be a 32-byte hex string with a leading 0x
	Key          string            `json:"key"`            // Key is the key of the table item in BCS encoded hex
	Value        string            `json:"value"`          // Value is the value of the table item in BCS encoded hex
	Data         *DecodedTableData `json:"data,omitempty"` // Data is the decoded table data, optional
}

// WriteSetChangeDeleteTableItem is a change that deletes a table item
type WriteSetChangeDeleteTableItem struct {
	StateKeyHash Hash              `json:"state_key_hash"` // StateKeyHash is the hash of the state key
	Handle       string            `json:"handle"`         // Handle is the handle of the table, this will be a 32-byte hex string with a leading 0x
	Key          string            `json:"key"`            // Key is the key of the table item in BCS encoded hex
	Data         *DeletedTableData `json:"data,omitempty"` // Data is the decoded table data, optional
}

// DecodedTableData is the decoded data for a table item
type DecodedTableData struct {
	Key       any    `json:"key"`        // Key is the key of the table item in a decoded format
	KeyType   string `json:"key_type"`   // KeyType is the type of the key as a string representation of a TypeTag
	Value     any    `json:"value"`      // Value is the value of the table item
	ValueType string `json:"value_type"` // ValueType is the type of the value as a string representation of a TypeTag
}

// DeletedTableData is the decoded data for a deleted table item
type DeletedTableData struct {
	Key     any    `json:"key"`      // Key is the key of the table item in a decoded format
	KeyType string `json:"key_type"` // KeyType is the type of the key as a string representation of a TypeTag
}

// MoveResource is a resource in Move, it should match the on-chain struct data
type MoveResource struct {
	Type string         `json:"type"` // Type is the type of the resource e.g. 0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin> or 0x1::account::Account
	Data map[string]any `json:"data"` // Data is the resource data matching the on-chain struct data
}
