// Package mining provides the Synor Mining SDK for Go.
// Pool connections, block templates, hashrate stats, and GPU management.
package mining

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

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

// DeviceType represents device type.
type DeviceType string

const (
	DeviceCPU       DeviceType = "cpu"
	DeviceGPUNvidia DeviceType = "gpu_nvidia"
	DeviceGPUAMD    DeviceType = "gpu_amd"
	DeviceASIC      DeviceType = "asic"
)

// DeviceStatus represents device status.
type DeviceStatus string

const (
	DeviceIdle    DeviceStatus = "idle"
	DeviceMining  DeviceStatus = "mining"
	DeviceError   DeviceStatus = "error"
	DeviceOffline DeviceStatus = "offline"
)

// ConnectionStatus represents connection status.
type ConnectionStatus string

const (
	StatusDisconnected  ConnectionStatus = "disconnected"
	StatusConnecting    ConnectionStatus = "connecting"
	StatusConnected     ConnectionStatus = "connected"
	StatusReconnecting  ConnectionStatus = "reconnecting"
)

// TimePeriod for stats.
type TimePeriod string

const (
	PeriodHour  TimePeriod = "hour"
	PeriodDay   TimePeriod = "day"
	PeriodWeek  TimePeriod = "week"
	PeriodMonth TimePeriod = "month"
	PeriodAll   TimePeriod = "all"
)

// SubmitResultStatus represents submit result status.
type SubmitResultStatus string

const (
	SubmitAccepted SubmitResultStatus = "accepted"
	SubmitRejected SubmitResultStatus = "rejected"
	SubmitStale    SubmitResultStatus = "stale"
)

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

// PoolConfig for connecting to a pool.
type PoolConfig struct {
	URL        string   `json:"url"`
	User       string   `json:"user"`
	Password   string   `json:"password,omitempty"`
	Algorithm  string   `json:"algorithm,omitempty"`
	Difficulty *float64 `json:"difficulty,omitempty"`
}

// StratumConnection represents a pool connection.
type StratumConnection struct {
	ID             string           `json:"id"`
	Pool           string           `json:"pool"`
	Status         ConnectionStatus `json:"status"`
	Algorithm      string           `json:"algorithm"`
	Difficulty     float64          `json:"difficulty"`
	ConnectedAt    int64            `json:"connected_at"`
	LastShareAt    *int64           `json:"last_share_at,omitempty"`
	AcceptedShares int64            `json:"accepted_shares"`
	RejectedShares int64            `json:"rejected_shares"`
	StaleShares    int64            `json:"stale_shares"`
}

// TemplateTransaction in a block template.
type TemplateTransaction struct {
	TxID   string `json:"txid"`
	Data   string `json:"data"`
	Fee    string `json:"fee"`
	Weight int    `json:"weight"`
}

// BlockTemplate for mining.
type BlockTemplate struct {
	ID                string                `json:"id"`
	PreviousBlockHash string                `json:"previous_block_hash"`
	MerkleRoot        string                `json:"merkle_root"`
	Timestamp         int64                 `json:"timestamp"`
	Bits              string                `json:"bits"`
	Height            int64                 `json:"height"`
	CoinbaseValue     string                `json:"coinbase_value"`
	Transactions      []TemplateTransaction `json:"transactions"`
	Target            string                `json:"target"`
	Algorithm         string                `json:"algorithm"`
	ExtraNonce        string                `json:"extra_nonce"`
}

// MinedWork to submit.
type MinedWork struct {
	TemplateID string `json:"template_id"`
	Nonce      string `json:"nonce"`
	ExtraNonce string `json:"extra_nonce"`
	Timestamp  int64  `json:"timestamp"`
	Hash       string `json:"hash"`
}

// ShareInfo in submit result.
type ShareInfo struct {
	Hash       string  `json:"hash"`
	Difficulty float64 `json:"difficulty"`
	Timestamp  int64   `json:"timestamp"`
	Accepted   bool    `json:"accepted"`
}

// SubmitResult from submitting work.
type SubmitResult struct {
	Status     SubmitResultStatus `json:"status"`
	Reason     string             `json:"reason,omitempty"`
	Share      ShareInfo          `json:"share"`
	BlockFound bool               `json:"block_found"`
	BlockHash  string             `json:"block_hash,omitempty"`
	Reward     string             `json:"reward,omitempty"`
}

// Hashrate statistics.
type Hashrate struct {
	Current    float64 `json:"current"`
	Average1h  float64 `json:"average_1h"`
	Average24h float64 `json:"average_24h"`
	Peak       float64 `json:"peak"`
	Unit       string  `json:"unit"`
}

// ShareStats for mining.
type ShareStats struct {
	Accepted   int64   `json:"accepted"`
	Rejected   int64   `json:"rejected"`
	Stale      int64   `json:"stale"`
	Total      int64   `json:"total"`
	AcceptRate float64 `json:"accept_rate"`
}

// DeviceTemperature info.
type DeviceTemperature struct {
	Current    float64 `json:"current"`
	Max        float64 `json:"max"`
	Throttling bool    `json:"throttling"`
}

// EarningsSnapshot for quick view.
type EarningsSnapshot struct {
	Today     string `json:"today"`
	Yesterday string `json:"yesterday"`
	ThisWeek  string `json:"this_week"`
	ThisMonth string `json:"this_month"`
	Total     string `json:"total"`
	Currency  string `json:"currency"`
}

// MiningStats comprehensive stats.
type MiningStats struct {
	Hashrate         Hashrate           `json:"hashrate"`
	Shares           ShareStats         `json:"shares"`
	Uptime           int64              `json:"uptime"`
	Efficiency       float64            `json:"efficiency"`
	PowerConsumption *float64           `json:"power_consumption,omitempty"`
	Temperature      *DeviceTemperature `json:"temperature,omitempty"`
	Earnings         EarningsSnapshot   `json:"earnings"`
}

// EarningsBreakdown by date.
type EarningsBreakdown struct {
	Date     int64   `json:"date"`
	Amount   string  `json:"amount"`
	Blocks   int     `json:"blocks"`
	Shares   int64   `json:"shares"`
	Hashrate float64 `json:"hashrate"`
}

// Earnings detailed info.
type Earnings struct {
	Period          TimePeriod          `json:"period"`
	StartDate       int64               `json:"start_date"`
	EndDate         int64               `json:"end_date"`
	Amount          string              `json:"amount"`
	Blocks          int                 `json:"blocks"`
	Shares          int64               `json:"shares"`
	AverageHashrate float64             `json:"average_hashrate"`
	Currency        string              `json:"currency"`
	Breakdown       []EarningsBreakdown `json:"breakdown"`
}

// MiningDevice represents a mining device.
type MiningDevice struct {
	ID          string       `json:"id"`
	Name        string       `json:"name"`
	Type        DeviceType   `json:"type"`
	Status      DeviceStatus `json:"status"`
	Hashrate    float64      `json:"hashrate"`
	Temperature float64      `json:"temperature"`
	FanSpeed    float64      `json:"fan_speed"`
	PowerDraw   float64      `json:"power_draw"`
	MemoryUsed  int64        `json:"memory_used"`
	MemoryTotal int64        `json:"memory_total"`
	Driver      string       `json:"driver,omitempty"`
	Firmware    string       `json:"firmware,omitempty"`
}

// DeviceConfig for device settings.
type DeviceConfig struct {
	Enabled           bool  `json:"enabled"`
	Intensity         *int  `json:"intensity,omitempty"`
	PowerLimit        *int  `json:"power_limit,omitempty"`
	CoreClockOffset   *int  `json:"core_clock_offset,omitempty"`
	MemoryClockOffset *int  `json:"memory_clock_offset,omitempty"`
	FanSpeed          *int  `json:"fan_speed,omitempty"`
}

// WorkerInfo for worker details.
type WorkerInfo struct {
	ID       string           `json:"id"`
	Name     string           `json:"name"`
	Status   ConnectionStatus `json:"status"`
	Hashrate Hashrate         `json:"hashrate"`
	Shares   ShareStats       `json:"shares"`
	Devices  []MiningDevice   `json:"devices"`
	LastSeen int64            `json:"last_seen"`
	Uptime   int64            `json:"uptime"`
}

// PoolStats for pool info.
type PoolStats struct {
	URL            string  `json:"url"`
	Workers        int     `json:"workers"`
	Hashrate       float64 `json:"hashrate"`
	Difficulty     float64 `json:"difficulty"`
	LastBlock      int64   `json:"last_block"`
	BlocksFound24h int     `json:"blocks_found_24h"`
	Luck           float64 `json:"luck"`
}

// MiningAlgorithm info.
type MiningAlgorithm struct {
	Name          string  `json:"name"`
	DisplayName   string  `json:"display_name"`
	HashUnit      string  `json:"hash_unit"`
	Profitability string  `json:"profitability"`
	Difficulty    float64 `json:"difficulty"`
	BlockReward   string  `json:"block_reward"`
	BlockTime     int     `json:"block_time"`
}

// WorkResult from getwork.
type WorkResult struct {
	Work      string `json:"work"`
	Target    string `json:"target"`
	Algorithm string `json:"algorithm"`
}

// StartMiningResult from start mining.
type StartMiningResult struct {
	Started bool     `json:"started"`
	Devices []string `json:"devices"`
}

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

// NewClient creates a new Mining 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},
	}
}

// ==================== Pool Connection ====================

// Connect connects to a mining pool.
func (c *Client) Connect(ctx context.Context, pool PoolConfig) (*StratumConnection, error) {
	var resp StratumConnection
	err := c.post(ctx, "/pool/connect", pool, &resp)
	if err != nil {
		return nil, err
	}
	c.mu.Lock()
	c.activeConnection = &resp
	c.mu.Unlock()
	return &resp, nil
}

// Disconnect disconnects from the current pool.
func (c *Client) Disconnect(ctx context.Context) error {
	c.mu.Lock()
	conn := c.activeConnection
	c.mu.Unlock()
	if conn != nil {
		err := c.post(ctx, fmt.Sprintf("/pool/disconnect/%s", conn.ID), nil, nil)
		if err != nil {
			return err
		}
		c.mu.Lock()
		c.activeConnection = nil
		c.mu.Unlock()
	}
	return nil
}

// GetConnectionStatus gets current connection status.
func (c *Client) GetConnectionStatus(ctx context.Context) (*StratumConnection, error) {
	c.mu.RLock()
	conn := c.activeConnection
	c.mu.RUnlock()
	if conn == nil {
		return nil, nil
	}
	var resp StratumConnection
	err := c.get(ctx, fmt.Sprintf("/pool/status/%s", conn.ID), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// Reconnect reconnects to the pool.
func (c *Client) Reconnect(ctx context.Context) (*StratumConnection, error) {
	c.mu.RLock()
	conn := c.activeConnection
	c.mu.RUnlock()
	if conn == nil {
		return nil, &MiningError{Message: "No active connection to reconnect"}
	}
	var resp StratumConnection
	err := c.post(ctx, fmt.Sprintf("/pool/reconnect/%s", conn.ID), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== Mining Operations ====================

// GetBlockTemplate gets the current block template.
func (c *Client) GetBlockTemplate(ctx context.Context) (*BlockTemplate, error) {
	var resp BlockTemplate
	err := c.get(ctx, "/mining/template", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// SubmitWork submits mined work.
func (c *Client) SubmitWork(ctx context.Context, work MinedWork) (*SubmitResult, error) {
	var resp SubmitResult
	err := c.post(ctx, "/mining/submit", work, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetWork gets work from pool.
func (c *Client) GetWork(ctx context.Context) (*WorkResult, error) {
	var resp WorkResult
	err := c.get(ctx, "/mining/getwork", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// StartMining starts mining on all enabled devices.
func (c *Client) StartMining(ctx context.Context, algorithm string) (*StartMiningResult, error) {
	body := make(map[string]interface{})
	if algorithm != "" {
		body["algorithm"] = algorithm
	}
	var resp StartMiningResult
	err := c.post(ctx, "/mining/start", body, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// StopMining stops mining on all devices.
func (c *Client) StopMining(ctx context.Context) (bool, error) {
	var resp struct {
		Stopped bool `json:"stopped"`
	}
	err := c.post(ctx, "/mining/stop", nil, &resp)
	if err != nil {
		return false, err
	}
	return resp.Stopped, nil
}

// ==================== Stats ====================

// GetHashrate gets current hashrate.
func (c *Client) GetHashrate(ctx context.Context) (*Hashrate, error) {
	var resp Hashrate
	err := c.get(ctx, "/stats/hashrate", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetStats gets mining stats.
func (c *Client) GetStats(ctx context.Context) (*MiningStats, error) {
	var resp MiningStats
	err := c.get(ctx, "/stats", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetEarnings gets earnings for a time period.
func (c *Client) GetEarnings(ctx context.Context, period *TimePeriod) (*Earnings, error) {
	path := "/stats/earnings"
	if period != nil {
		path = fmt.Sprintf("%s?period=%s", path, *period)
	}
	var resp Earnings
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetEarningsHistory gets earnings history.
func (c *Client) GetEarningsHistory(ctx context.Context, limit, offset *int) ([]Earnings, 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 := "/stats/earnings/history"
	if len(params) > 0 {
		path = fmt.Sprintf("%s?%s", path, params.Encode())
	}
	var resp struct {
		Earnings []Earnings `json:"earnings"`
	}
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return resp.Earnings, nil
}

// GetPoolStats gets pool stats.
func (c *Client) GetPoolStats(ctx context.Context) (*PoolStats, error) {
	var resp PoolStats
	err := c.get(ctx, "/pool/stats", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== GPU Management ====================

// ListDevices lists all mining devices.
func (c *Client) ListDevices(ctx context.Context) ([]MiningDevice, error) {
	var resp struct {
		Devices []MiningDevice `json:"devices"`
	}
	err := c.get(ctx, "/devices", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Devices, nil
}

// GetDevice gets device details.
func (c *Client) GetDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
	var resp MiningDevice
	err := c.get(ctx, fmt.Sprintf("/devices/%s", url.PathEscape(deviceID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// SetDeviceConfig sets device configuration.
func (c *Client) SetDeviceConfig(ctx context.Context, deviceID string, config DeviceConfig) (*MiningDevice, error) {
	var resp MiningDevice
	err := c.post(ctx, fmt.Sprintf("/devices/%s/config", url.PathEscape(deviceID)), config, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// EnableDevice enables a device for mining.
func (c *Client) EnableDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
	var resp MiningDevice
	err := c.post(ctx, fmt.Sprintf("/devices/%s/enable", url.PathEscape(deviceID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// DisableDevice disables a device.
func (c *Client) DisableDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
	var resp MiningDevice
	err := c.post(ctx, fmt.Sprintf("/devices/%s/disable", url.PathEscape(deviceID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ResetDevice resets device to default settings.
func (c *Client) ResetDevice(ctx context.Context, deviceID string) (*MiningDevice, error) {
	var resp MiningDevice
	err := c.post(ctx, fmt.Sprintf("/devices/%s/reset", url.PathEscape(deviceID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== Workers ====================

// ListWorkers lists all workers.
func (c *Client) ListWorkers(ctx context.Context) ([]WorkerInfo, error) {
	var resp struct {
		Workers []WorkerInfo `json:"workers"`
	}
	err := c.get(ctx, "/workers", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Workers, nil
}

// GetWorker gets worker details.
func (c *Client) GetWorker(ctx context.Context, workerID string) (*WorkerInfo, error) {
	var resp WorkerInfo
	err := c.get(ctx, fmt.Sprintf("/workers/%s", url.PathEscape(workerID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// CreateWorker creates a new worker.
func (c *Client) CreateWorker(ctx context.Context, name string) (*WorkerInfo, error) {
	var resp WorkerInfo
	err := c.post(ctx, "/workers", map[string]string{"name": name}, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// DeleteWorker deletes a worker.
func (c *Client) DeleteWorker(ctx context.Context, workerID string) error {
	return c.delete(ctx, fmt.Sprintf("/workers/%s", url.PathEscape(workerID)))
}

// ==================== Algorithms ====================

// GetSupportedAlgorithms gets supported mining algorithms.
func (c *Client) GetSupportedAlgorithms(ctx context.Context) ([]MiningAlgorithm, error) {
	var resp struct {
		Algorithms []MiningAlgorithm `json:"algorithms"`
	}
	err := c.get(ctx, "/algorithms", &resp)
	if err != nil {
		return nil, err
	}
	return resp.Algorithms, nil
}

// GetCurrentAlgorithm gets current algorithm.
func (c *Client) GetCurrentAlgorithm(ctx context.Context) (*MiningAlgorithm, error) {
	var resp MiningAlgorithm
	err := c.get(ctx, "/algorithms/current", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// SwitchAlgorithm switches to a different algorithm.
func (c *Client) SwitchAlgorithm(ctx context.Context, algorithm string) (bool, error) {
	var resp struct {
		Switched bool `json:"switched"`
	}
	err := c.post(ctx, "/algorithms/switch", map[string]string{"algorithm": algorithm}, &resp)
	if err != nil {
		return false, err
	}
	return resp.Switched, nil
}

// ==================== 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(ctx context.Context) error {
	c.Disconnect(ctx)
	c.mu.Lock()
	defer c.mu.Unlock()
	c.closed = true
	return nil
}

// ==================== 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 &MiningError{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 &MiningError{
			Message:    msg,
			Code:       errResp.Code,
			StatusCode: resp.StatusCode,
		}
	}

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

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

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