//! Governance commands for DAO voting and treasury management.

use anyhow::Result;
use serde_json::json;

use crate::client::RpcClient;
use crate::output::{self, OutputFormat};
use crate::GovernanceCommands;

/// Handle governance commands.
pub async fn handle(
    client: &RpcClient,
    cmd: GovernanceCommands,
    format: OutputFormat,
) -> Result<()> {
    match cmd {
        GovernanceCommands::Info => info(client, format).await,
        GovernanceCommands::Stats => stats(client, format).await,

        // Proposal commands
        GovernanceCommands::Proposals { state } => {
            proposals(client, state.as_deref(), format).await
        }
        GovernanceCommands::Proposal { id } => proposal(client, &id, format).await,
        GovernanceCommands::CreateProposal {
            proposer,
            proposal_type,
            title,
            description,
            recipient,
            amount,
            parameter,
            old_value,
            new_value,
        } => {
            create_proposal(
                client,
                &proposer,
                &proposal_type,
                &title,
                &description,
                recipient.as_deref(),
                amount,
                parameter.as_deref(),
                old_value.as_deref(),
                new_value.as_deref(),
                format,
            )
            .await
        }
        GovernanceCommands::Vote {
            proposal_id,
            voter,
            choice,
            reason,
        } => {
            vote(
                client,
                &proposal_id,
                &voter,
                &choice,
                reason.as_deref(),
                format,
            )
            .await
        }
        GovernanceCommands::Execute {
            proposal_id,
            executor,
        } => execute(client, &proposal_id, &executor, format).await,

        // Treasury commands
        GovernanceCommands::Treasury => treasury(client, format).await,
        GovernanceCommands::TreasuryPool { id } => treasury_pool(client, &id, format).await,
    }
}

/// Get governance info.
async fn info(client: &RpcClient, format: OutputFormat) -> Result<()> {
    let info = client.get_governance_info().await?;

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&info)?);
        }
        OutputFormat::Text => {
            output::print_header("Governance Info");
            output::print_kv("Proposal Threshold", &format_synor(info.proposal_threshold));
            output::print_kv("Quorum", &format!("{}%", info.quorum_bps as f64 / 100.0));
            output::print_kv("Voting Period", &format_blocks(info.voting_period_blocks));
            output::print_kv(
                "Execution Delay",
                &format_blocks(info.execution_delay_blocks),
            );
            println!();
            output::print_kv("Total Proposals", &info.total_proposals.to_string());
            output::print_kv("Active Proposals", &info.active_proposals.to_string());
            output::print_kv(
                "Treasury Balance",
                &format_synor(info.total_treasury_balance),
            );
        }
    }

    Ok(())
}

/// Get DAO statistics.
async fn stats(client: &RpcClient, format: OutputFormat) -> Result<()> {
    let stats = client.get_dao_stats().await?;

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&stats)?);
        }
        OutputFormat::Text => {
            output::print_header("DAO Statistics");
            output::print_kv("Total Proposals", &stats.total_proposals.to_string());
            output::print_kv("Active Proposals", &stats.active_proposals.to_string());
            output::print_kv("Passed Proposals", &stats.passed_proposals.to_string());
            output::print_kv("Defeated Proposals", &stats.defeated_proposals.to_string());
            output::print_kv("Executed Proposals", &stats.executed_proposals.to_string());
            output::print_kv("Total Votes Cast", &stats.total_votes_cast.to_string());
            output::print_kv("Council Members", &stats.council_members.to_string());
        }
    }

    Ok(())
}

/// List proposals.
async fn proposals(client: &RpcClient, state: Option<&str>, format: OutputFormat) -> Result<()> {
    let proposals = match state {
        Some(s) => client.get_proposals_by_state(s).await?,
        None => client.get_active_proposals().await?,
    };

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&proposals)?);
        }
        OutputFormat::Text => {
            let title = match state {
                Some(s) => format!("{} Proposals", capitalize(s)),
                None => "Active Proposals".to_string(),
            };
            output::print_header(&title);

            if proposals.is_empty() {
                output::print_info("No proposals found");
                return Ok(());
            }

            for proposal in &proposals {
                println!();
                println!(
                    "#{} [{}] {}",
                    proposal.number,
                    state_emoji(&proposal.state),
                    proposal.title
                );
                println!("  ID: {}...", &proposal.id[..16]);
                println!("  Proposer: {}", &proposal.proposer);
                println!(
                    "  Votes: {} Yes / {} No / {} Abstain ({:.1}% Yes)",
                    proposal.yes_votes,
                    proposal.no_votes,
                    proposal.abstain_votes,
                    proposal.yes_percentage
                );
                println!(
                    "  Participation: {:.2}% | Quorum: {}",
                    proposal.participation_rate,
                    if proposal.has_quorum {
                        "Reached"
                    } else {
                        "Not reached"
                    }
                );
                if let Some(remaining) = proposal.time_remaining_blocks {
                    println!("  Time Remaining: {}", format_blocks(remaining));
                }
            }
        }
    }

    Ok(())
}

/// Get proposal details.
async fn proposal(client: &RpcClient, id: &str, format: OutputFormat) -> Result<()> {
    let proposal = client.get_proposal(id).await?;

    if let Some(error) = &proposal.error {
        output::print_error(&format!("Failed to get proposal: {}", error));
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&proposal)?);
        }
        OutputFormat::Text => {
            output::print_header(&format!(
                "Proposal #{}: {}",
                proposal.number, proposal.title
            ));
            println!();
            output::print_kv("ID", &proposal.id);
            output::print_kv(
                "State",
                &format!("{} {}", state_emoji(&proposal.state), proposal.state),
            );
            output::print_kv("Type", &proposal.proposal_type);
            output::print_kv("Proposer", &proposal.proposer);
            println!();
            output::print_kv("Description", "");
            println!("{}", proposal.description);
            if let Some(url) = &proposal.discussion_url {
                output::print_kv("Discussion", url);
            }
            println!();
            println!("Timeline:");
            output::print_kv("  Created", &format!("Block {}", proposal.created_at_block));
            output::print_kv(
                "  Voting Starts",
                &format!("Block {}", proposal.voting_starts_block),
            );
            output::print_kv(
                "  Voting Ends",
                &format!("Block {}", proposal.voting_ends_block),
            );
            output::print_kv(
                "  Execution Allowed",
                &format!("Block {}", proposal.execution_allowed_block),
            );
            println!();
            println!("Voting Results:");
            let total = proposal.yes_votes + proposal.no_votes + proposal.abstain_votes;
            let yes_pct = if total > 0 {
                proposal.yes_votes as f64 / (proposal.yes_votes + proposal.no_votes) as f64 * 100.0
            } else {
                0.0
            };
            output::print_kv(
                "  Yes",
                &format!("{} ({:.1}%)", format_synor(proposal.yes_votes), yes_pct),
            );
            output::print_kv("  No", &format_synor(proposal.no_votes));
            output::print_kv("  Abstain", &format_synor(proposal.abstain_votes));
            output::print_kv("  Total Voters", &proposal.votes.len().to_string());

            if !proposal.votes.is_empty() {
                println!();
                println!("Recent Votes:");
                for vote in proposal.votes.iter().take(5) {
                    let choice_emoji = match vote.choice.as_str() {
                        "Yes" => "✅",
                        "No" => "❌",
                        "Abstain" => "⏸️",
                        _ => "🔘",
                    };
                    println!(
                        "  {} {} {} (weight: {})",
                        choice_emoji,
                        &vote.voter[..20],
                        vote.choice,
                        vote.weight
                    );
                    if let Some(reason) = &vote.reason {
                        println!("      \"{}\"", reason);
                    }
                }
            }
        }
    }

    Ok(())
}

/// Create a proposal.
#[allow(clippy::too_many_arguments)]
async fn create_proposal(
    client: &RpcClient,
    proposer: &str,
    proposal_type: &str,
    title: &str,
    description: &str,
    recipient: Option<&str>,
    amount: Option<u64>,
    parameter: Option<&str>,
    old_value: Option<&str>,
    new_value: Option<&str>,
    format: OutputFormat,
) -> Result<()> {
    // Build proposal params based on type
    let params = match proposal_type {
        "treasury_spend" | "ecosystem_grant" => {
            let recipient = recipient
                .ok_or_else(|| anyhow::anyhow!("--recipient required for treasury proposals"))?;
            let amount = amount
                .ok_or_else(|| anyhow::anyhow!("--amount required for treasury proposals"))?;
            json!({
                "recipient": recipient,
                "amount": amount
            })
        }
        "parameter_change" => {
            let param = parameter.ok_or_else(|| anyhow::anyhow!("--parameter required"))?;
            let old = old_value.ok_or_else(|| anyhow::anyhow!("--old-value required"))?;
            let new = new_value.ok_or_else(|| anyhow::anyhow!("--new-value required"))?;
            json!({
                "parameter": param,
                "old_value": old,
                "new_value": new
            })
        }
        "signaling" => {
            json!({})
        }
        _ => {
            json!({})
        }
    };

    let spinner = output::create_spinner("Creating proposal...");

    let result = client
        .create_proposal(proposer, proposal_type, title, description, params)
        .await?;

    spinner.finish_and_clear();

    if let Some(error) = &result.error {
        output::print_error(&format!("Failed to create proposal: {}", error));
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_success("Proposal created!");
            output::print_kv("Proposal ID", &result.proposal_id);
            output::print_kv("Number", &result.number.to_string());
            println!();
            output::print_info("Voting will begin after the voting delay period");
        }
    }

    Ok(())
}

/// Cast a vote.
async fn vote(
    client: &RpcClient,
    proposal_id: &str,
    voter: &str,
    choice: &str,
    reason: Option<&str>,
    format: OutputFormat,
) -> Result<()> {
    // Validate choice
    let valid_choices = ["yes", "no", "abstain"];
    if !valid_choices.contains(&choice.to_lowercase().as_str()) {
        anyhow::bail!("Invalid choice. Use: yes, no, or abstain");
    }

    let spinner = output::create_spinner("Casting vote...");

    let result = client.vote(proposal_id, voter, choice, reason).await?;

    spinner.finish_and_clear();

    if let Some(error) = &result.error {
        output::print_error(&format!("Failed to vote: {}", error));
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_success("Vote cast!");
            output::print_kv("Proposal", &result.proposal_id);
            output::print_kv("Voter", &result.voter);
            output::print_kv("Choice", &result.choice);
        }
    }

    Ok(())
}

/// Execute a proposal.
async fn execute(
    client: &RpcClient,
    proposal_id: &str,
    executor: &str,
    format: OutputFormat,
) -> Result<()> {
    let spinner = output::create_spinner("Executing proposal...");

    let result = client.execute_proposal(proposal_id, executor).await?;

    spinner.finish_and_clear();

    if let Some(error) = &result.error {
        output::print_error(&format!("Failed to execute proposal: {}", error));
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_success("Proposal executed!");
            output::print_kv("Proposal", &result.proposal_id);
            output::print_kv("Executed At", &format!("Block {}", result.executed_at));
        }
    }

    Ok(())
}

/// Get treasury overview.
async fn treasury(client: &RpcClient, format: OutputFormat) -> Result<()> {
    let pools = client.get_treasury_pools().await?;
    let total = client.get_treasury_balance().await?;

    match format {
        OutputFormat::Json => {
            let result = json!({
                "total_balance": total,
                "pools": pools
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_header("Treasury Overview");
            output::print_kv("Total Balance", &format_synor(total));
            println!();
            println!("Pools:");
            for pool in &pools {
                println!();
                let status = if pool.frozen {
                    "🔒 FROZEN"
                } else {
                    "✅ Active"
                };
                println!("  {} [{}]", pool.name, status);
                println!("    ID: {}", pool.id);
                println!("    Balance: {}", format_synor(pool.balance));
                println!(
                    "    Total Deposited: {}",
                    format_synor(pool.total_deposited)
                );
                println!("    Total Spent: {}", format_synor(pool.total_spent));
            }
        }
    }

    Ok(())
}

/// Get treasury pool details.
async fn treasury_pool(client: &RpcClient, id: &str, format: OutputFormat) -> Result<()> {
    let pool = client.get_treasury_pool(id).await?;

    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(&pool)?);
        }
        OutputFormat::Text => {
            let status = if pool.frozen {
                "🔒 FROZEN"
            } else {
                "✅ Active"
            };
            output::print_header(&format!("{} [{}]", pool.name, status));
            output::print_kv("ID", &pool.id);
            output::print_kv("Balance", &format_synor(pool.balance));
            output::print_kv("Total Deposited", &format_synor(pool.total_deposited));
            output::print_kv("Total Spent", &format_synor(pool.total_spent));
        }
    }

    Ok(())
}

// ==================== Helper Functions ====================

/// Format SYNOR amount (8 decimal places).
fn format_synor(amount: u64) -> String {
    let whole = amount / 100_000_000;
    let frac = amount % 100_000_000;
    if frac == 0 {
        format!("{} SYNOR", whole)
    } else {
        format!("{}.{:08} SYNOR", whole, frac)
            .trim_end_matches('0')
            .to_string()
    }
}

/// Format blocks as human-readable time.
fn format_blocks(blocks: u64) -> String {
    // Assuming ~1 second per block
    let seconds = blocks;
    if seconds < 60 {
        format!("{} blocks (~{} sec)", blocks, seconds)
    } else if seconds < 3600 {
        format!("{} blocks (~{} min)", blocks, seconds / 60)
    } else if seconds < 86400 {
        format!("{} blocks (~{:.1} hr)", blocks, seconds as f64 / 3600.0)
    } else {
        format!("{} blocks (~{:.1} days)", blocks, seconds as f64 / 86400.0)
    }
}

/// State emoji.
fn state_emoji(state: &str) -> &'static str {
    match state.to_lowercase().as_str() {
        "pending" => "⏳",
        "active" => "🗳️",
        "passed" => "✅",
        "defeated" => "❌",
        "executed" => "🚀",
        "cancelled" => "🚫",
        "expired" => "⌛",
        _ => "❓",
    }
}

/// Capitalize first letter.
fn capitalize(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}
