//! Synor Governance SDK for Rust.
//! Proposals, voting, DAOs, and vesting.

use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;

const DEFAULT_ENDPOINT: &str = "https://governance.synor.io/v1";
const DEFAULT_TIMEOUT: u64 = 30;
const DEFAULT_RETRIES: u32 = 3;

/// Proposal status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ProposalStatus {
    Draft,
    Active,
    Passed,
    Rejected,
    Executed,
    Cancelled,
    Expired,
}

/// Vote choice.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum VoteChoice {
    For,
    Against,
    Abstain,
}

/// Proposal type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProposalType {
    ParameterChange,
    TreasurySpend,
    Upgrade,
    Text,
    Custom,
}

/// DAO type.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DaoType {
    Token,
    Multisig,
    Hybrid,
}

/// Vesting status.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum VestingStatus {
    Active,
    Paused,
    Completed,
    Cancelled,
}

/// Governance SDK configuration.
#[derive(Debug, Clone)]
pub struct GovernanceConfig {
    pub api_key: String,
    pub endpoint: String,
    pub timeout: Duration,
    pub retries: u32,
    pub debug: bool,
}

impl GovernanceConfig {
    pub fn new(api_key: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            endpoint: DEFAULT_ENDPOINT.to_string(),
            timeout: Duration::from_secs(DEFAULT_TIMEOUT),
            retries: DEFAULT_RETRIES,
            debug: false,
        }
    }

    pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
        self.endpoint = endpoint.into();
        self
    }
}

/// Proposal action.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProposalAction {
    pub target: String,
    pub value: String,
    pub calldata: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

/// Proposal draft for creating proposals.
#[derive(Debug, Clone, Serialize)]
pub struct ProposalDraft {
    pub title: String,
    pub description: String,
    #[serde(rename = "type")]
    pub proposal_type: ProposalType,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub actions: Vec<ProposalAction>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub quorum: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub threshold: Option<String>,
}

/// Proposal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proposal {
    pub id: String,
    pub title: String,
    pub description: String,
    #[serde(rename = "type")]
    pub proposal_type: ProposalType,
    pub status: ProposalStatus,
    pub proposer: String,
    pub actions: Vec<ProposalAction>,
    pub start_time: i64,
    pub end_time: i64,
    pub quorum: String,
    pub threshold: String,
    pub for_votes: String,
    pub against_votes: String,
    pub abstain_votes: String,
    pub total_votes: String,
    pub created_at: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub executed_at: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub execution_tx_hash: Option<String>,
}

/// Proposal filter.
#[derive(Debug, Clone, Default)]
pub struct ProposalFilter {
    pub status: Option<ProposalStatus>,
    pub proposal_type: Option<ProposalType>,
    pub proposer: Option<String>,
    pub limit: Option<i32>,
    pub offset: Option<i32>,
}

/// Vote.
#[derive(Debug, Clone, Serialize)]
pub struct Vote {
    pub choice: VoteChoice,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
}

/// Vote receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoteReceipt {
    pub proposal_id: String,
    pub voter: String,
    pub choice: VoteChoice,
    pub weight: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    pub timestamp: i64,
    pub tx_hash: String,
}

/// Delegation info.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationInfo {
    pub address: String,
    pub amount: String,
    pub timestamp: i64,
}

/// Delegation receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationReceipt {
    pub delegator: String,
    pub delegatee: String,
    pub amount: String,
    pub timestamp: i64,
    pub tx_hash: String,
}

/// Voting power.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VotingPower {
    pub address: String,
    pub own_power: String,
    pub delegated_power: String,
    pub total_power: String,
    pub delegated_from: Vec<DelegationInfo>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub delegated_to: Option<DelegationInfo>,
}

/// DAO config for creating DAOs.
#[derive(Debug, Clone, Serialize)]
pub struct DaoConfig {
    pub name: String,
    #[serde(rename = "type")]
    pub dao_type: DaoType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub token_address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signers: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub threshold: Option<i32>,
    pub voting_period: i32,
    pub quorum: String,
    pub proposal_threshold: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timelock_delay: Option<i32>,
}

/// DAO.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dao {
    pub id: String,
    pub name: String,
    #[serde(rename = "type")]
    pub dao_type: DaoType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub token_address: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signers: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub threshold: Option<i32>,
    pub voting_period: i32,
    pub quorum: String,
    pub proposal_threshold: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timelock_delay: Option<i32>,
    pub treasury: String,
    pub total_proposals: i32,
    pub active_proposals: i32,
    pub total_members: i32,
    pub created_at: i64,
}

/// Vesting schedule for creating vesting contracts.
#[derive(Debug, Clone, Serialize)]
pub struct VestingSchedule {
    pub beneficiary: String,
    pub total_amount: String,
    pub start_time: i64,
    pub cliff_duration: i32,
    pub vesting_duration: i32,
    #[serde(default)]
    pub revocable: bool,
}

/// Vesting contract.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VestingContract {
    pub id: String,
    pub beneficiary: String,
    pub total_amount: String,
    pub released_amount: String,
    pub vested_amount: String,
    pub start_time: i64,
    pub cliff_time: i64,
    pub end_time: i64,
    pub revocable: bool,
    pub status: VestingStatus,
    pub created_at: i64,
    pub tx_hash: String,
}

/// Claim result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimResult {
    pub vesting_id: String,
    pub amount: String,
    pub recipient: String,
    pub timestamp: i64,
    pub tx_hash: String,
}

/// Governance transaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GovernanceTransaction {
    pub tx_hash: String,
    pub timestamp: i64,
    pub block_number: i64,
    pub status: String,
}

/// DAO member.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DaoMember {
    pub address: String,
    pub power: String,
}

/// Claimable amount.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimableAmount {
    pub claimable: String,
    pub vested: String,
}

/// Governance SDK error.
#[derive(Error, Debug)]
pub enum GovernanceError {
    #[error("HTTP error: {message}")]
    Http {
        message: String,
        code: Option<String>,
        status_code: u16,
    },
    #[error("Client closed")]
    ClientClosed,
    #[error("Request error: {0}")]
    Request(#[from] reqwest::Error),
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),
}

/// Synor Governance SDK client.
pub struct SynorGovernance {
    config: GovernanceConfig,
    client: Client,
    closed: Arc<AtomicBool>,
}

impl SynorGovernance {
    /// Create a new Governance client.
    pub fn new(config: GovernanceConfig) -> Result<Self, GovernanceError> {
        let client = Client::builder().timeout(config.timeout).build()?;

        Ok(Self {
            config,
            client,
            closed: Arc::new(AtomicBool::new(false)),
        })
    }

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

    /// Create a new proposal.
    pub async fn create_proposal(&self, draft: ProposalDraft) -> Result<Proposal, GovernanceError> {
        self.post("/proposals", &draft).await
    }

    /// Get a proposal by ID.
    pub async fn get_proposal(&self, proposal_id: &str) -> Result<Proposal, GovernanceError> {
        self.get(&format!("/proposals/{}", urlencoding::encode(proposal_id)))
            .await
    }

    /// List proposals with optional filtering.
    pub async fn list_proposals(
        &self,
        filter: Option<ProposalFilter>,
    ) -> Result<Vec<Proposal>, GovernanceError> {
        let mut params = Vec::new();
        if let Some(f) = filter {
            if let Some(s) = f.status {
                params.push(format!("status={:?}", s).to_lowercase());
            }
            if let Some(t) = f.proposal_type {
                params.push(format!("type={:?}", t).to_lowercase());
            }
            if let Some(p) = f.proposer {
                params.push(format!("proposer={}", p));
            }
            if let Some(l) = f.limit {
                params.push(format!("limit={}", l));
            }
            if let Some(o) = f.offset {
                params.push(format!("offset={}", o));
            }
        }
        let path = if params.is_empty() {
            "/proposals".to_string()
        } else {
            format!("/proposals?{}", params.join("&"))
        };
        #[derive(Deserialize)]
        struct Response {
            proposals: Vec<Proposal>,
        }
        let resp: Response = self.get(&path).await?;
        Ok(resp.proposals)
    }

    /// Cancel a proposal.
    pub async fn cancel_proposal(&self, proposal_id: &str) -> Result<Proposal, GovernanceError> {
        self.post::<(), Proposal>(
            &format!("/proposals/{}/cancel", urlencoding::encode(proposal_id)),
            &(),
        )
        .await
    }

    /// Execute a passed proposal.
    pub async fn execute_proposal(
        &self,
        proposal_id: &str,
    ) -> Result<GovernanceTransaction, GovernanceError> {
        self.post::<(), GovernanceTransaction>(
            &format!("/proposals/{}/execute", urlencoding::encode(proposal_id)),
            &(),
        )
        .await
    }

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

    /// Vote on a proposal.
    pub async fn vote(
        &self,
        proposal_id: &str,
        vote: Vote,
        weight: Option<&str>,
    ) -> Result<VoteReceipt, GovernanceError> {
        #[derive(Serialize)]
        struct VoteRequest {
            choice: VoteChoice,
            #[serde(skip_serializing_if = "Option::is_none")]
            reason: Option<String>,
            #[serde(skip_serializing_if = "Option::is_none")]
            weight: Option<String>,
        }
        let req = VoteRequest {
            choice: vote.choice,
            reason: vote.reason,
            weight: weight.map(String::from),
        };
        self.post(
            &format!("/proposals/{}/vote", urlencoding::encode(proposal_id)),
            &req,
        )
        .await
    }

    /// Get votes for a proposal.
    pub async fn get_votes(
        &self,
        proposal_id: &str,
        limit: Option<i32>,
        offset: Option<i32>,
    ) -> Result<Vec<VoteReceipt>, GovernanceError> {
        let mut params = Vec::new();
        if let Some(l) = limit {
            params.push(format!("limit={}", l));
        }
        if let Some(o) = offset {
            params.push(format!("offset={}", o));
        }
        let path = if params.is_empty() {
            format!("/proposals/{}/votes", urlencoding::encode(proposal_id))
        } else {
            format!(
                "/proposals/{}/votes?{}",
                urlencoding::encode(proposal_id),
                params.join("&")
            )
        };
        #[derive(Deserialize)]
        struct Response {
            votes: Vec<VoteReceipt>,
        }
        let resp: Response = self.get(&path).await?;
        Ok(resp.votes)
    }

    /// Delegate voting power.
    pub async fn delegate(
        &self,
        to: &str,
        amount: Option<&str>,
    ) -> Result<DelegationReceipt, GovernanceError> {
        #[derive(Serialize)]
        struct Request {
            to: String,
            #[serde(skip_serializing_if = "Option::is_none")]
            amount: Option<String>,
        }
        self.post(
            "/voting/delegate",
            &Request {
                to: to.to_string(),
                amount: amount.map(String::from),
            },
        )
        .await
    }

    /// Undelegate voting power.
    pub async fn undelegate(
        &self,
        from: Option<&str>,
    ) -> Result<DelegationReceipt, GovernanceError> {
        #[derive(Serialize)]
        struct Request {
            #[serde(skip_serializing_if = "Option::is_none")]
            from: Option<String>,
        }
        self.post(
            "/voting/undelegate",
            &Request {
                from: from.map(String::from),
            },
        )
        .await
    }

    /// Get voting power for an address.
    pub async fn get_voting_power(&self, address: &str) -> Result<VotingPower, GovernanceError> {
        self.get(&format!("/voting/power/{}", urlencoding::encode(address)))
            .await
    }

    /// Get voting power for the authenticated user.
    pub async fn get_my_voting_power(&self) -> Result<VotingPower, GovernanceError> {
        self.get("/voting/power").await
    }

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

    /// Create a new DAO.
    pub async fn create_dao(&self, config: DaoConfig) -> Result<Dao, GovernanceError> {
        self.post("/daos", &config).await
    }

    /// Get a DAO by ID.
    pub async fn get_dao(&self, dao_id: &str) -> Result<Dao, GovernanceError> {
        self.get(&format!("/daos/{}", urlencoding::encode(dao_id)))
            .await
    }

    /// List DAOs.
    pub async fn list_daos(
        &self,
        limit: Option<i32>,
        offset: Option<i32>,
    ) -> Result<Vec<Dao>, GovernanceError> {
        let mut params = Vec::new();
        if let Some(l) = limit {
            params.push(format!("limit={}", l));
        }
        if let Some(o) = offset {
            params.push(format!("offset={}", o));
        }
        let path = if params.is_empty() {
            "/daos".to_string()
        } else {
            format!("/daos?{}", params.join("&"))
        };
        #[derive(Deserialize)]
        struct Response {
            daos: Vec<Dao>,
        }
        let resp: Response = self.get(&path).await?;
        Ok(resp.daos)
    }

    /// Get DAO members.
    pub async fn get_dao_members(
        &self,
        dao_id: &str,
        limit: Option<i32>,
        offset: Option<i32>,
    ) -> Result<Vec<DaoMember>, GovernanceError> {
        let mut params = Vec::new();
        if let Some(l) = limit {
            params.push(format!("limit={}", l));
        }
        if let Some(o) = offset {
            params.push(format!("offset={}", o));
        }
        let path = if params.is_empty() {
            format!("/daos/{}/members", urlencoding::encode(dao_id))
        } else {
            format!(
                "/daos/{}/members?{}",
                urlencoding::encode(dao_id),
                params.join("&")
            )
        };
        #[derive(Deserialize)]
        struct Response {
            members: Vec<DaoMember>,
        }
        let resp: Response = self.get(&path).await?;
        Ok(resp.members)
    }

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

    /// Create a vesting schedule.
    pub async fn create_vesting_schedule(
        &self,
        schedule: VestingSchedule,
    ) -> Result<VestingContract, GovernanceError> {
        self.post("/vesting", &schedule).await
    }

    /// Get a vesting contract.
    pub async fn get_vesting_contract(
        &self,
        contract_id: &str,
    ) -> Result<VestingContract, GovernanceError> {
        self.get(&format!("/vesting/{}", urlencoding::encode(contract_id)))
            .await
    }

    /// List vesting contracts.
    pub async fn list_vesting_contracts(
        &self,
        beneficiary: Option<&str>,
    ) -> Result<Vec<VestingContract>, GovernanceError> {
        let path = match beneficiary {
            Some(b) => format!("/vesting?beneficiary={}", urlencoding::encode(b)),
            None => "/vesting".to_string(),
        };
        #[derive(Deserialize)]
        struct Response {
            contracts: Vec<VestingContract>,
        }
        let resp: Response = self.get(&path).await?;
        Ok(resp.contracts)
    }

    /// Claim vested tokens.
    pub async fn claim_vested(&self, contract_id: &str) -> Result<ClaimResult, GovernanceError> {
        self.post::<(), ClaimResult>(
            &format!("/vesting/{}/claim", urlencoding::encode(contract_id)),
            &(),
        )
        .await
    }

    /// Revoke a vesting contract.
    pub async fn revoke_vesting(
        &self,
        contract_id: &str,
    ) -> Result<VestingContract, GovernanceError> {
        self.post::<(), VestingContract>(
            &format!("/vesting/{}/revoke", urlencoding::encode(contract_id)),
            &(),
        )
        .await
    }

    /// Get claimable amount for a vesting contract.
    pub async fn get_claimable_amount(
        &self,
        contract_id: &str,
    ) -> Result<ClaimableAmount, GovernanceError> {
        self.get(&format!(
            "/vesting/{}/claimable",
            urlencoding::encode(contract_id)
        ))
        .await
    }

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

    /// Health check.
    pub async fn health_check(&self) -> bool {
        #[derive(Deserialize)]
        struct Response {
            status: String,
        }
        match self.get::<Response>("/health").await {
            Ok(resp) => resp.status == "healthy",
            Err(_) => false,
        }
    }

    /// Check if client is closed.
    pub fn is_closed(&self) -> bool {
        self.closed.load(Ordering::SeqCst)
    }

    /// Close the client.
    pub fn close(&self) {
        self.closed.store(true, Ordering::SeqCst);
    }

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

    async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, GovernanceError> {
        self.execute(|| async {
            let resp = self
                .client
                .get(format!("{}{}", self.config.endpoint, path))
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "rust/0.1.0")
                .send()
                .await?;
            self.handle_response(resp).await
        })
        .await
    }

    async fn post<B: Serialize, T: for<'de> Deserialize<'de>>(
        &self,
        path: &str,
        body: &B,
    ) -> Result<T, GovernanceError> {
        self.execute(|| async {
            let resp = self
                .client
                .post(format!("{}{}", self.config.endpoint, path))
                .header("Authorization", format!("Bearer {}", self.config.api_key))
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "rust/0.1.0")
                .json(body)
                .send()
                .await?;
            self.handle_response(resp).await
        })
        .await
    }

    async fn execute<F, Fut, T>(&self, operation: F) -> Result<T, GovernanceError>
    where
        F: Fn() -> Fut,
        Fut: std::future::Future<Output = Result<T, GovernanceError>>,
    {
        if self.closed.load(Ordering::SeqCst) {
            return Err(GovernanceError::ClientClosed);
        }

        let mut last_error = None;
        for attempt in 0..self.config.retries {
            match operation().await {
                Ok(result) => return Ok(result),
                Err(e) => {
                    if self.config.debug {
                        eprintln!("Attempt {} failed: {:?}", attempt + 1, e);
                    }
                    last_error = Some(e);
                    if attempt < self.config.retries - 1 {
                        tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
                    }
                }
            }
        }
        Err(last_error.unwrap())
    }

    async fn handle_response<T: for<'de> Deserialize<'de>>(
        &self,
        resp: reqwest::Response,
    ) -> Result<T, GovernanceError> {
        if resp.status().is_success() {
            Ok(resp.json().await?)
        } else {
            Err(self.parse_error(resp).await)
        }
    }

    async fn parse_error(&self, resp: reqwest::Response) -> GovernanceError {
        let status = resp.status().as_u16();
        let body = resp.text().await.unwrap_or_default();

        #[derive(Deserialize)]
        struct ErrorResponse {
            message: Option<String>,
            code: Option<String>,
        }

        let (message, code) = match serde_json::from_str::<ErrorResponse>(&body) {
            Ok(err) => (
                err.message.unwrap_or_else(|| format!("HTTP {}", status)),
                err.code,
            ),
            Err(_) => (format!("HTTP {}", status), None),
        };

        GovernanceError::Http {
            message,
            code,
            status_code: status,
        }
    }
}
