opensefaz-cli 0.1.0

CLI para manipulação de documentos fiscais eletrônicos
//! OpenSefaz CLI - Ferramenta de linha de comando para NF-e

use clap::{Parser, Subcommand};
use opensefaz_rs::{Config, NfeClient, Certificado, Ambiente};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "opensefaz")]
#[command(author = "OpenSefaz Contributors")]
#[command(version = "0.1.0")]
#[command(about = "CLI para manipulação de documentos fiscais eletrônicos", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    /// Arquivo de certificado digital
    #[arg(short, long, global = true)]
    certificado: Option<PathBuf>,

    /// Senha do certificado
    #[arg(short, long, global = true)]
    senha: Option<String>,

    /// CNPJ da empresa
    #[arg(long, global = true)]
    cnpj: Option<String>,

    /// UF da empresa
    #[arg(short, long, global = true, default_value = "SP")]
    uf: String,

    /// Ambiente (homologacao ou producao)
    #[arg(short, long, global = true, default_value = "homologacao")]
    ambiente: String,

    /// Nível de log (trace, debug, info, warn, error)
    #[arg(short, long, global = true, default_value = "info")]
    log: String,
}

#[derive(Subcommand)]
enum Commands {
    /// Consulta uma NF-e pela chave de acesso
    Consultar {
        /// Chave de acesso da NF-e (44 dígitos)
        chave: String,
    },

    /// Consulta o status do serviço da SEFAZ
    Status,

    /// Valida um arquivo XML de NF-e
    Validar {
        /// Caminho para o arquivo XML
        arquivo: PathBuf,
    },

    /// Assina um arquivo XML de NF-e
    Assinar {
        /// Caminho para o arquivo XML
        arquivo: PathBuf,

        /// Caminho para salvar o XML assinado
        #[arg(short, long)]
        saida: Option<PathBuf>,
    },

    /// Gera uma chave de acesso de teste
    GerarChave {
        /// Código do estado (2 dígitos)
        #[arg(short, long, default_value = "42")]
        estado: String,

        /// CNPJ do emitente
        #[arg(long)]
        cnpj: Option<String>,
    },

    /// Verifica a validade do certificado
    Certificado,

    /// Formata documentos (CNPJ, CPF, CEP)
    Formatar {
        /// Valor a formatar
        valor: String,

        /// Tipo do documento (cnpj, cpf, cep)
        #[arg(short, long)]
        tipo: Option<String>,
    },
}

fn criar_cliente(args: &Cli) -> Result<NfeClient, Box<dyn std::error::Error>> {
    let certificado_path = args.certificado.as_ref()
        .ok_or("Certificado é obrigatório")?;
    
    let senha = args.senha.as_ref()
        .ok_or("Senha do certificado é obrigatória")?;
    
    let cnpj = args.cnpj.as_ref()
        .ok_or("CNPJ é obrigatório")?;

    let ambiente = match args.ambiente.as_str() {
        "producao" => Ambiente::Producao,
        _ => Ambiente::Homologacao,
    };

    let config = Config::new(
        cnpj.clone(),
        args.uf.clone(),
        ambiente,
    );

    let certificado = Certificado::from_pem_file(certificado_path, senha)?;
    
    Ok(NfeClient::new(config, certificado)?)
}

fn formatar_valor(valor: &str, tipo: Option<&str>) -> String {
    match tipo.unwrap_or("auto") {
        "cnpj" => opensefaz_rs::utils::formatar_cnpj(valor),
        "cpf" => opensefaz_rs::utils::formatar_cpf(valor),
        "cep" => opensefaz_rs::utils::formatar_cep(valor),
        "auto" => {
            match valor.len() {
                14 => opensefaz_rs::utils::formatar_cnpj(valor),
                11 => opensefaz_rs::utils::formatar_cpf(valor),
                8 => opensefaz_rs::utils::formatar_cep(valor),
                _ => valor.to_string(),
            }
        }
        _ => valor.to_string(),
    }
}

#[tokio::main]
async fn main() {
    let args = Cli::parse();

    // Configurar logging
    env_logger::Builder::from_env(
        env_logger::Env::default().default_filter_or(&args.log)
    ).init();

    let resultado = match args.command {
        Commands::Consultar { ref chave } => {
            match criar_cliente(&args) {
                Ok(client) => {
                    println!("Consultando NF-e: {}", chave);
                    match client.consultar_por_chave(chave).await {
                        Ok(resultado) => {
                            println!("Status: {:?}", resultado.status);
                            println!("Protocolo: {:?}", resultado.protocolo);
                            println!("Motivo: {}", resultado.motivo);
                            Ok(())
                        }
                        Err(e) => Err(format!("Erro na consulta: {}", e)),
                    }
                }
                Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
            }
        }

        Commands::Status => {
            match criar_cliente(&args) {
                Ok(client) => {
                    println!("Consultando status do serviço...");
                    match client.consultar_status_servico().await {
                        Ok(status) => {
                            if status {
                                println!("Serviço: Online");
                            } else {
                                println!("Serviço: Offline");
                            }
                            Ok(())
                        }
                        Err(e) => Err(format!("Erro na consulta: {}", e)),
                    }
                }
                Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
            }
        }

        Commands::Validar { ref arquivo } => {
            match std::fs::read_to_string(arquivo) {
                Ok(conteudo) => {
                    match criar_cliente(&args) {
                        Ok(client) => {
                            match client.validar_xml(&conteudo) {
                                Ok(_) => {
                                    println!("XML válido!");
                                    Ok(())
                                }
                                Err(e) => Err(format!("XML inválido: {}", e)),
                            }
                        }
                        Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
                    }
                }
                Err(e) => Err(format!("Erro ao ler arquivo: {}", e)),
            }
        }

        Commands::Assinar { ref arquivo, ref saida } => {
            match std::fs::read_to_string(arquivo) {
                Ok(conteudo) => {
                    match criar_cliente(&args) {
                        Ok(client) => {
                            match client.assinar_xml(&conteudo) {
                                Ok(xml_assinado) => {
                                    let caminho_saida = saida.clone().unwrap_or_else(|| {
                                        let mut path = arquivo.clone();
                                        path.set_extension("assinado.xml");
                                        path
                                    });
                                    
                                    match std::fs::write(&caminho_saida, &xml_assinado) {
                                        Ok(_) => {
                                            println!("XML assinado com sucesso!");
                                            println!("Salvo em: {:?}", caminho_saida);
                                            Ok(())
                                        }
                                        Err(e) => Err(format!("Erro ao salvar: {}", e)),
                                    }
                                }
                                Err(e) => Err(format!("Erro ao assinar: {}", e)),
                            }
                        }
                        Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
                    }
                }
                Err(e) => Err(format!("Erro ao ler arquivo: {}", e)),
            }
        }

        Commands::GerarChave { estado, cnpj } => {
            let cnpj = cnpj.unwrap_or_else(|| "99999999999999".to_string());
            let data = chrono::Utc::now();
            let ano_mes = data.format("%y%m").to_string();
            
            let chave = format!(
                "{}{}{}550010000000011{}",
                estado,
                ano_mes,
                cnpj,
                opensefaz_rs::utils::gerar_numero_aleatorio()
            );

            let dv = opensefaz_rs::utils::calcular_digito_verificador(&chave);
            let chave_completa = format!("{}{}", chave, dv);

            println!("Chave de acesso gerada:");
            println!("{}", opensefaz_rs::types::ChaveAcesso::formatar(&chave_completa));
            Ok(())
        }

        Commands::Certificado => {
            match criar_cliente(&args) {
                Ok(_) => {
                    println!("Certificado carregado com sucesso!");
                    // Em produção, mostrar detalhes do certificado
                    Ok(())
                }
                Err(e) => Err(format!("Erro ao carregar certificado: {}", e)),
            }
        }

        Commands::Formatar { valor, tipo } => {
            let formatado = formatar_valor(&valor, tipo.as_deref());
            println!("Original: {}", valor);
            println!("Formatado: {}", formatado);
            Ok(())
        }
    };

    if let Err(e) = resultado {
        eprintln!("Erro: {}", e);
        std::process::exit(1);
    }
}