use crate::api::admin::NodeInfo;
use crate::api::messages::{
    IncomingMessage, MessageDestination, MessageStatus, PushMessageBody, PushMessageReceipt,
};
use crate::api::peers::{AggregatedStats, PeerInfo};
use crate::api::pubkey::PubkeyLookup;
use crate::api::routes::RoutesSnapshot;
use crate::api::topics::DefaultAction;
use crate::api::MyceliumClient;
use crate::error::{AppError, AppResult};
use crate::sidecar::SidecarConfig;
use crate::state::AppState;
use serde::Serialize;
use tauri::{AppHandle, State};

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DaemonStatus {
    pub running: bool,
    pub api_url: Option<String>,
    pub key_path: Option<String>,
    pub config_path: Option<String>,
    /// Full overlay IPv6 (e.g. `43d:956e:7877:d933:eecc:b305:21ff:77f9`).
    /// Surfaced separately from `nodeSubnet` so the UI can show users the
    /// address they should actually use as a message destination.
    pub overlay_ip: Option<String>,
}

fn snapshot(state: &AppState) -> DaemonStatus {
    let sc = &state.sidecar;
    DaemonStatus {
        running: sc.is_running(),
        api_url: sc.client().map(|c| c.base_url().to_string()),
        key_path: sc.key_path().map(|p| p.display().to_string()),
        config_path: sc.config_path().map(|p| p.display().to_string()),
        overlay_ip: sc.overlay_ip(),
    }
}

fn require_client(state: &AppState) -> AppResult<MyceliumClient> {
    state.sidecar.client().ok_or(AppError::DaemonNotRunning)
}

#[tauri::command]
pub fn daemon_status(state: State<'_, AppState>) -> DaemonStatus {
    snapshot(&state)
}

#[tauri::command]
pub async fn start_daemon(
    app: AppHandle,
    state: State<'_, AppState>,
    config: Option<SidecarConfig>,
) -> AppResult<DaemonStatus> {
    let cfg = config.unwrap_or_default();
    state.sidecar.start(&app, &cfg).await?;
    state
        .poller
        .start(app.clone(), std::sync::Arc::clone(&state.sidecar));
    Ok(snapshot(&state))
}

#[tauri::command]
pub async fn stop_daemon(state: State<'_, AppState>) -> AppResult<DaemonStatus> {
    state.poller.stop();
    state.sidecar.stop().await;
    Ok(snapshot(&state))
}

#[tauri::command]
pub async fn node_info(state: State<'_, AppState>) -> AppResult<NodeInfo> {
    let client = require_client(&state)?;
    client.node_info().await
}

#[tauri::command]
pub fn sidecar_logs(state: State<'_, AppState>) -> Vec<String> {
    state.sidecar.logs_snapshot()
}

// ─── Peers ───────────────────────────────────────────────────────────────────

#[tauri::command]
pub async fn peers_list(state: State<'_, AppState>) -> AppResult<Vec<PeerInfo>> {
    require_client(&state)?.list_peers().await
}

#[tauri::command]
pub async fn peer_add(state: State<'_, AppState>, endpoint: String) -> AppResult<()> {
    if endpoint.trim().is_empty() {
        return Err(AppError::BadInput("endpoint must not be empty".into()));
    }
    require_client(&state)?.add_peer(endpoint.trim()).await
}

#[tauri::command]
pub async fn peer_remove(state: State<'_, AppState>, endpoint: String) -> AppResult<()> {
    require_client(&state)?.remove_peer(&endpoint).await
}

#[tauri::command]
pub async fn peers_stats(state: State<'_, AppState>) -> AppResult<AggregatedStats> {
    let peers = require_client(&state)?.list_peers().await?;
    Ok(crate::api::peers::aggregate(&peers))
}

// ─── Routes ──────────────────────────────────────────────────────────────────

#[tauri::command]
pub async fn routes_snapshot(state: State<'_, AppState>) -> AppResult<RoutesSnapshot> {
    require_client(&state)?.routes_snapshot().await
}

// ─── Messages ────────────────────────────────────────────────────────────────

#[tauri::command]
pub async fn send_message(
    state: State<'_, AppState>,
    destination: MessageDestination,
    topic_b64: String,
    payload_b64: String,
) -> AppResult<PushMessageReceipt> {
    let body = PushMessageBody {
        dst: destination,
        topic: topic_b64,
        payload: payload_b64,
    };
    require_client(&state)?.send_message(&body).await
}

#[tauri::command]
pub async fn reply_message(
    state: State<'_, AppState>,
    id: String,
    destination: MessageDestination,
    topic_b64: String,
    payload_b64: String,
) -> AppResult<PushMessageReceipt> {
    let body = PushMessageBody {
        dst: destination,
        topic: topic_b64,
        payload: payload_b64,
    };
    require_client(&state)?.reply_message(&id, &body).await
}

#[tauri::command]
pub async fn message_status(state: State<'_, AppState>, id: String) -> AppResult<MessageStatus> {
    require_client(&state)?.message_status(&id).await
}

#[tauri::command]
pub fn inbox_messages(state: State<'_, AppState>) -> Vec<IncomingMessage> {
    state.poller.inbox_snapshot()
}

#[tauri::command]
pub fn inbox_clear(state: State<'_, AppState>) {
    state.poller.clear_inbox();
}

// ─── Topics ──────────────────────────────────────────────────────────────────

#[tauri::command]
pub async fn topics_default_get(state: State<'_, AppState>) -> AppResult<DefaultAction> {
    require_client(&state)?.topics_default_get().await
}

#[tauri::command]
pub async fn topics_default_set(state: State<'_, AppState>, accept: bool) -> AppResult<()> {
    require_client(&state)?
        .topics_default_set(&DefaultAction { accept })
        .await
}

#[tauri::command]
pub async fn topics_list(state: State<'_, AppState>) -> AppResult<Vec<String>> {
    require_client(&state)?.topics_list().await
}

#[tauri::command]
pub async fn topic_add(state: State<'_, AppState>, topic_b64: String) -> AppResult<()> {
    if topic_b64.trim().is_empty() {
        return Err(AppError::BadInput("topic must not be empty".into()));
    }
    require_client(&state)?.topic_add(topic_b64.trim()).await
}

#[tauri::command]
pub async fn topic_remove(state: State<'_, AppState>, topic_b64: String) -> AppResult<()> {
    require_client(&state)?.topic_remove(&topic_b64).await
}

#[tauri::command]
pub async fn topic_sources_list(
    state: State<'_, AppState>,
    topic_b64: String,
) -> AppResult<Vec<String>> {
    require_client(&state)?.topic_sources_list(&topic_b64).await
}

#[tauri::command]
pub async fn topic_source_add(
    state: State<'_, AppState>,
    topic_b64: String,
    subnet: String,
) -> AppResult<()> {
    require_client(&state)?
        .topic_source_add(&topic_b64, &subnet)
        .await
}

#[tauri::command]
pub async fn topic_source_remove(
    state: State<'_, AppState>,
    topic_b64: String,
    subnet: String,
) -> AppResult<()> {
    require_client(&state)?
        .topic_source_remove(&topic_b64, &subnet)
        .await
}

#[tauri::command]
pub async fn topic_forward_get(
    state: State<'_, AppState>,
    topic_b64: String,
) -> AppResult<Option<String>> {
    require_client(&state)?.topic_forward_get(&topic_b64).await
}

#[tauri::command]
pub async fn topic_forward_set(
    state: State<'_, AppState>,
    topic_b64: String,
    socket_path: String,
) -> AppResult<()> {
    require_client(&state)?
        .topic_forward_set(&topic_b64, &socket_path)
        .await
}

#[tauri::command]
pub async fn topic_forward_remove(state: State<'_, AppState>, topic_b64: String) -> AppResult<()> {
    require_client(&state)?
        .topic_forward_remove(&topic_b64)
        .await
}

// ─── Pubkey ──────────────────────────────────────────────────────────────────

#[tauri::command]
pub async fn lookup_pubkey(state: State<'_, AppState>, ip: String) -> AppResult<PubkeyLookup> {
    require_client(&state)?.lookup_pubkey(&ip).await
}

// ─── Identity ────────────────────────────────────────────────────────────────

/// Stops the daemon (if running), removes the saved private key file, and
/// returns the daemon to the idle state. The caller restarts the daemon
/// to provoke regeneration.
#[tauri::command]
pub async fn regenerate_identity(state: State<'_, AppState>) -> AppResult<()> {
    state.poller.stop();
    let key_path = state.sidecar.key_path();
    state.sidecar.stop().await;

    if let Some(path) = key_path {
        if path.exists() {
            std::fs::remove_file(&path).map_err(AppError::from)?;
        }
    } else {
        // Sidecar never started; resolve the canonical path via app_data_dir.
        if let Some(p) = default_key_path() {
            if p.exists() {
                std::fs::remove_file(&p).map_err(AppError::from)?;
            }
        }
    }
    Ok(())
}

fn default_key_path() -> Option<std::path::PathBuf> {
    // Best-effort: fall back to the same XDG location the sidecar uses.
    dirs_like_app_data().ok().map(|d| d.join("priv_key.bin"))
}

fn dirs_like_app_data() -> std::io::Result<std::path::PathBuf> {
    // We can't reach the AppHandle here, so we mirror Tauri's path:
    // $XDG_DATA_HOME/<identifier>/ or $HOME/.local/share/<identifier>/.
    let identifier = "tech.threefold.mycellium-ui";
    if let Ok(d) = std::env::var("XDG_DATA_HOME") {
        return Ok(std::path::PathBuf::from(d).join(identifier));
    }
    if let Ok(home) = std::env::var("HOME") {
        return Ok(std::path::PathBuf::from(home)
            .join(".local/share")
            .join(identifier));
    }
    Err(std::io::Error::new(
        std::io::ErrorKind::NotFound,
        "no app data dir",
    ))
}
