// Package wallet provides a Go SDK for Synor Wallet operations.
//
// Manage wallets, sign transactions, and query balances on the Synor blockchain.
//
// Example:
//
//	client := wallet.NewClient("your-api-key")
//	result, err := client.CreateWallet(ctx, wallet.Standard)
//	fmt.Println("Address:", result.Wallet.Address)
//	fmt.Println("Mnemonic:", result.Mnemonic) // Store securely!
package wallet

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

// Version of the SDK.
const Version = "0.1.0"

// DefaultEndpoint is the default API endpoint.
const DefaultEndpoint = "https://wallet.synor.cc/api/v1"

// Network represents the blockchain network.
type Network string

const (
	Mainnet Network = "mainnet"
	Testnet Network = "testnet"
)

// WalletType represents the type of wallet.
type WalletType string

const (
	Standard WalletType = "standard"
	Multisig WalletType = "multisig"
	Stealth  WalletType = "stealth"
	Hardware WalletType = "hardware"
)

// Priority represents transaction priority levels.
type Priority string

const (
	Low    Priority = "low"
	Medium Priority = "medium"
	High   Priority = "high"
	Urgent Priority = "urgent"
)

// Config holds client configuration.
type Config struct {
	APIKey         string
	Endpoint       string
	Network        Network
	Timeout        time.Duration
	Debug          bool
	DerivationPath string
}

// DefaultConfig returns a default configuration.
func DefaultConfig(apiKey string) Config {
	return Config{
		APIKey:   apiKey,
		Endpoint: DefaultEndpoint,
		Network:  Mainnet,
		Timeout:  30 * time.Second,
		Debug:    false,
	}
}

// Client is the Synor Wallet client.
type Client struct {
	config     Config
	httpClient *http.Client
}

// NewClient creates a new client with the given API key.
func NewClient(apiKey string) *Client {
	return NewClientWithConfig(DefaultConfig(apiKey))
}

// NewClientWithConfig creates a new client with custom configuration.
func NewClientWithConfig(config Config) *Client {
	return &Client{
		config: config,
		httpClient: &http.Client{
			Timeout: config.Timeout,
		},
	}
}

// Wallet represents a wallet instance.
type Wallet struct {
	ID        string     `json:"id"`
	Address   string     `json:"address"`
	PublicKey string     `json:"publicKey"`
	Type      WalletType `json:"type"`
	CreatedAt int64      `json:"createdAt"`
}

// CreateWalletResult contains the created wallet and mnemonic.
type CreateWalletResult struct {
	Wallet   Wallet `json:"wallet"`
	Mnemonic string `json:"mnemonic"`
}

// StealthAddress represents a stealth address for private payments.
type StealthAddress struct {
	Address      string `json:"address"`
	ViewKey      string `json:"viewKey"`
	SpendKey     string `json:"spendKey"`
	EphemeralKey string `json:"ephemeralKey,omitempty"`
}

// TransactionInput represents a transaction input.
type TransactionInput struct {
	TxID      string `json:"txid"`
	Vout      int    `json:"vout"`
	Amount    string `json:"amount"`
	ScriptSig string `json:"scriptSig,omitempty"`
}

// TransactionOutput represents a transaction output.
type TransactionOutput struct {
	Address      string `json:"address"`
	Amount       string `json:"amount"`
	ScriptPubKey string `json:"scriptPubKey,omitempty"`
}

// Transaction represents an unsigned transaction.
type Transaction struct {
	Version  int                 `json:"version"`
	Inputs   []TransactionInput  `json:"inputs"`
	Outputs  []TransactionOutput `json:"outputs"`
	LockTime int                 `json:"lockTime,omitempty"`
	Fee      string              `json:"fee,omitempty"`
}

// SignedTransaction represents a signed transaction.
type SignedTransaction struct {
	Raw    string `json:"raw"`
	TxID   string `json:"txid"`
	Size   int    `json:"size"`
	Weight int    `json:"weight,omitempty"`
}

// SignedMessage represents a signed message.
type SignedMessage struct {
	Signature string `json:"signature"`
	PublicKey string `json:"publicKey"`
	Address   string `json:"address"`
}

// UTXO represents an unspent transaction output.
type UTXO struct {
	TxID          string `json:"txid"`
	Vout          int    `json:"vout"`
	Amount        string `json:"amount"`
	Address       string `json:"address"`
	Confirmations int    `json:"confirmations"`
	ScriptPubKey  string `json:"scriptPubKey,omitempty"`
}

// Balance represents balance information.
type Balance struct {
	Confirmed   string `json:"confirmed"`
	Unconfirmed string `json:"unconfirmed"`
	Total       string `json:"total"`
}

// TokenBalance represents a token balance.
type TokenBalance struct {
	Token    string `json:"token"`
	Symbol   string `json:"symbol"`
	Decimals int    `json:"decimals"`
	Balance  string `json:"balance"`
}

// BalanceResponse contains native and token balances.
type BalanceResponse struct {
	Native Balance        `json:"native"`
	Tokens []TokenBalance `json:"tokens,omitempty"`
}

// FeeEstimate represents a fee estimation result.
type FeeEstimate struct {
	Priority        Priority `json:"priority"`
	FeeRate         string   `json:"feeRate"`
	EstimatedBlocks int      `json:"estimatedBlocks"`
}

// ImportWalletOptions contains options for importing a wallet.
type ImportWalletOptions struct {
	Mnemonic   string
	Passphrase string
	Type       WalletType
}

// BuildTransactionOptions contains options for building a transaction.
type BuildTransactionOptions struct {
	To            string
	Amount        string
	FeeRate       float64
	UTXOs         []UTXO
	ChangeAddress string
}

// GetUtxosOptions contains options for querying UTXOs.
type GetUtxosOptions struct {
	MinConfirmations int
	MinAmount        string
}

// CreateWallet creates a new wallet.
func (c *Client) CreateWallet(ctx context.Context, walletType WalletType) (*CreateWalletResult, error) {
	body := map[string]interface{}{
		"type":    walletType,
		"network": c.config.Network,
	}
	if c.config.DerivationPath != "" {
		body["derivationPath"] = c.config.DerivationPath
	}

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

// ImportWallet imports a wallet from mnemonic phrase.
func (c *Client) ImportWallet(ctx context.Context, opts ImportWalletOptions) (*Wallet, error) {
	body := map[string]interface{}{
		"mnemonic":   opts.Mnemonic,
		"passphrase": opts.Passphrase,
		"type":       opts.Type,
		"network":    c.config.Network,
	}
	if c.config.DerivationPath != "" {
		body["derivationPath"] = c.config.DerivationPath
	}

	var resp struct {
		Wallet Wallet `json:"wallet"`
	}
	if err := c.request(ctx, "POST", "/wallets/import", body, &resp); err != nil {
		return nil, err
	}
	return &resp.Wallet, nil
}

// GetWallet gets a wallet by ID.
func (c *Client) GetWallet(ctx context.Context, walletID string) (*Wallet, error) {
	var resp struct {
		Wallet Wallet `json:"wallet"`
	}
	if err := c.request(ctx, "GET", "/wallets/"+walletID, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.Wallet, nil
}

// ListWallets lists all wallets for this account.
func (c *Client) ListWallets(ctx context.Context) ([]Wallet, error) {
	var resp struct {
		Wallets []Wallet `json:"wallets"`
	}
	if err := c.request(ctx, "GET", "/wallets", nil, &resp); err != nil {
		return nil, err
	}
	return resp.Wallets, nil
}

// GetAddress gets an address at a specific index for a wallet.
func (c *Client) GetAddress(ctx context.Context, walletID string, index int) (string, error) {
	path := fmt.Sprintf("/wallets/%s/addresses/%d", walletID, index)
	var resp struct {
		Address string `json:"address"`
	}
	if err := c.request(ctx, "GET", path, nil, &resp); err != nil {
		return "", err
	}
	return resp.Address, nil
}

// GetStealthAddress generates a stealth address for receiving private payments.
func (c *Client) GetStealthAddress(ctx context.Context, walletID string) (*StealthAddress, error) {
	path := fmt.Sprintf("/wallets/%s/stealth", walletID)
	var resp struct {
		StealthAddress StealthAddress `json:"stealthAddress"`
	}
	if err := c.request(ctx, "POST", path, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.StealthAddress, nil
}

// SignTransaction signs a transaction.
func (c *Client) SignTransaction(ctx context.Context, walletID string, tx *Transaction) (*SignedTransaction, error) {
	path := fmt.Sprintf("/wallets/%s/sign", walletID)
	body := map[string]interface{}{
		"transaction": tx,
	}
	var resp struct {
		SignedTransaction SignedTransaction `json:"signedTransaction"`
	}
	if err := c.request(ctx, "POST", path, body, &resp); err != nil {
		return nil, err
	}
	return &resp.SignedTransaction, nil
}

// SignMessage signs a message.
func (c *Client) SignMessage(ctx context.Context, walletID, message, format string) (*SignedMessage, error) {
	if format == "" {
		format = "text"
	}
	path := fmt.Sprintf("/wallets/%s/sign-message", walletID)
	body := map[string]interface{}{
		"message": message,
		"format":  format,
	}
	var result SignedMessage
	if err := c.request(ctx, "POST", path, body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// VerifyMessage verifies a signed message.
func (c *Client) VerifyMessage(ctx context.Context, message, signature, address string) (bool, error) {
	body := map[string]interface{}{
		"message":   message,
		"signature": signature,
		"address":   address,
	}
	var resp struct {
		Valid bool `json:"valid"`
	}
	if err := c.request(ctx, "POST", "/verify-message", body, &resp); err != nil {
		return false, err
	}
	return resp.Valid, nil
}

// GetBalance gets the balance for an address.
func (c *Client) GetBalance(ctx context.Context, address string, includeTokens bool) (*BalanceResponse, error) {
	path := fmt.Sprintf("/balances/%s?includeTokens=%t", address, includeTokens)
	var result BalanceResponse
	if err := c.request(ctx, "GET", path, nil, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// GetUTXOs gets UTXOs for an address.
func (c *Client) GetUTXOs(ctx context.Context, address string, opts *GetUtxosOptions) ([]UTXO, error) {
	path := "/utxos/" + address
	if opts != nil {
		params := url.Values{}
		if opts.MinConfirmations > 0 {
			params.Set("minConfirmations", fmt.Sprintf("%d", opts.MinConfirmations))
		}
		if opts.MinAmount != "" {
			params.Set("minAmount", opts.MinAmount)
		}
		if len(params) > 0 {
			path += "?" + params.Encode()
		}
	}

	var resp struct {
		UTXOs []UTXO `json:"utxos"`
	}
	if err := c.request(ctx, "GET", path, nil, &resp); err != nil {
		return nil, err
	}
	return resp.UTXOs, nil
}

// BuildTransaction builds a transaction without signing.
func (c *Client) BuildTransaction(ctx context.Context, walletID string, opts BuildTransactionOptions) (*Transaction, error) {
	path := fmt.Sprintf("/wallets/%s/build-tx", walletID)
	body := map[string]interface{}{
		"to":     opts.To,
		"amount": opts.Amount,
	}
	if opts.FeeRate > 0 {
		body["feeRate"] = opts.FeeRate
	}
	if len(opts.UTXOs) > 0 {
		body["utxos"] = opts.UTXOs
	}
	if opts.ChangeAddress != "" {
		body["changeAddress"] = opts.ChangeAddress
	}

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

// SendTransaction builds and signs a transaction in one step.
func (c *Client) SendTransaction(ctx context.Context, walletID string, opts BuildTransactionOptions) (*SignedTransaction, error) {
	tx, err := c.BuildTransaction(ctx, walletID, opts)
	if err != nil {
		return nil, err
	}
	return c.SignTransaction(ctx, walletID, tx)
}

// EstimateFee estimates the transaction fee.
func (c *Client) EstimateFee(ctx context.Context, priority Priority) (*FeeEstimate, error) {
	path := fmt.Sprintf("/fees/estimate?priority=%s", priority)
	var result FeeEstimate
	if err := c.request(ctx, "GET", path, nil, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// GetAllFeeEstimates gets fee estimates for all priority levels.
func (c *Client) GetAllFeeEstimates(ctx context.Context) ([]FeeEstimate, error) {
	var resp struct {
		Estimates []FeeEstimate `json:"estimates"`
	}
	if err := c.request(ctx, "GET", "/fees/estimate/all", nil, &resp); err != nil {
		return nil, err
	}
	return resp.Estimates, nil
}

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

	var bodyReader io.Reader
	if body != nil {
		bodyBytes, err := json.Marshal(body)
		if err != nil {
			return fmt.Errorf("failed to marshal request: %w", err)
		}
		bodyReader = bytes.NewReader(bodyBytes)
	}

	req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Network", string(c.config.Network))

	if c.config.Debug {
		fmt.Printf("[SynorWallet] %s %s\n", method, path)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		var errResp struct {
			Message string `json:"message"`
			Code    string `json:"code"`
		}
		json.NewDecoder(resp.Body).Decode(&errResp)
		return &WalletError{
			Message:    errResp.Message,
			StatusCode: resp.StatusCode,
			Code:       errResp.Code,
		}
	}

	if result != nil {
		if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
			return fmt.Errorf("failed to decode response: %w", err)
		}
	}

	return nil
}

// WalletError represents an API error.
type WalletError struct {
	Message    string
	StatusCode int
	Code       string
}

func (e *WalletError) Error() string {
	return fmt.Sprintf("wallet: %s (status %d)", e.Message, e.StatusCode)
}
