// Package economics provides the Synor Economics SDK for Go.
// Pricing, billing, staking, and discount management.
package economics

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

const (
	DefaultEndpoint = "https://economics.synor.io/v1"
	DefaultTimeout  = 30 * time.Second
	DefaultRetries  = 3
)

// ServiceType represents a service type for pricing.
type ServiceType string

const (
	ServiceCompute  ServiceType = "compute"
	ServiceStorage  ServiceType = "storage"
	ServiceDatabase ServiceType = "database"
	ServiceHosting  ServiceType = "hosting"
	ServiceBridge   ServiceType = "bridge"
	ServiceRPC      ServiceType = "rpc"
)

// BillingPeriod represents a billing period.
type BillingPeriod string

const (
	PeriodHourly  BillingPeriod = "hourly"
	PeriodDaily   BillingPeriod = "daily"
	PeriodWeekly  BillingPeriod = "weekly"
	PeriodMonthly BillingPeriod = "monthly"
)

// StakeStatus represents stake status.
type StakeStatus string

const (
	StakeActive    StakeStatus = "active"
	StakeUnstaking StakeStatus = "unstaking"
	StakeWithdrawn StakeStatus = "withdrawn"
	StakeSlashed   StakeStatus = "slashed"
)

// DiscountType represents discount type.
type DiscountType string

const (
	DiscountPercentage DiscountType = "percentage"
	DiscountFixed      DiscountType = "fixed"
	DiscountVolume     DiscountType = "volume"
	DiscountReferral   DiscountType = "referral"
)

// InvoiceStatus represents invoice status.
type InvoiceStatus string

const (
	InvoicePending   InvoiceStatus = "pending"
	InvoicePaid      InvoiceStatus = "paid"
	InvoiceOverdue   InvoiceStatus = "overdue"
	InvoiceCancelled InvoiceStatus = "cancelled"
)

// Config for the Economics client.
type Config struct {
	APIKey   string
	Endpoint string
	Timeout  time.Duration
	Retries  int
	Debug    bool
}

// UsageMetrics for pricing calculations.
type UsageMetrics struct {
	ComputeHours    *float64 `json:"compute_hours,omitempty"`
	StorageBytes    *int64   `json:"storage_bytes,omitempty"`
	DatabaseOps     *int64   `json:"database_ops,omitempty"`
	HostingRequests *int64   `json:"hosting_requests,omitempty"`
	BridgeTransfers *int64   `json:"bridge_transfers,omitempty"`
	RPCCalls        *int64   `json:"rpc_calls,omitempty"`
}

// ServiceUsage in a plan.
type ServiceUsage struct {
	Service ServiceType  `json:"service"`
	Metrics UsageMetrics `json:"metrics"`
	Tier    string       `json:"tier,omitempty"`
}

// UsagePlan for cost estimation.
type UsagePlan struct {
	Services  []ServiceUsage `json:"services"`
	Period    BillingPeriod  `json:"period"`
	StartDate *int64         `json:"start_date,omitempty"`
	EndDate   *int64         `json:"end_date,omitempty"`
}

// AppliedDiscount represents a discount applied to a price.
type AppliedDiscount struct {
	Code        string       `json:"code"`
	Type        DiscountType `json:"type"`
	Value       string       `json:"value"`
	Savings     string       `json:"savings"`
	Description string       `json:"description,omitempty"`
}

// TaxAmount represents tax.
type TaxAmount struct {
	Name   string  `json:"name"`
	Rate   float64 `json:"rate"`
	Amount string  `json:"amount"`
}

// Price breakdown.
type Price struct {
	Service   ServiceType       `json:"service"`
	BasePrice string            `json:"base_price"`
	Quantity  string            `json:"quantity"`
	Unit      string            `json:"unit"`
	Subtotal  string            `json:"subtotal"`
	Discounts []AppliedDiscount `json:"discounts"`
	Total     string            `json:"total"`
	Currency  string            `json:"currency"`
}

// CostEstimate for a usage plan.
type CostEstimate struct {
	Services   []Price           `json:"services"`
	Subtotal   string            `json:"subtotal"`
	Discounts  []AppliedDiscount `json:"discounts"`
	Taxes      []TaxAmount       `json:"taxes"`
	Total      string            `json:"total"`
	Currency   string            `json:"currency"`
	ValidUntil int64             `json:"valid_until"`
}

// UsageDetail for a single operation.
type UsageDetail struct {
	Timestamp int64   `json:"timestamp"`
	Operation string  `json:"operation"`
	Quantity  float64 `json:"quantity"`
	Unit      string  `json:"unit"`
	Cost      string  `json:"cost"`
}

// ServiceUsageRecord for billing.
type ServiceUsageRecord struct {
	Service ServiceType   `json:"service"`
	Metrics UsageMetrics  `json:"metrics"`
	Cost    string        `json:"cost"`
	Details []UsageDetail `json:"details"`
}

// Usage record.
type Usage struct {
	Period    BillingPeriod        `json:"period"`
	StartDate int64                `json:"start_date"`
	EndDate   int64                `json:"end_date"`
	Services  []ServiceUsageRecord `json:"services"`
	TotalCost string               `json:"total_cost"`
	Currency  string               `json:"currency"`
}

// InvoiceItem in an invoice.
type InvoiceItem struct {
	Service     ServiceType `json:"service"`
	Description string      `json:"description"`
	Quantity    string      `json:"quantity"`
	UnitPrice   string      `json:"unit_price"`
	Total       string      `json:"total"`
}

// Invoice record.
type Invoice struct {
	ID            string            `json:"id"`
	Status        InvoiceStatus     `json:"status"`
	Period        BillingPeriod     `json:"period"`
	StartDate     int64             `json:"start_date"`
	EndDate       int64             `json:"end_date"`
	Items         []InvoiceItem     `json:"items"`
	Subtotal      string            `json:"subtotal"`
	Discounts     []AppliedDiscount `json:"discounts"`
	Taxes         []TaxAmount       `json:"taxes"`
	Total         string            `json:"total"`
	Currency      string            `json:"currency"`
	DueDate       int64             `json:"due_date"`
	PaidAt        *int64            `json:"paid_at,omitempty"`
	PaymentMethod string            `json:"payment_method,omitempty"`
}

// AccountBalance for billing.
type AccountBalance struct {
	Available   string `json:"available"`
	Pending     string `json:"pending"`
	Reserved    string `json:"reserved"`
	Total       string `json:"total"`
	Currency    string `json:"currency"`
	CreditLimit string `json:"credit_limit,omitempty"`
	LastUpdated int64  `json:"last_updated"`
}

// StakeReceipt from staking.
type StakeReceipt struct {
	ID               string `json:"id"`
	Amount           string `json:"amount"`
	LockDuration     int    `json:"lock_duration"`
	StartDate        int64  `json:"start_date"`
	EndDate          int64  `json:"end_date"`
	APY              string `json:"apy"`
	EstimatedRewards string `json:"estimated_rewards"`
	TxHash           string `json:"tx_hash"`
}

// UnstakeReceipt from unstaking.
type UnstakeReceipt struct {
	ID              string `json:"id"`
	StakeID         string `json:"stake_id"`
	Amount          string `json:"amount"`
	Rewards         string `json:"rewards"`
	Total           string `json:"total"`
	UnbondingPeriod int    `json:"unbonding_period"`
	AvailableAt     int64  `json:"available_at"`
	TxHash          string `json:"tx_hash"`
}

// StakeInfo for active stakes.
type StakeInfo struct {
	ID             string      `json:"id"`
	Amount         string      `json:"amount"`
	Status         StakeStatus `json:"status"`
	LockDuration   int         `json:"lock_duration"`
	StartDate      int64       `json:"start_date"`
	EndDate        int64       `json:"end_date"`
	APY            string      `json:"apy"`
	EarnedRewards  string      `json:"earned_rewards"`
	PendingRewards string      `json:"pending_rewards"`
	Validator      string      `json:"validator,omitempty"`
}

// StakeRewardDetail for individual stake rewards.
type StakeRewardDetail struct {
	StakeID string `json:"stake_id"`
	Earned  string `json:"earned"`
	Pending string `json:"pending"`
	APY     string `json:"apy"`
}

// StakingRewards summary.
type StakingRewards struct {
	TotalEarned        string              `json:"total_earned"`
	Pending            string              `json:"pending"`
	Claimed            string              `json:"claimed"`
	LastClaimDate      *int64              `json:"last_claim_date,omitempty"`
	NextClaimAvailable *int64              `json:"next_claim_available,omitempty"`
	Stakes             []StakeRewardDetail `json:"stakes"`
}

// Discount available or active.
type Discount struct {
	Code               string        `json:"code"`
	Type               DiscountType  `json:"type"`
	Value              string        `json:"value"`
	Description        string        `json:"description"`
	ValidFrom          int64         `json:"valid_from"`
	ValidUntil         int64         `json:"valid_until"`
	MinPurchase        string        `json:"min_purchase,omitempty"`
	MaxDiscount        string        `json:"max_discount,omitempty"`
	ApplicableServices []ServiceType `json:"applicable_services"`
	UsageLimit         *int          `json:"usage_limit,omitempty"`
	UsedCount          int           `json:"used_count"`
}

// StakeOptions for staking.
type StakeOptions struct {
	Validator    string `json:"validator,omitempty"`
	AutoCompound *bool  `json:"auto_compound,omitempty"`
	LockDuration *int   `json:"lock_duration,omitempty"`
}

// StakingAPY response.
type StakingAPY struct {
	APY             string `json:"apy"`
	MinLockDuration int    `json:"min_lock_duration"`
	MaxLockDuration int    `json:"max_lock_duration"`
}

// Client for the Synor Economics SDK.
type Client struct {
	config Config
	client *http.Client
	mu     sync.RWMutex
	closed bool
}

// NewClient creates a new Economics client.
func NewClient(config Config) *Client {
	if config.Endpoint == "" {
		config.Endpoint = DefaultEndpoint
	}
	if config.Timeout == 0 {
		config.Timeout = DefaultTimeout
	}
	if config.Retries == 0 {
		config.Retries = DefaultRetries
	}

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

// ==================== Pricing Operations ====================

// GetPrice gets price for a service based on usage.
func (c *Client) GetPrice(ctx context.Context, service ServiceType, usage UsageMetrics) (*Price, error) {
	var resp struct {
		Price Price `json:"price"`
	}
	err := c.post(ctx, "/pricing/calculate", map[string]interface{}{
		"service": service,
		"usage":   usage,
	}, &resp)
	if err != nil {
		return nil, err
	}
	return &resp.Price, nil
}

// EstimateCost estimates cost for a usage plan.
func (c *Client) EstimateCost(ctx context.Context, plan UsagePlan) (*CostEstimate, error) {
	var resp CostEstimate
	err := c.post(ctx, "/pricing/estimate", plan, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetPricingTiers gets pricing tiers for a service.
func (c *Client) GetPricingTiers(ctx context.Context, service ServiceType) ([]Price, error) {
	var resp struct {
		Tiers []Price `json:"tiers"`
	}
	err := c.get(ctx, fmt.Sprintf("/pricing/%s/tiers", service), &resp)
	if err != nil {
		return nil, err
	}
	return resp.Tiers, nil
}

// ==================== Usage & Billing Operations ====================

// GetUsage gets usage for a billing period.
func (c *Client) GetUsage(ctx context.Context, period *BillingPeriod) (*Usage, error) {
	path := "/usage"
	if period != nil {
		path = fmt.Sprintf("/usage?period=%s", *period)
	}
	var resp Usage
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetUsageHistory gets usage history.
func (c *Client) GetUsageHistory(ctx context.Context, limit, offset *int) ([]Usage, error) {
	params := url.Values{}
	if limit != nil {
		params.Set("limit", fmt.Sprintf("%d", *limit))
	}
	if offset != nil {
		params.Set("offset", fmt.Sprintf("%d", *offset))
	}
	path := "/usage/history"
	if len(params) > 0 {
		path = fmt.Sprintf("%s?%s", path, params.Encode())
	}
	var resp struct {
		Usage []Usage `json:"usage"`
	}
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return resp.Usage, nil
}

// GetInvoices gets all invoices.
func (c *Client) GetInvoices(ctx context.Context) ([]Invoice, error) {
	var resp struct {
		Invoices []Invoice `json:"invoices"`
	}
	err := c.get(ctx, "/invoices", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Invoices, nil
}

// GetInvoice gets a specific invoice.
func (c *Client) GetInvoice(ctx context.Context, invoiceID string) (*Invoice, error) {
	var resp Invoice
	err := c.get(ctx, fmt.Sprintf("/invoices/%s", url.PathEscape(invoiceID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// PayInvoice pays an invoice.
func (c *Client) PayInvoice(ctx context.Context, invoiceID, paymentMethod string) (*Invoice, error) {
	var resp Invoice
	body := make(map[string]interface{})
	if paymentMethod != "" {
		body["payment_method"] = paymentMethod
	}
	err := c.post(ctx, fmt.Sprintf("/invoices/%s/pay", url.PathEscape(invoiceID)), body, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetBalance gets account balance.
func (c *Client) GetBalance(ctx context.Context) (*AccountBalance, error) {
	var resp AccountBalance
	err := c.get(ctx, "/balance", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// AddFunds adds funds to account.
func (c *Client) AddFunds(ctx context.Context, amount, paymentMethod string) (*AccountBalance, error) {
	var resp AccountBalance
	err := c.post(ctx, "/balance/deposit", map[string]interface{}{
		"amount":         amount,
		"payment_method": paymentMethod,
	}, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== Staking Operations ====================

// Stake stakes tokens.
func (c *Client) Stake(ctx context.Context, amount string, options *StakeOptions) (*StakeReceipt, error) {
	body := map[string]interface{}{"amount": amount}
	if options != nil {
		if options.Validator != "" {
			body["validator"] = options.Validator
		}
		if options.AutoCompound != nil {
			body["auto_compound"] = *options.AutoCompound
		}
		if options.LockDuration != nil {
			body["lock_duration"] = *options.LockDuration
		}
	}
	var resp StakeReceipt
	err := c.post(ctx, "/staking/stake", body, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// Unstake unstakes tokens.
func (c *Client) Unstake(ctx context.Context, stakeID string) (*UnstakeReceipt, error) {
	var resp UnstakeReceipt
	err := c.post(ctx, fmt.Sprintf("/staking/stakes/%s/unstake", url.PathEscape(stakeID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetStakingRewards gets staking rewards.
func (c *Client) GetStakingRewards(ctx context.Context) (*StakingRewards, error) {
	var resp StakingRewards
	err := c.get(ctx, "/staking/rewards", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ClaimRewards claims staking rewards.
func (c *Client) ClaimRewards(ctx context.Context, stakeID string) (*StakingRewards, error) {
	path := "/staking/rewards/claim"
	if stakeID != "" {
		path = fmt.Sprintf("/staking/stakes/%s/claim", url.PathEscape(stakeID))
	}
	var resp StakingRewards
	err := c.post(ctx, path, nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ListStakes lists active stakes.
func (c *Client) ListStakes(ctx context.Context) ([]StakeInfo, error) {
	var resp struct {
		Stakes []StakeInfo `json:"stakes"`
	}
	err := c.get(ctx, "/staking/stakes", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Stakes, nil
}

// GetStake gets stake details.
func (c *Client) GetStake(ctx context.Context, stakeID string) (*StakeInfo, error) {
	var resp StakeInfo
	err := c.get(ctx, fmt.Sprintf("/staking/stakes/%s", url.PathEscape(stakeID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetStakingAPY gets current APY for staking.
func (c *Client) GetStakingAPY(ctx context.Context) (*StakingAPY, error) {
	var resp StakingAPY
	err := c.get(ctx, "/staking/apy", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== Discount Operations ====================

// ApplyDiscount applies a discount code.
func (c *Client) ApplyDiscount(ctx context.Context, code string) (*Discount, error) {
	var resp Discount
	err := c.post(ctx, "/discounts/apply", map[string]interface{}{"code": code}, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetAvailableDiscounts gets available discounts.
func (c *Client) GetAvailableDiscounts(ctx context.Context) ([]Discount, error) {
	var resp struct {
		Discounts []Discount `json:"discounts"`
	}
	err := c.get(ctx, "/discounts", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Discounts, nil
}

// GetActiveDiscounts gets active discounts on account.
func (c *Client) GetActiveDiscounts(ctx context.Context) ([]Discount, error) {
	var resp struct {
		Discounts []Discount `json:"discounts"`
	}
	err := c.get(ctx, "/discounts/active", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Discounts, nil
}

// RemoveDiscount removes a discount.
func (c *Client) RemoveDiscount(ctx context.Context, code string) error {
	return c.delete(ctx, fmt.Sprintf("/discounts/%s", url.PathEscape(code)))
}

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

// HealthCheck performs a health check.
func (c *Client) HealthCheck(ctx context.Context) bool {
	var resp struct {
		Status string `json:"status"`
	}
	err := c.get(ctx, "/health", &resp)
	return err == nil && resp.Status == "healthy"
}

// IsClosed returns true if the client is closed.
func (c *Client) IsClosed() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.closed
}

// Close closes the client.
func (c *Client) Close() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.closed = true
}

// ==================== Private Methods ====================

func (c *Client) get(ctx context.Context, path string, result interface{}) error {
	return c.execute(ctx, "GET", path, nil, result)
}

func (c *Client) post(ctx context.Context, path string, body, result interface{}) error {
	return c.execute(ctx, "POST", path, body, result)
}

func (c *Client) delete(ctx context.Context, path string) error {
	return c.execute(ctx, "DELETE", path, nil, nil)
}

func (c *Client) execute(ctx context.Context, method, path string, body, result interface{}) error {
	c.mu.RLock()
	if c.closed {
		c.mu.RUnlock()
		return &EconomicsError{Message: "Client has been closed"}
	}
	c.mu.RUnlock()

	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 c.config.Debug {
			fmt.Printf("Attempt %d failed: %v\n", attempt+1, 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, result interface{}) error {
	url := c.config.Endpoint + path

	var bodyReader io.Reader
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return err
		}
		bodyReader = bytes.NewReader(data)
	}

	req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
	if err != nil {
		return 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.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

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

	if result != nil && len(respBody) > 0 {
		return json.Unmarshal(respBody, result)
	}
	return nil
}

// EconomicsError represents an error from the Economics API.
type EconomicsError struct {
	Message    string
	Code       string
	StatusCode int
}

func (e *EconomicsError) Error() string {
	if e.Code != "" {
		return fmt.Sprintf("%s (%s)", e.Message, e.Code)
	}
	return e.Message
}
