//! # quicproquo-bot — Bot SDK for E2E encrypted messaging
//!
//! Build automated agents that run on the quicproquo network with full MLS
//! end-to-end encryption. The bot SDK wraps the client library into a simple
//! polling-based API: connect, authenticate, send, receive.
//!
//! ## Quick start
//!
//! ```rust,no_run
//! use quicproquo_bot::{Bot, BotConfig};
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let config = BotConfig::new("127.0.0.1:7000", "bot-user", "bot-password")
//!         .ca_cert("server-cert.der")
//!         .state_path("bot-state.bin");
//!
//!     let bot = Bot::connect(config).await?;
//!
//!     // Send a DM
//!     bot.send_dm("alice", "Hello from bot!").await?;
//!
//!     // Poll for messages
//!     loop {
//!         for msg in bot.receive(5000).await? {
//!             println!("{}: {}", msg.sender, msg.text);
//!             if msg.text.starts_with("!echo ") {
//!                 bot.send_dm(&msg.sender, &msg.text[6..]).await?;
//!             }
//!         }
//!     }
//! }
//! ```
//!
//! ## Pipe mode (stdin/stdout JSON lines)
//!
//! The bot SDK also supports non-interactive pipe mode for shell integration:
//!
//! ```bash
//! # Send via pipe
//! echo '{"to":"alice","text":"hello"}' | qpq pipe --state bot.bin
//!
//! # Receive via pipe (JSON lines to stdout)
//! qpq pipe --recv --state bot.bin
//! ```

use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Context;
use tokio::task::LocalSet;

use quicproquo_client::{connect_node, init_auth, opaque_login, resolve_user, ClientAuth};
use quicproquo_core::IdentityKeypair;

/// Configuration for connecting a bot to a quicproquo server.
#[derive(Clone, Debug)]
pub struct BotConfig {
    /// Server address (host:port).
    pub server: String,
    /// Path to the server's CA certificate (DER format).
    pub ca_cert: PathBuf,
    /// TLS server name (defaults to "localhost").
    pub server_name: String,
    /// Bot's username for OPAQUE authentication.
    pub username: String,
    /// Bot's password for OPAQUE authentication.
    pub password: String,
    /// Path to the bot's encrypted state file.
    pub state_path: PathBuf,
    /// Password for the encrypted state file (None = unencrypted).
    pub state_password: Option<String>,
    /// Device ID reported to the server.
    pub device_id: Option<String>,
}

impl BotConfig {
    /// Create a new bot configuration with required fields.
    pub fn new(server: &str, username: &str, password: &str) -> Self {
        Self {
            server: server.to_string(),
            ca_cert: PathBuf::from("server-cert.der"),
            server_name: "localhost".to_string(),
            username: username.to_string(),
            password: password.to_string(),
            state_path: PathBuf::from("bot-state.bin"),
            state_password: None,
            device_id: None,
        }
    }

    /// Set the CA certificate path.
    pub fn ca_cert(mut self, path: &str) -> Self {
        self.ca_cert = PathBuf::from(path);
        self
    }

    /// Set the TLS server name for certificate validation.
    pub fn server_name(mut self, name: &str) -> Self {
        self.server_name = name.to_string();
        self
    }

    /// Set the state file path.
    pub fn state_path(mut self, path: &str) -> Self {
        self.state_path = PathBuf::from(path);
        self
    }

    /// Set the state file encryption password.
    pub fn state_password(mut self, pwd: &str) -> Self {
        self.state_password = Some(pwd.to_string());
        self
    }

    /// Set the device ID.
    pub fn device_id(mut self, id: &str) -> Self {
        self.device_id = Some(id.to_string());
        self
    }
}

/// A received message from the quicproquo network.
#[derive(Clone, Debug, serde::Serialize)]
pub struct Message {
    /// The sender's username (or "unknown" if resolution failed).
    pub sender: String,
    /// The decrypted plaintext message content.
    pub text: String,
    /// Server-assigned sequence number.
    pub seq: u64,
}

/// A bot connected to a quicproquo server.
///
/// The bot maintains its identity and MLS group state. Each call to
/// `send_dm` or `receive` opens a fresh QUIC connection (stateless
/// reconnect pattern — same as the CLI client).
pub struct Bot {
    config: BotConfig,
    identity: Arc<IdentityKeypair>,
}

impl Bot {
    /// Connect to a quicproquo server and authenticate.
    ///
    /// Loads or creates an identity from the state file, connects via QUIC/TLS,
    /// and performs OPAQUE password authentication.
    pub async fn connect(config: BotConfig) -> anyhow::Result<Self> {
        let state = quicproquo_client::client::state::load_or_init_state(
            &config.state_path,
            config.state_password.as_deref(),
        )
        .context("load or init bot state")?;

        let identity = Arc::new(IdentityKeypair::from_seed(state.identity_seed));

        // Authenticate on the first connection.
        let local = LocalSet::new();
        let cfg = config.clone();
        let id = Arc::clone(&identity);

        local
            .run_until(async {
                let client =
                    connect_node(&cfg.server, &cfg.ca_cert, &cfg.server_name).await?;

                let pk = id.public_key_bytes();
                let token = opaque_login(
                    &client,
                    &cfg.username,
                    &cfg.password,
                    &pk,
                )
                .await
                .context("OPAQUE login")?;

                init_auth(ClientAuth::from_raw(token, cfg.device_id.clone()));

                tracing::info!(username = %cfg.username, server = %cfg.server, "bot authenticated");
                Ok::<(), anyhow::Error>(())
            })
            .await?;

        Ok(Self { config, identity })
    }

    /// Send a plaintext message to a peer by username.
    ///
    /// Resolves the username to an identity key, then encrypts via MLS
    /// and delivers through the server.
    pub async fn send_dm(&self, peer_username: &str, text: &str) -> anyhow::Result<()> {
        // Resolve username → identity key hex so we send to the specific peer.
        let peer_key = self
            .resolve_user(peer_username)
            .await
            .context("resolve peer username")?;
        let peer_key_hex = hex::encode(&peer_key);

        quicproquo_client::cmd_send(
            &self.config.state_path,
            &self.config.server,
            &self.config.ca_cert,
            &self.config.server_name,
            Some(&peer_key_hex),
            false,
            text,
            self.config.state_password.as_deref(),
        )
        .await
        .context("send message")?;

        Ok(())
    }

    /// Receive pending messages, waiting up to `timeout_ms` milliseconds.
    ///
    /// Returns decrypted application messages. MLS control messages (commits,
    /// welcomes) are processed internally but not returned.
    pub async fn receive(&self, timeout_ms: u64) -> anyhow::Result<Vec<Message>> {
        let plaintexts = quicproquo_client::receive_pending_plaintexts(
            &self.config.state_path,
            &self.config.server,
            &self.config.ca_cert,
            &self.config.server_name,
            timeout_ms,
            self.config.state_password.as_deref(),
        )
        .await?;

        let messages: Vec<Message> = plaintexts
            .into_iter()
            .enumerate()
            .map(|(i, plaintext)| Message {
                sender: "peer".to_string(), // TODO: resolve from MLS group roster
                text: String::from_utf8_lossy(&plaintext).to_string(),
                seq: i as u64,
            })
            .collect();

        Ok(messages)
    }

    /// Receive raw plaintext bytes (for binary protocols or non-UTF-8 content).
    pub async fn receive_raw(&self, timeout_ms: u64) -> anyhow::Result<Vec<Vec<u8>>> {
        quicproquo_client::receive_pending_plaintexts(
            &self.config.state_path,
            &self.config.server,
            &self.config.ca_cert,
            &self.config.server_name,
            timeout_ms,
            self.config.state_password.as_deref(),
        )
        .await
    }

    /// Resolve a username to a 32-byte identity key.
    pub async fn resolve_user(&self, username: &str) -> anyhow::Result<Vec<u8>> {
        let local = LocalSet::new();
        let cfg = self.config.clone();
        let username = username.to_string();

        local
            .run_until(async {
                let client = connect_node(&cfg.server, &cfg.ca_cert, &cfg.server_name).await?;
                let key = resolve_user(&client, &username)
                    .await?
                    .ok_or_else(|| anyhow::anyhow!("user not found: {username}"))?;
                Ok(key)
            })
            .await
    }

    /// Get the bot's own username.
    pub fn username(&self) -> &str {
        &self.config.username
    }

    /// Get the bot's identity public key (32 bytes, Ed25519).
    pub fn identity_key(&self) -> [u8; 32] {
        self.identity.public_key_bytes()
    }

    /// Get the bot's identity key as a hex string.
    pub fn identity_key_hex(&self) -> String {
        hex::encode(self.identity.public_key_bytes())
    }
}

/// Read JSON commands from stdin and process them.
///
/// Each line should be a JSON object with:
/// - `{"action": "send", "to": "username", "text": "message"}`
/// - `{"action": "recv", "timeout_ms": 5000}`
/// - `{"action": "resolve", "username": "alice"}`
///
/// Results are written to stdout as JSON lines.
pub async fn run_pipe_mode(bot: &Bot) -> anyhow::Result<()> {
    use tokio::io::{AsyncBufReadExt, BufReader};

    let stdin = BufReader::new(tokio::io::stdin());
    let mut lines = stdin.lines();

    while let Ok(Some(line)) = lines.next_line().await {
        let line = line.trim().to_string();
        if line.is_empty() {
            continue;
        }

        let cmd: serde_json::Value = match serde_json::from_str(&line) {
            Ok(v) => v,
            Err(e) => {
                let err = serde_json::json!({"error": format!("invalid JSON: {e}")});
                println!("{err}");
                continue;
            }
        };

        let action = cmd["action"].as_str().unwrap_or("");
        let result = match action {
            "send" => {
                let to = cmd["to"].as_str().unwrap_or("");
                let text = cmd["text"].as_str().unwrap_or("");
                match bot.send_dm(to, text).await {
                    Ok(()) => serde_json::json!({"status": "ok", "action": "send"}),
                    Err(e) => serde_json::json!({"error": format!("{e:#}")}),
                }
            }
            "recv" => {
                let timeout = cmd["timeout_ms"].as_u64().unwrap_or(5000);
                match bot.receive(timeout).await {
                    Ok(msgs) => serde_json::json!({"status": "ok", "messages": msgs}),
                    Err(e) => serde_json::json!({"error": format!("{e:#}")}),
                }
            }
            "resolve" => {
                let username = cmd["username"].as_str().unwrap_or("");
                match bot.resolve_user(username).await {
                    Ok(key) => serde_json::json!({
                        "status": "ok",
                        "identity_key": hex::encode(&key),
                    }),
                    Err(e) => serde_json::json!({"error": format!("{e:#}")}),
                }
            }
            _ => serde_json::json!({"error": format!("unknown action: {action}")}),
        };

        println!("{result}");
    }

    Ok(())
}
