//! Governance service for DAO voting and treasury management.
//!
//! Integrates synor-governance with the node services.

use std::sync::Arc;

use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info, warn};

use synor_governance::{
    DaoStats, GovernanceConfig, Proposal, ProposalId, ProposalState, ProposalSummary, ProposalType,
    Treasury, TreasuryPoolId, VoteChoice, VotingConfig, DAO,
};
use synor_types::Address;

use super::StorageService;

/// Governance service errors.
#[derive(Debug, thiserror::Error)]
pub enum GovernanceError {
    #[error("DAO error: {0}")]
    Dao(#[from] synor_governance::dao::DaoError),

    #[error("Treasury error: {0}")]
    Treasury(String),

    #[error("Not initialized")]
    NotInitialized,

    #[error("Storage error: {0}")]
    Storage(String),

    #[error("Invalid address: {0}")]
    InvalidAddress(String),

    #[error("Proposal not found")]
    ProposalNotFound,

    #[error("Pool not found")]
    PoolNotFound,
}

/// Governance service state.
struct GovernanceState {
    /// The DAO instance.
    dao: DAO,
    /// The treasury instance.
    treasury: Treasury,
    /// Current block height (for time-based operations).
    current_block: u64,
    /// Whether governance is initialized.
    initialized: bool,
}

/// Governance service managing DAO and treasury.
pub struct GovernanceService {
    /// Internal state.
    state: RwLock<GovernanceState>,
    /// Storage service reference.
    storage: Arc<StorageService>,
    /// Governance configuration.
    config: GovernanceConfig,
    /// Shutdown receiver.
    _shutdown_rx: broadcast::Receiver<()>,
}

impl GovernanceService {
    /// Creates a new governance service.
    pub fn new(
        storage: Arc<StorageService>,
        config: GovernanceConfig,
        shutdown_rx: broadcast::Receiver<()>,
    ) -> Self {
        let voting_config = VotingConfig {
            proposal_threshold: config.proposal_threshold,
            quorum_bps: config.quorum_bps,
            voting_period_blocks: config.voting_period_blocks,
            voting_delay_blocks: 86_400, // ~2.4 hours at 10 bps
            execution_delay_blocks: config.execution_delay_blocks,
            quadratic_voting: false,
            max_votes_per_address: 0,
        };

        let state = GovernanceState {
            dao: DAO::new(voting_config),
            treasury: Treasury::new(),
            current_block: 0,
            initialized: false,
        };

        GovernanceService {
            state: RwLock::new(state),
            storage,
            config,
            _shutdown_rx: shutdown_rx,
        }
    }

    /// Creates a service with default configuration for a network.
    pub fn for_network(
        network: &str,
        storage: Arc<StorageService>,
        shutdown_rx: broadcast::Receiver<()>,
    ) -> Self {
        let config = match network {
            "devnet" => GovernanceConfig::fast(),
            _ => GovernanceConfig::default(),
        };

        Self::new(storage, config, shutdown_rx)
    }

    /// Starts the governance service.
    pub async fn start(&self) -> anyhow::Result<()> {
        info!("Starting governance service...");

        let mut state = self.state.write().await;

        // Initialize treasury with genesis pools
        let current_time = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        state.treasury = Treasury::create_genesis_pools(current_time);

        // Load current block height from storage
        if let Some(chain_state) = self.storage.get_chain_state().await? {
            state.current_block = chain_state.daa_score;
        }

        state.initialized = true;

        info!(
            pools = state.treasury.pools().count(),
            "Governance service started"
        );

        Ok(())
    }

    /// Stops the governance service.
    pub async fn stop(&self) -> anyhow::Result<()> {
        info!("Stopping governance service...");

        // Persist any pending state to storage
        let state = self.state.read().await;
        if state.initialized {
            // In production, we'd save DAO state to storage here
            debug!("Governance state saved");
        }

        info!("Governance service stopped");
        Ok(())
    }

    /// Updates the current block height.
    pub async fn set_block_height(&self, height: u64) {
        let mut state = self.state.write().await;
        state.current_block = height;
        state.dao.update_all_states(height);
    }

    // ==================== DAO Methods ====================

    /// Creates a new proposal.
    pub async fn create_proposal(
        &self,
        proposer: Address,
        proposer_balance: u64,
        proposal_type: ProposalType,
        title: String,
        description: String,
        total_supply: u64,
    ) -> Result<ProposalId, GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let current_block = state.current_block;
        let id = state.dao.create_proposal(
            proposer.clone(),
            proposer_balance,
            proposal_type,
            title.clone(),
            description,
            current_block,
            total_supply,
        )?;

        info!(
            proposal_id = %hex::encode(id.as_bytes()),
            proposer = %proposer,
            title = %title,
            "Proposal created"
        );

        Ok(id)
    }

    /// Casts a vote on a proposal.
    pub async fn vote(
        &self,
        proposal_id: &ProposalId,
        voter: Address,
        voter_balance: u64,
        choice: VoteChoice,
        reason: Option<String>,
    ) -> Result<(), GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let current_block = state.current_block;
        state.dao.vote(
            proposal_id,
            voter.clone(),
            voter_balance,
            choice,
            current_block,
            reason,
        )?;

        info!(
            proposal_id = %hex::encode(proposal_id.as_bytes()),
            voter = %voter,
            choice = ?choice,
            "Vote cast"
        );

        Ok(())
    }

    /// Executes a passed proposal.
    pub async fn execute_proposal(
        &self,
        proposal_id: &ProposalId,
        executor: &Address,
    ) -> Result<Proposal, GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let current_block = state.current_block;
        let proposal = state
            .dao
            .execute(proposal_id, executor, current_block)?
            .clone();

        info!(
            proposal_id = %hex::encode(proposal_id.as_bytes()),
            executor = %executor,
            "Proposal executed"
        );

        // Handle proposal execution based on type
        self.handle_proposal_execution(&proposal, &mut state)
            .await?;

        Ok(proposal)
    }

    /// Handles the execution of a proposal based on its type.
    async fn handle_proposal_execution(
        &self,
        proposal: &Proposal,
        state: &mut GovernanceState,
    ) -> Result<(), GovernanceError> {
        match &proposal.proposal_type {
            ProposalType::TreasurySpend {
                recipient,
                amount,
                reason,
            } => {
                info!(
                    recipient = %recipient,
                    amount = amount,
                    reason = %reason,
                    "Executing treasury spend"
                );
                // In production, this would create a spending request
                // For now, we log the action
            }
            ProposalType::ParameterChange {
                parameter,
                old_value,
                new_value,
            } => {
                info!(
                    parameter = %parameter,
                    old_value = %old_value,
                    new_value = %new_value,
                    "Executing parameter change"
                );
                // In production, this would update chain parameters
            }
            ProposalType::CouncilChange {
                action,
                member,
                role,
            } => {
                info!(
                    action = ?action,
                    member = %member,
                    role = %role,
                    "Executing council change"
                );
                match action {
                    synor_governance::dao::CouncilAction::Add => {
                        state.dao.add_council_member(member.clone());
                    }
                    synor_governance::dao::CouncilAction::Remove => {
                        state.dao.remove_council_member(member);
                    }
                    synor_governance::dao::CouncilAction::ChangeRole => {
                        // Role changes would be tracked separately
                    }
                }
            }
            _ => {
                debug!(
                    proposal_type = ?proposal.proposal_type,
                    "Proposal type handled"
                );
            }
        }

        Ok(())
    }

    /// Cancels a proposal.
    pub async fn cancel_proposal(
        &self,
        proposal_id: &ProposalId,
        canceller: &Address,
    ) -> Result<(), GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        state.dao.cancel(proposal_id, canceller)?;

        info!(
            proposal_id = %hex::encode(proposal_id.as_bytes()),
            canceller = %canceller,
            "Proposal cancelled"
        );

        Ok(())
    }

    /// Gets a proposal by ID.
    pub async fn get_proposal(
        &self,
        proposal_id: &ProposalId,
    ) -> Result<Proposal, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        state
            .dao
            .get_proposal(proposal_id)
            .cloned()
            .ok_or(GovernanceError::ProposalNotFound)
    }

    /// Gets a proposal summary.
    pub async fn get_proposal_summary(
        &self,
        proposal_id: &ProposalId,
    ) -> Result<ProposalSummary, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let proposal = state
            .dao
            .get_proposal(proposal_id)
            .ok_or(GovernanceError::ProposalNotFound)?;

        Ok(proposal.summary(state.current_block, self.config.quorum_bps))
    }

    /// Gets all active proposals.
    pub async fn get_active_proposals(&self) -> Result<Vec<ProposalSummary>, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let proposals = state.dao.active_proposals(state.current_block);
        Ok(proposals
            .into_iter()
            .map(|p| p.summary(state.current_block, self.config.quorum_bps))
            .collect())
    }

    /// Gets proposals by state.
    pub async fn get_proposals_by_state(
        &self,
        proposal_state: ProposalState,
    ) -> Result<Vec<ProposalSummary>, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let proposals = state.dao.proposals_by_state(proposal_state);
        Ok(proposals
            .into_iter()
            .map(|p| p.summary(state.current_block, self.config.quorum_bps))
            .collect())
    }

    /// Gets DAO statistics.
    pub async fn get_dao_stats(&self) -> Result<DaoStats, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        Ok(state.dao.stats())
    }

    /// Sets the DAO guardian.
    pub async fn set_guardian(&self, guardian: Address) -> Result<(), GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        state.dao.set_guardian(guardian.clone());
        state.treasury.set_guardian(guardian.clone());

        info!(guardian = %guardian, "Guardian set");

        Ok(())
    }

    // ==================== Treasury Methods ====================

    /// Gets all treasury pools.
    pub async fn get_treasury_pools(&self) -> Result<Vec<TreasuryPoolInfo>, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let pools = state
            .treasury
            .pools()
            .map(|p| TreasuryPoolInfo {
                id: p.id,
                name: p.name.clone(),
                balance: p.balance,
                total_deposited: p.total_deposited,
                total_spent: p.total_spent,
                frozen: p.config.frozen,
            })
            .collect();

        Ok(pools)
    }

    /// Gets treasury pool by ID.
    pub async fn get_treasury_pool(
        &self,
        pool_id: &TreasuryPoolId,
    ) -> Result<TreasuryPoolInfo, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let pool = state
            .treasury
            .get_pool(pool_id)
            .ok_or(GovernanceError::PoolNotFound)?;

        Ok(TreasuryPoolInfo {
            id: pool.id,
            name: pool.name.clone(),
            balance: pool.balance,
            total_deposited: pool.total_deposited,
            total_spent: pool.total_spent,
            frozen: pool.config.frozen,
        })
    }

    /// Gets total treasury balance across all pools.
    pub async fn get_total_treasury_balance(&self) -> Result<u64, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        Ok(state.treasury.total_balance())
    }

    /// Emergency freeze all treasury pools.
    pub async fn emergency_freeze(&self, caller: &Address) -> Result<(), GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        state
            .treasury
            .emergency_freeze(caller)
            .map_err(|e| GovernanceError::Treasury(e.to_string()))?;

        warn!(caller = %caller, "Emergency freeze activated");

        Ok(())
    }

    /// Emergency unfreeze all treasury pools.
    pub async fn emergency_unfreeze(&self, caller: &Address) -> Result<(), GovernanceError> {
        let mut state = self.state.write().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        state
            .treasury
            .emergency_unfreeze(caller)
            .map_err(|e| GovernanceError::Treasury(e.to_string()))?;

        info!(caller = %caller, "Emergency freeze deactivated");

        Ok(())
    }

    /// Gets governance configuration.
    pub fn config(&self) -> &GovernanceConfig {
        &self.config
    }
}

/// Treasury pool information for API responses.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct TreasuryPoolInfo {
    pub id: TreasuryPoolId,
    pub name: String,
    pub balance: u64,
    pub total_deposited: u64,
    pub total_spent: u64,
    pub frozen: bool,
}

/// Governance info for API responses.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct GovernanceInfo {
    pub proposal_threshold: u64,
    pub quorum_bps: u32,
    pub voting_period_blocks: u64,
    pub execution_delay_blocks: u64,
    pub total_proposals: u64,
    pub active_proposals: u64,
    pub total_treasury_balance: u64,
}

impl GovernanceService {
    /// Gets governance overview info.
    pub async fn get_info(&self) -> Result<GovernanceInfo, GovernanceError> {
        let state = self.state.read().await;

        if !state.initialized {
            return Err(GovernanceError::NotInitialized);
        }

        let stats = state.dao.stats();

        Ok(GovernanceInfo {
            proposal_threshold: self.config.proposal_threshold,
            quorum_bps: self.config.quorum_bps,
            voting_period_blocks: self.config.voting_period_blocks,
            execution_delay_blocks: self.config.execution_delay_blocks,
            total_proposals: stats.total_proposals,
            active_proposals: stats.active_proposals,
            total_treasury_balance: state.treasury.total_balance(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use synor_types::address::AddressType;

    fn test_address(n: u8) -> Address {
        let mut bytes = [0u8; 32];
        bytes[0] = n;
        Address::from_parts(synor_types::Network::Devnet, AddressType::P2PKH, bytes)
    }

    // Note: Full tests require a mock StorageService
    // These are basic unit tests for the governance types

    #[test]
    fn test_treasury_pool_info() {
        let info = TreasuryPoolInfo {
            id: TreasuryPoolId::new([1u8; 32]),
            name: "Test Pool".to_string(),
            balance: 1_000_000,
            total_deposited: 1_000_000,
            total_spent: 0,
            frozen: false,
        };

        assert_eq!(info.name, "Test Pool");
        assert_eq!(info.balance, 1_000_000);
        assert!(!info.frozen);
    }

    #[test]
    fn test_governance_info() {
        let info = GovernanceInfo {
            proposal_threshold: 100_000,
            quorum_bps: 1000,
            voting_period_blocks: 864_000,
            execution_delay_blocks: 172_800,
            total_proposals: 5,
            active_proposals: 2,
            total_treasury_balance: 31_500_000,
        };

        assert_eq!(info.quorum_bps, 1000); // 10%
        assert_eq!(info.active_proposals, 2);
    }
}
