// Package governance provides the Synor Governance SDK for Go.
// Proposals, voting, DAOs, and vesting.
package governance

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

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

// ProposalStatus represents proposal status.
type ProposalStatus string

const (
	ProposalDraft     ProposalStatus = "draft"
	ProposalActive    ProposalStatus = "active"
	ProposalPassed    ProposalStatus = "passed"
	ProposalRejected  ProposalStatus = "rejected"
	ProposalExecuted  ProposalStatus = "executed"
	ProposalCancelled ProposalStatus = "cancelled"
	ProposalExpired   ProposalStatus = "expired"
)

// VoteChoice represents vote choice.
type VoteChoice string

const (
	VoteFor     VoteChoice = "for"
	VoteAgainst VoteChoice = "against"
	VoteAbstain VoteChoice = "abstain"
)

// ProposalType represents proposal type.
type ProposalType string

const (
	ProposalParameterChange ProposalType = "parameter_change"
	ProposalTreasurySpend   ProposalType = "treasury_spend"
	ProposalUpgrade         ProposalType = "upgrade"
	ProposalText            ProposalType = "text"
	ProposalCustom          ProposalType = "custom"
)

// DaoType represents DAO type.
type DaoType string

const (
	DaoToken    DaoType = "token"
	DaoMultisig DaoType = "multisig"
	DaoHybrid   DaoType = "hybrid"
)

// VestingStatus represents vesting status.
type VestingStatus string

const (
	VestingActive    VestingStatus = "active"
	VestingPaused    VestingStatus = "paused"
	VestingCompleted VestingStatus = "completed"
	VestingCancelled VestingStatus = "cancelled"
)

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

// ProposalAction represents a proposal action.
type ProposalAction struct {
	Target      string `json:"target"`
	Value       string `json:"value"`
	Calldata    string `json:"calldata"`
	Description string `json:"description,omitempty"`
}

// ProposalDraft for creating proposals.
type ProposalDraft struct {
	Title       string           `json:"title"`
	Description string           `json:"description"`
	Type        ProposalType     `json:"type"`
	Actions     []ProposalAction `json:"actions,omitempty"`
	StartTime   *int64           `json:"start_time,omitempty"`
	EndTime     *int64           `json:"end_time,omitempty"`
	Quorum      string           `json:"quorum,omitempty"`
	Threshold   string           `json:"threshold,omitempty"`
}

// Proposal represents a governance proposal.
type Proposal struct {
	ID              string           `json:"id"`
	Title           string           `json:"title"`
	Description     string           `json:"description"`
	Type            ProposalType     `json:"type"`
	Status          ProposalStatus   `json:"status"`
	Proposer        string           `json:"proposer"`
	Actions         []ProposalAction `json:"actions"`
	StartTime       int64            `json:"start_time"`
	EndTime         int64            `json:"end_time"`
	Quorum          string           `json:"quorum"`
	Threshold       string           `json:"threshold"`
	ForVotes        string           `json:"for_votes"`
	AgainstVotes    string           `json:"against_votes"`
	AbstainVotes    string           `json:"abstain_votes"`
	TotalVotes      string           `json:"total_votes"`
	CreatedAt       int64            `json:"created_at"`
	ExecutedAt      *int64           `json:"executed_at,omitempty"`
	ExecutionTxHash string           `json:"execution_tx_hash,omitempty"`
}

// ProposalFilter for filtering proposals.
type ProposalFilter struct {
	Status   *ProposalStatus
	Type     *ProposalType
	Proposer string
	Limit    *int
	Offset   *int
}

// Vote represents a vote.
type Vote struct {
	Choice VoteChoice `json:"choice"`
	Reason string     `json:"reason,omitempty"`
}

// VoteReceipt represents a vote receipt.
type VoteReceipt struct {
	ProposalID string     `json:"proposal_id"`
	Voter      string     `json:"voter"`
	Choice     VoteChoice `json:"choice"`
	Weight     string     `json:"weight"`
	Reason     string     `json:"reason,omitempty"`
	Timestamp  int64      `json:"timestamp"`
	TxHash     string     `json:"tx_hash"`
}

// DelegationInfo represents delegation information.
type DelegationInfo struct {
	Address   string `json:"address"`
	Amount    string `json:"amount"`
	Timestamp int64  `json:"timestamp"`
}

// DelegationReceipt represents a delegation receipt.
type DelegationReceipt struct {
	Delegator string `json:"delegator"`
	Delegatee string `json:"delegatee"`
	Amount    string `json:"amount"`
	Timestamp int64  `json:"timestamp"`
	TxHash    string `json:"tx_hash"`
}

// VotingPower represents voting power.
type VotingPower struct {
	Address        string           `json:"address"`
	OwnPower       string           `json:"own_power"`
	DelegatedPower string           `json:"delegated_power"`
	TotalPower     string           `json:"total_power"`
	DelegatedFrom  []DelegationInfo `json:"delegated_from"`
	DelegatedTo    *DelegationInfo  `json:"delegated_to,omitempty"`
}

// DaoConfig for creating DAOs.
type DaoConfig struct {
	Name              string   `json:"name"`
	Type              DaoType  `json:"type"`
	TokenAddress      string   `json:"token_address,omitempty"`
	Signers           []string `json:"signers,omitempty"`
	Threshold         *int     `json:"threshold,omitempty"`
	VotingPeriod      int      `json:"voting_period"`
	Quorum            string   `json:"quorum"`
	ProposalThreshold string   `json:"proposal_threshold"`
	TimelockDelay     *int     `json:"timelock_delay,omitempty"`
}

// Dao represents a DAO.
type Dao struct {
	ID                string   `json:"id"`
	Name              string   `json:"name"`
	Type              DaoType  `json:"type"`
	TokenAddress      string   `json:"token_address,omitempty"`
	Signers           []string `json:"signers,omitempty"`
	Threshold         *int     `json:"threshold,omitempty"`
	VotingPeriod      int      `json:"voting_period"`
	Quorum            string   `json:"quorum"`
	ProposalThreshold string   `json:"proposal_threshold"`
	TimelockDelay     *int     `json:"timelock_delay,omitempty"`
	Treasury          string   `json:"treasury"`
	TotalProposals    int      `json:"total_proposals"`
	ActiveProposals   int      `json:"active_proposals"`
	TotalMembers      int      `json:"total_members"`
	CreatedAt         int64    `json:"created_at"`
}

// VestingSchedule for creating vesting contracts.
type VestingSchedule struct {
	Beneficiary     string `json:"beneficiary"`
	TotalAmount     string `json:"total_amount"`
	StartTime       int64  `json:"start_time"`
	CliffDuration   int    `json:"cliff_duration"`
	VestingDuration int    `json:"vesting_duration"`
	Revocable       bool   `json:"revocable"`
}

// VestingContract represents a vesting contract.
type VestingContract struct {
	ID             string        `json:"id"`
	Beneficiary    string        `json:"beneficiary"`
	TotalAmount    string        `json:"total_amount"`
	ReleasedAmount string        `json:"released_amount"`
	VestedAmount   string        `json:"vested_amount"`
	StartTime      int64         `json:"start_time"`
	CliffTime      int64         `json:"cliff_time"`
	EndTime        int64         `json:"end_time"`
	Revocable      bool          `json:"revocable"`
	Status         VestingStatus `json:"status"`
	CreatedAt      int64         `json:"created_at"`
	TxHash         string        `json:"tx_hash"`
}

// ClaimResult represents a claim result.
type ClaimResult struct {
	VestingID string `json:"vesting_id"`
	Amount    string `json:"amount"`
	Recipient string `json:"recipient"`
	Timestamp int64  `json:"timestamp"`
	TxHash    string `json:"tx_hash"`
}

// GovernanceTransaction represents a transaction result.
type GovernanceTransaction struct {
	TxHash      string `json:"tx_hash"`
	Timestamp   int64  `json:"timestamp"`
	BlockNumber int64  `json:"block_number"`
	Status      string `json:"status"`
}

// DaoMember represents a DAO member.
type DaoMember struct {
	Address string `json:"address"`
	Power   string `json:"power"`
}

// ClaimableAmount represents claimable amount.
type ClaimableAmount struct {
	Claimable string `json:"claimable"`
	Vested    string `json:"vested"`
}

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

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

// ==================== Proposal Operations ====================

// CreateProposal creates a new proposal.
func (c *Client) CreateProposal(ctx context.Context, draft ProposalDraft) (*Proposal, error) {
	var resp Proposal
	err := c.post(ctx, "/proposals", draft, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetProposal gets a proposal by ID.
func (c *Client) GetProposal(ctx context.Context, proposalID string) (*Proposal, error) {
	var resp Proposal
	err := c.get(ctx, fmt.Sprintf("/proposals/%s", url.PathEscape(proposalID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ListProposals lists proposals with optional filtering.
func (c *Client) ListProposals(ctx context.Context, filter *ProposalFilter) ([]Proposal, error) {
	params := url.Values{}
	if filter != nil {
		if filter.Status != nil {
			params.Set("status", string(*filter.Status))
		}
		if filter.Type != nil {
			params.Set("type", string(*filter.Type))
		}
		if filter.Proposer != "" {
			params.Set("proposer", filter.Proposer)
		}
		if filter.Limit != nil {
			params.Set("limit", fmt.Sprintf("%d", *filter.Limit))
		}
		if filter.Offset != nil {
			params.Set("offset", fmt.Sprintf("%d", *filter.Offset))
		}
	}
	path := "/proposals"
	if len(params) > 0 {
		path = fmt.Sprintf("%s?%s", path, params.Encode())
	}
	var resp struct {
		Proposals []Proposal `json:"proposals"`
	}
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return resp.Proposals, nil
}

// CancelProposal cancels a proposal.
func (c *Client) CancelProposal(ctx context.Context, proposalID string) (*Proposal, error) {
	var resp Proposal
	err := c.post(ctx, fmt.Sprintf("/proposals/%s/cancel", url.PathEscape(proposalID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ExecuteProposal executes a passed proposal.
func (c *Client) ExecuteProposal(ctx context.Context, proposalID string) (*GovernanceTransaction, error) {
	var resp GovernanceTransaction
	err := c.post(ctx, fmt.Sprintf("/proposals/%s/execute", url.PathEscape(proposalID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== Voting Operations ====================

// Vote votes on a proposal.
func (c *Client) Vote(ctx context.Context, proposalID string, vote Vote, weight string) (*VoteReceipt, error) {
	body := map[string]interface{}{
		"choice": vote.Choice,
	}
	if vote.Reason != "" {
		body["reason"] = vote.Reason
	}
	if weight != "" {
		body["weight"] = weight
	}
	var resp VoteReceipt
	err := c.post(ctx, fmt.Sprintf("/proposals/%s/vote", url.PathEscape(proposalID)), body, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetVotes gets votes for a proposal.
func (c *Client) GetVotes(ctx context.Context, proposalID string, limit, offset *int) ([]VoteReceipt, 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 := fmt.Sprintf("/proposals/%s/votes", url.PathEscape(proposalID))
	if len(params) > 0 {
		path = fmt.Sprintf("%s?%s", path, params.Encode())
	}
	var resp struct {
		Votes []VoteReceipt `json:"votes"`
	}
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return resp.Votes, nil
}

// Delegate delegates voting power.
func (c *Client) Delegate(ctx context.Context, to string, amount string) (*DelegationReceipt, error) {
	body := map[string]interface{}{"to": to}
	if amount != "" {
		body["amount"] = amount
	}
	var resp DelegationReceipt
	err := c.post(ctx, "/voting/delegate", body, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// Undelegate undelegates voting power.
func (c *Client) Undelegate(ctx context.Context, from string) (*DelegationReceipt, error) {
	body := make(map[string]interface{})
	if from != "" {
		body["from"] = from
	}
	var resp DelegationReceipt
	err := c.post(ctx, "/voting/undelegate", body, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetVotingPower gets voting power for an address.
func (c *Client) GetVotingPower(ctx context.Context, address string) (*VotingPower, error) {
	var resp VotingPower
	err := c.get(ctx, fmt.Sprintf("/voting/power/%s", url.PathEscape(address)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetMyVotingPower gets voting power for the authenticated user.
func (c *Client) GetMyVotingPower(ctx context.Context) (*VotingPower, error) {
	var resp VotingPower
	err := c.get(ctx, "/voting/power", &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ==================== DAO Operations ====================

// CreateDao creates a new DAO.
func (c *Client) CreateDao(ctx context.Context, config DaoConfig) (*Dao, error) {
	var resp Dao
	err := c.post(ctx, "/daos", config, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetDao gets a DAO by ID.
func (c *Client) GetDao(ctx context.Context, daoID string) (*Dao, error) {
	var resp Dao
	err := c.get(ctx, fmt.Sprintf("/daos/%s", url.PathEscape(daoID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

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

// GetDaoMembers gets DAO members.
func (c *Client) GetDaoMembers(ctx context.Context, daoID string, limit, offset *int) ([]DaoMember, 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 := fmt.Sprintf("/daos/%s/members", url.PathEscape(daoID))
	if len(params) > 0 {
		path = fmt.Sprintf("%s?%s", path, params.Encode())
	}
	var resp struct {
		Members []DaoMember `json:"members"`
	}
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return resp.Members, nil
}

// ==================== Vesting Operations ====================

// CreateVestingSchedule creates a vesting schedule.
func (c *Client) CreateVestingSchedule(ctx context.Context, schedule VestingSchedule) (*VestingContract, error) {
	var resp VestingContract
	err := c.post(ctx, "/vesting", schedule, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetVestingContract gets a vesting contract.
func (c *Client) GetVestingContract(ctx context.Context, contractID string) (*VestingContract, error) {
	var resp VestingContract
	err := c.get(ctx, fmt.Sprintf("/vesting/%s", url.PathEscape(contractID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// ListVestingContracts lists vesting contracts.
func (c *Client) ListVestingContracts(ctx context.Context, beneficiary string) ([]VestingContract, error) {
	path := "/vesting"
	if beneficiary != "" {
		path = fmt.Sprintf("%s?beneficiary=%s", path, url.QueryEscape(beneficiary))
	}
	var resp struct {
		Contracts []VestingContract `json:"contracts"`
	}
	err := c.get(ctx, path, &resp)
	if err != nil {
		return nil, err
	}
	return resp.Contracts, nil
}

// ClaimVested claims vested tokens.
func (c *Client) ClaimVested(ctx context.Context, contractID string) (*ClaimResult, error) {
	var resp ClaimResult
	err := c.post(ctx, fmt.Sprintf("/vesting/%s/claim", url.PathEscape(contractID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// RevokeVesting revokes a vesting contract.
func (c *Client) RevokeVesting(ctx context.Context, contractID string) (*VestingContract, error) {
	var resp VestingContract
	err := c.post(ctx, fmt.Sprintf("/vesting/%s/revoke", url.PathEscape(contractID)), nil, &resp)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

// GetClaimableAmount gets claimable amount for a vesting contract.
func (c *Client) GetClaimableAmount(ctx context.Context, contractID string) (*ClaimableAmount, error) {
	var resp ClaimableAmount
	err := c.get(ctx, fmt.Sprintf("/vesting/%s/claimable", url.PathEscape(contractID)), &resp)
	if err != nil {
		return nil, err
	}
	return &resp, 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() {
	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) execute(ctx context.Context, method, path string, body, result interface{}) error {
	c.mu.RLock()
	if c.closed {
		c.mu.RUnlock()
		return &GovernanceError{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 &GovernanceError{
			Message:    msg,
			Code:       errResp.Code,
			StatusCode: resp.StatusCode,
		}
	}

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

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

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