// Package contract provides smart contract deployment and interaction.
package contract

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"sync/atomic"
	"time"
)

const DefaultEndpoint = "https://contract.synor.cc/api/v1"

// Config is the contract client configuration.
type Config struct {
	APIKey          string
	Endpoint        string
	Timeout         time.Duration
	Retries         int
	Debug           bool
	DefaultGasLimit string
	DefaultGasPrice string
}

// AbiEntryType is the type of ABI entry.
type AbiEntryType string

const (
	AbiFunction    AbiEntryType = "function"
	AbiConstructor AbiEntryType = "constructor"
	AbiEvent       AbiEntryType = "event"
	AbiError       AbiEntryType = "error"
	AbiFallback    AbiEntryType = "fallback"
	AbiReceive     AbiEntryType = "receive"
)

// StateMutability represents function state mutability.
type StateMutability string

const (
	StatePure       StateMutability = "pure"
	StateView       StateMutability = "view"
	StateNonpayable StateMutability = "nonpayable"
	StatePayable    StateMutability = "payable"
)

// TransactionStatus represents transaction status.
type TransactionStatus string

const (
	StatusSuccess  TransactionStatus = "success"
	StatusReverted TransactionStatus = "reverted"
)

// VerificationStatus represents verification status.
type VerificationStatus string

const (
	VerificationVerified VerificationStatus = "verified"
	VerificationPending  VerificationStatus = "pending"
	VerificationFailed   VerificationStatus = "failed"
)

// AbiParameter represents an ABI parameter.
type AbiParameter struct {
	Name         string         `json:"name"`
	Type         string         `json:"type"`
	Indexed      bool           `json:"indexed,omitempty"`
	Components   []AbiParameter `json:"components,omitempty"`
	InternalType string         `json:"internalType,omitempty"`
}

// AbiEntry represents an ABI entry.
type AbiEntry struct {
	Type            AbiEntryType    `json:"type"`
	Name            string          `json:"name,omitempty"`
	Inputs          []AbiParameter  `json:"inputs,omitempty"`
	Outputs         []AbiParameter  `json:"outputs,omitempty"`
	StateMutability StateMutability `json:"stateMutability,omitempty"`
	Anonymous       bool            `json:"anonymous,omitempty"`
}

// Abi is a contract ABI.
type Abi []AbiEntry

// DeploymentResult is the result of a contract deployment.
type DeploymentResult struct {
	Address           string `json:"address"`
	TransactionHash   string `json:"transactionHash"`
	BlockNumber       int64  `json:"blockNumber"`
	GasUsed           string `json:"gasUsed"`
	EffectiveGasPrice string `json:"effectiveGasPrice"`
}

// EventLog is a raw event log.
type EventLog struct {
	LogIndex        int      `json:"logIndex"`
	Address         string   `json:"address"`
	Topics          []string `json:"topics"`
	Data            string   `json:"data"`
	BlockNumber     int64    `json:"blockNumber"`
	TransactionHash string   `json:"transactionHash"`
}

// DecodedEvent is a decoded event.
type DecodedEvent struct {
	Name      string                 `json:"name"`
	Signature string                 `json:"signature"`
	Args      map[string]interface{} `json:"args"`
	Log       EventLog               `json:"log"`
}

// TransactionResult is the result of a transaction.
type TransactionResult struct {
	TransactionHash   string            `json:"transactionHash"`
	BlockNumber       int64             `json:"blockNumber"`
	BlockHash         string            `json:"blockHash"`
	GasUsed           string            `json:"gasUsed"`
	EffectiveGasPrice string            `json:"effectiveGasPrice"`
	Status            TransactionStatus `json:"status"`
	Logs              []EventLog        `json:"logs"`
	ReturnValue       interface{}       `json:"returnValue,omitempty"`
	RevertReason      string            `json:"revertReason,omitempty"`
}

// ContractInterface is a parsed contract interface.
type ContractInterface struct {
	Abi       Abi
	Functions map[string]AbiEntry
	Events    map[string]AbiEntry
	Errors    map[string]AbiEntry
}

// GasEstimation is a gas estimation result.
type GasEstimation struct {
	GasLimit      string `json:"gasLimit"`
	GasPrice      string `json:"gasPrice"`
	EstimatedCost string `json:"estimatedCost"`
}

// BytecodeMetadata contains bytecode metadata.
type BytecodeMetadata struct {
	Compiler string   `json:"compiler"`
	Language string   `json:"language"`
	Sources  []string `json:"sources"`
}

// BytecodeInfo contains contract bytecode information.
type BytecodeInfo struct {
	Bytecode         string            `json:"bytecode"`
	DeployedBytecode string            `json:"deployedBytecode,omitempty"`
	Abi              Abi               `json:"abi,omitempty"`
	Metadata         *BytecodeMetadata `json:"metadata,omitempty"`
}

// VerificationResult is the result of contract verification.
type VerificationResult struct {
	Status  VerificationStatus `json:"status"`
	Message string             `json:"message"`
	Abi     Abi                `json:"abi,omitempty"`
}

// MulticallRequest is a multicall request.
type MulticallRequest struct {
	Address      string `json:"address"`
	CallData     string `json:"callData"`
	AllowFailure bool   `json:"allowFailure,omitempty"`
}

// MulticallResult is a multicall result.
type MulticallResult struct {
	Success    bool        `json:"success"`
	ReturnData string      `json:"returnData"`
	Decoded    interface{} `json:"decoded,omitempty"`
}

// Error is a contract error.
type Error struct {
	Message    string
	Code       string
	StatusCode int
}

func (e *Error) Error() string {
	return e.Message
}

// Client is a Synor Contract client.
type Client struct {
	config     Config
	httpClient *http.Client
	closed     atomic.Bool
}

// NewClient creates a new contract client.
func NewClient(config Config) *Client {
	if config.Endpoint == "" {
		config.Endpoint = DefaultEndpoint
	}
	if config.Timeout == 0 {
		config.Timeout = 30 * time.Second
	}
	if config.Retries == 0 {
		config.Retries = 3
	}

	return &Client{
		config: config,
		httpClient: &http.Client{
			Timeout: config.Timeout,
		},
	}
}

// ==================== Deployment ====================

// DeployOptions are options for deploying a contract.
type DeployOptions struct {
	Bytecode string
	Abi      Abi
	Args     []interface{}
	GasLimit string
	GasPrice string
	Value    string
	Salt     string
}

// Deploy deploys a smart contract.
func (c *Client) Deploy(ctx context.Context, opts DeployOptions) (*DeploymentResult, error) {
	body := map[string]interface{}{
		"bytecode": opts.Bytecode,
	}
	if opts.Abi != nil {
		body["abi"] = opts.Abi
	}
	if opts.Args != nil {
		body["args"] = opts.Args
	}
	gasLimit := opts.GasLimit
	if gasLimit == "" {
		gasLimit = c.config.DefaultGasLimit
	}
	if gasLimit != "" {
		body["gasLimit"] = gasLimit
	}
	gasPrice := opts.GasPrice
	if gasPrice == "" {
		gasPrice = c.config.DefaultGasPrice
	}
	if gasPrice != "" {
		body["gasPrice"] = gasPrice
	}
	if opts.Value != "" {
		body["value"] = opts.Value
	}
	if opts.Salt != "" {
		body["salt"] = opts.Salt
	}

	var resp struct {
		Deployment DeploymentResult `json:"deployment"`
	}
	if err := c.request(ctx, "POST", "/contracts/deploy", body, &resp); err != nil {
		return nil, err
	}
	return &resp.Deployment, nil
}

// PredictAddress predicts the CREATE2 deployment address.
func (c *Client) PredictAddress(ctx context.Context, bytecode, salt string, constructorArgs []interface{}) (string, error) {
	body := map[string]interface{}{
		"bytecode": bytecode,
		"salt":     salt,
	}
	if constructorArgs != nil {
		body["constructorArgs"] = constructorArgs
	}
	var resp struct {
		Address string `json:"address"`
	}
	if err := c.request(ctx, "POST", "/contracts/predict-address", body, &resp); err != nil {
		return "", err
	}
	return resp.Address, nil
}

// ==================== Contract Interaction ====================

// CallOptions are options for calling a contract.
type CallOptions struct {
	Address     string
	Method      string
	Args        []interface{}
	Abi         Abi
	BlockNumber interface{}
}

// Call calls a view/pure function.
func (c *Client) Call(ctx context.Context, opts CallOptions) (interface{}, error) {
	body := map[string]interface{}{
		"address": opts.Address,
		"method":  opts.Method,
		"abi":     opts.Abi,
	}
	if opts.Args != nil {
		body["args"] = opts.Args
	}
	if opts.BlockNumber != nil {
		body["blockNumber"] = opts.BlockNumber
	} else {
		body["blockNumber"] = "latest"
	}

	var resp struct {
		Result interface{} `json:"result"`
	}
	if err := c.request(ctx, "POST", "/contracts/call", body, &resp); err != nil {
		return nil, err
	}
	return resp.Result, nil
}

// SendOptions are options for sending a transaction.
type SendOptions struct {
	Address  string
	Method   string
	Args     []interface{}
	Abi      Abi
	GasLimit string
	GasPrice string
	Value    string
}

// Send sends a state-changing transaction.
func (c *Client) Send(ctx context.Context, opts SendOptions) (*TransactionResult, error) {
	body := map[string]interface{}{
		"address": opts.Address,
		"method":  opts.Method,
		"abi":     opts.Abi,
	}
	if opts.Args != nil {
		body["args"] = opts.Args
	}
	gasLimit := opts.GasLimit
	if gasLimit == "" {
		gasLimit = c.config.DefaultGasLimit
	}
	if gasLimit != "" {
		body["gasLimit"] = gasLimit
	}
	gasPrice := opts.GasPrice
	if gasPrice == "" {
		gasPrice = c.config.DefaultGasPrice
	}
	if gasPrice != "" {
		body["gasPrice"] = gasPrice
	}
	if opts.Value != "" {
		body["value"] = opts.Value
	}

	var resp struct {
		Transaction TransactionResult `json:"transaction"`
	}
	if err := c.request(ctx, "POST", "/contracts/send", body, &resp); err != nil {
		return nil, err
	}
	return &resp.Transaction, nil
}

// Multicall executes multiple calls in a single request.
func (c *Client) Multicall(ctx context.Context, requests []MulticallRequest, abis map[string]Abi) ([]MulticallResult, error) {
	body := map[string]interface{}{
		"calls": requests,
	}
	if abis != nil {
		body["abis"] = abis
	}
	var resp struct {
		Results []MulticallResult `json:"results"`
	}
	if err := c.request(ctx, "POST", "/contracts/multicall", body, &resp); err != nil {
		return nil, err
	}
	return resp.Results, nil
}

// ==================== Events ====================

// EventFilter is a filter for events.
type EventFilter struct {
	Address   string
	EventName string
	Abi       Abi
	FromBlock interface{}
	ToBlock   interface{}
	Filter    map[string]interface{}
}

// GetEvents gets historical events.
func (c *Client) GetEvents(ctx context.Context, filter EventFilter) ([]DecodedEvent, error) {
	body := map[string]interface{}{
		"address": filter.Address,
	}
	if filter.Abi != nil {
		body["abi"] = filter.Abi
	}
	if filter.EventName != "" {
		body["eventName"] = filter.EventName
	}
	if filter.FromBlock != nil {
		body["fromBlock"] = filter.FromBlock
	} else {
		body["fromBlock"] = "earliest"
	}
	if filter.ToBlock != nil {
		body["toBlock"] = filter.ToBlock
	} else {
		body["toBlock"] = "latest"
	}
	if filter.Filter != nil {
		body["filter"] = filter.Filter
	}

	var resp struct {
		Events []DecodedEvent `json:"events"`
	}
	if err := c.request(ctx, "POST", "/contracts/events", body, &resp); err != nil {
		return nil, err
	}
	return resp.Events, nil
}

// GetLogs gets raw event logs.
func (c *Client) GetLogs(ctx context.Context, filter EventFilter) ([]EventLog, error) {
	body := map[string]interface{}{
		"address": filter.Address,
	}
	if filter.Abi != nil {
		body["abi"] = filter.Abi
	}
	if filter.EventName != "" {
		body["eventName"] = filter.EventName
	}
	if filter.FromBlock != nil {
		body["fromBlock"] = filter.FromBlock
	} else {
		body["fromBlock"] = "earliest"
	}
	if filter.ToBlock != nil {
		body["toBlock"] = filter.ToBlock
	} else {
		body["toBlock"] = "latest"
	}
	if filter.Filter != nil {
		body["filter"] = filter.Filter
	}

	var resp struct {
		Logs []EventLog `json:"logs"`
	}
	if err := c.request(ctx, "POST", "/contracts/logs", body, &resp); err != nil {
		return nil, err
	}
	return resp.Logs, nil
}

// DecodeLog decodes a raw event log.
func (c *Client) DecodeLog(ctx context.Context, log EventLog, abi Abi) (*DecodedEvent, error) {
	body := map[string]interface{}{
		"log": log,
		"abi": abi,
	}
	var resp struct {
		Event DecodedEvent `json:"event"`
	}
	if err := c.request(ctx, "POST", "/contracts/decode-log", body, &resp); err != nil {
		return nil, err
	}
	return &resp.Event, nil
}

// ==================== ABI Utilities ====================

// LoadAbi loads and parses a contract ABI.
func (c *Client) LoadAbi(abi Abi) *ContractInterface {
	ci := &ContractInterface{
		Abi:       abi,
		Functions: make(map[string]AbiEntry),
		Events:    make(map[string]AbiEntry),
		Errors:    make(map[string]AbiEntry),
	}
	for _, entry := range abi {
		switch entry.Type {
		case AbiFunction:
			if entry.Name != "" {
				ci.Functions[entry.Name] = entry
			}
		case AbiEvent:
			if entry.Name != "" {
				ci.Events[entry.Name] = entry
			}
		case AbiError:
			if entry.Name != "" {
				ci.Errors[entry.Name] = entry
			}
		}
	}
	return ci
}

// EncodeCall encodes a function call.
func (c *Client) EncodeCall(ctx context.Context, method string, args []interface{}, abi Abi) (string, error) {
	body := map[string]interface{}{
		"method": method,
		"args":   args,
		"abi":    abi,
	}
	var resp struct {
		Data string `json:"data"`
	}
	if err := c.request(ctx, "POST", "/contracts/encode", body, &resp); err != nil {
		return "", err
	}
	return resp.Data, nil
}

// DecodeResult decodes function return data.
func (c *Client) DecodeResult(ctx context.Context, method, data string, abi Abi) (interface{}, error) {
	body := map[string]interface{}{
		"method": method,
		"data":   data,
		"abi":    abi,
	}
	var resp struct {
		Result interface{} `json:"result"`
	}
	if err := c.request(ctx, "POST", "/contracts/decode", body, &resp); err != nil {
		return nil, err
	}
	return resp.Result, nil
}

// GetFunctionSelector gets the function selector.
func (c *Client) GetFunctionSelector(ctx context.Context, signature string) (string, error) {
	body := map[string]interface{}{
		"signature": signature,
	}
	var resp struct {
		Selector string `json:"selector"`
	}
	if err := c.request(ctx, "POST", "/contracts/selector", body, &resp); err != nil {
		return "", err
	}
	return resp.Selector, nil
}

// ==================== Gas Estimation ====================

// EstimateGasOptions are options for gas estimation.
type EstimateGasOptions struct {
	Address  string
	Method   string
	Args     []interface{}
	Abi      Abi
	Bytecode string
	Value    string
	From     string
}

// EstimateGas estimates gas for a contract call or deployment.
func (c *Client) EstimateGas(ctx context.Context, opts EstimateGasOptions) (*GasEstimation, error) {
	body := map[string]interface{}{}
	if opts.Address != "" {
		body["address"] = opts.Address
	}
	if opts.Method != "" {
		body["method"] = opts.Method
	}
	if opts.Args != nil {
		body["args"] = opts.Args
	}
	if opts.Abi != nil {
		body["abi"] = opts.Abi
	}
	if opts.Bytecode != "" {
		body["bytecode"] = opts.Bytecode
	}
	if opts.Value != "" {
		body["value"] = opts.Value
	}
	if opts.From != "" {
		body["from"] = opts.From
	}

	var resp struct {
		Estimation GasEstimation `json:"estimation"`
	}
	if err := c.request(ctx, "POST", "/contracts/estimate-gas", body, &resp); err != nil {
		return nil, err
	}
	return &resp.Estimation, nil
}

// ==================== Contract Info ====================

// GetBytecode gets contract bytecode.
func (c *Client) GetBytecode(ctx context.Context, address string) (*BytecodeInfo, error) {
	var info BytecodeInfo
	if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/bytecode", address), nil, &info); err != nil {
		return nil, err
	}
	return &info, nil
}

// IsContract checks if an address is a contract.
func (c *Client) IsContract(ctx context.Context, address string) (bool, error) {
	var resp struct {
		IsContract bool `json:"isContract"`
	}
	if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/is-contract", address), nil, &resp); err != nil {
		return false, err
	}
	return resp.IsContract, nil
}

// ReadStorage reads a contract storage slot.
func (c *Client) ReadStorage(ctx context.Context, address, slot string, blockNumber interface{}) (string, error) {
	body := map[string]interface{}{
		"address": address,
		"slot":    slot,
	}
	if blockNumber != nil {
		body["blockNumber"] = blockNumber
	} else {
		body["blockNumber"] = "latest"
	}
	var resp struct {
		Value string `json:"value"`
	}
	if err := c.request(ctx, "POST", "/contracts/storage", body, &resp); err != nil {
		return "", err
	}
	return resp.Value, nil
}

// ==================== Verification ====================

// VerifyContractOptions are options for contract verification.
type VerifyContractOptions struct {
	Address              string
	SourceCode           string
	CompilerVersion      string
	ConstructorArguments string
	Optimization         bool
	OptimizationRuns     int
	ContractName         string
}

// VerifyContract submits a contract for verification.
func (c *Client) VerifyContract(ctx context.Context, opts VerifyContractOptions) (*VerificationResult, error) {
	body := map[string]interface{}{
		"address":         opts.Address,
		"sourceCode":      opts.SourceCode,
		"compilerVersion": opts.CompilerVersion,
		"optimization":    opts.Optimization,
		"optimizationRuns": opts.OptimizationRuns,
	}
	if opts.ConstructorArguments != "" {
		body["constructorArguments"] = opts.ConstructorArguments
	}
	if opts.ContractName != "" {
		body["contractName"] = opts.ContractName
	}

	var result VerificationResult
	if err := c.request(ctx, "POST", "/contracts/verify", body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// GetVerificationStatus gets verification status.
func (c *Client) GetVerificationStatus(ctx context.Context, address string) (*VerificationResult, error) {
	var result VerificationResult
	if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/verification", address), nil, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// GetVerifiedAbi gets the verified contract ABI.
func (c *Client) GetVerifiedAbi(ctx context.Context, address string) (Abi, error) {
	var resp struct {
		Abi Abi `json:"abi"`
	}
	if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/abi", address), nil, &resp); err != nil {
		return nil, err
	}
	return resp.Abi, nil
}

// ==================== Lifecycle ====================

// HealthCheck checks if the service is healthy.
func (c *Client) HealthCheck(ctx context.Context) bool {
	var resp struct {
		Status string `json:"status"`
	}
	if err := c.request(ctx, "GET", "/health", nil, &resp); err != nil {
		return false
	}
	return resp.Status == "healthy"
}

// Close closes the client.
func (c *Client) Close() {
	c.closed.Store(true)
}

// IsClosed returns whether the client is closed.
func (c *Client) IsClosed() bool {
	return c.closed.Load()
}

// ==================== Private ====================

func (c *Client) request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
	if c.closed.Load() {
		return &Error{Message: "client has been closed"}
	}

	var lastErr error
	for attempt := 0; attempt < c.config.Retries; attempt++ {
		err := c.doRequest(ctx, method, path, body, result)
		if err == nil {
			return nil
		}
		lastErr = err
		if attempt < c.config.Retries-1 {
			time.Sleep(time.Duration(1<<attempt) * time.Second)
		}
	}
	return lastErr
}

func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}, result interface{}) error {
	url := c.config.Endpoint + path

	var bodyReader io.Reader
	if body != nil {
		jsonBody, err := json.Marshal(body)
		if err != nil {
			return &Error{Message: fmt.Sprintf("failed to marshal body: %v", err)}
		}
		bodyReader = bytes.NewReader(jsonBody)
	}

	req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
	if err != nil {
		return &Error{Message: fmt.Sprintf("failed to create request: %v", err)}
	}

	req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-SDK-Version", "go/0.1.0")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return &Error{Message: fmt.Sprintf("request failed: %v", err)}
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return &Error{Message: fmt.Sprintf("failed to read response: %v", err)}
	}

	if resp.StatusCode >= 400 {
		var errResp struct {
			Message string `json:"message"`
			Error   string `json:"error"`
			Code    string `json:"code"`
		}
		json.Unmarshal(respBody, &errResp)
		msg := errResp.Message
		if msg == "" {
			msg = errResp.Error
		}
		if msg == "" {
			msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
		}
		return &Error{
			Message:    msg,
			Code:       errResp.Code,
			StatusCode: resp.StatusCode,
		}
	}

	if result != nil {
		if err := json.Unmarshal(respBody, result); err != nil {
			return &Error{Message: fmt.Sprintf("failed to parse response: %v", err)}
		}
	}

	return nil
}
