//! `cog-ha-matter` — Home Assistant + Matter Cognitum Seed cog (ADR-116).
//!
//! Binary entrypoint. The actual wiring lives in [`cog_ha_matter`] —
//! this main.rs is intentionally tiny so the cog runtime can call
//! into the library from tests and from the Seed's control plane
//! integration tests without re-launching the binary.

use std::process::ExitCode;

use clap::Parser;
use cog_ha_matter::runtime;
use tokio::sync::broadcast;
use tracing::{info, warn};
use wifi_densepose_sensing_server::mqtt::state::VitalsSnapshot;

#[derive(Parser, Debug)]
#[command(
    name = "cog-ha-matter",
    version,
    about = "Home Assistant + Matter Cognitum Seed cog",
    long_about = "Wraps the ADR-115 HA-DISCO + HA-MIND publisher as a \
                  Seed-installable artifact with mDNS, embedded broker, \
                  RuVector-backed thresholds, and Ed25519 witness. See \
                  docs/adr/ADR-116-cog-ha-matter-seed.md for the design."
)]
struct Args {
    /// Where to find the local sensing-server (the cog speaks to it
    /// to pull `VitalsSnapshot` for republication over MQTT/Matter).
    #[arg(long, default_value = "http://127.0.0.1:3000")]
    sensing_url: String,

    /// MQTT broker host. When omitted the cog can spin up an embedded
    /// rumqttd on `DEFAULT_EMBEDDED_BROKER_PORT` (v1: external only).
    #[arg(long, default_value = "127.0.0.1")]
    mqtt_host: String,

    /// MQTT broker port.
    #[arg(long, default_value_t = cog_ha_matter::DEFAULT_EMBEDDED_BROKER_PORT)]
    mqtt_port: u16,

    /// Strip biometrics at the wire — only semantic primitives published.
    /// Matches ADR-115 `--privacy-mode`. The right default for any
    /// deployment with non-tenant occupants.
    #[arg(long)]
    privacy_mode: bool,

    /// Print the manifest the cog would self-report to the Seed's
    /// control plane and exit. Useful for the build-time signer.
    #[arg(long)]
    print_manifest: bool,

    /// mDNS hostname for the Seed advertisement. Must end with
    /// `.local.` per RFC 6762. Default lets HA's discovery find a
    /// dev cog on localhost without LAN config.
    #[arg(long, default_value = "cog-ha-matter.local.")]
    mdns_hostname: String,

    /// LAN-routable IPv4 the cog binds the control plane on. The
    /// mDNS responder advertises this; HA reaches back to it for
    /// MQTT + Matter Bridge.
    #[arg(long, default_value = "127.0.0.1")]
    mdns_ipv4: String,

    /// Skip the mDNS responder. Useful in containerised CI where
    /// multicast bind is filtered, or when running multiple cog
    /// instances on the same loopback.
    #[arg(long)]
    no_mdns: bool,
}

#[tokio::main]
async fn main() -> ExitCode {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "cog_ha_matter=info,info".into()),
        )
        .init();

    let args = Args::parse();

    info!(
        sensing_url = %args.sensing_url,
        mqtt = format!("{}:{}", args.mqtt_host, args.mqtt_port),
        privacy = args.privacy_mode,
        "cog-ha-matter starting (ADR-116 P2 scaffold)"
    );

    if args.print_manifest {
        // Emit the manifest with build-time-template placeholders. The
        // Makefile substitutes {{VERSION}} / {{ARCH}} before signing.
        let m = cog_ha_matter::manifest::CogManifest {
            id: cog_ha_matter::COG_ID.into(),
            version: env!("CARGO_PKG_VERSION").into(),
            binary_url:
                "https://storage.googleapis.com/cognitum-apps/cogs/{{ARCH}}/cog-ha-matter-{{ARCH}}"
                    .into(),
            binary_bytes: 0,
            binary_sha256: String::new(),
            binary_signature: String::new(),
            installed_at: 0,
            status: "installed".into(),
        };
        println!(
            "{}",
            serde_json::to_string_pretty(&m).expect("manifest serialization is infallible")
        );
        return ExitCode::SUCCESS;
    }

    // P3: boot the ADR-115 publisher. The broadcast tx is held by
    // main so the channel doesn't close before the sensing-server
    // bridge (next iter) wires its VitalsSnapshot producer in.
    let identity = runtime::CogIdentity::default_for_build();
    let inputs = runtime::build_publisher_inputs(
        &args.mqtt_host,
        args.mqtt_port,
        args.privacy_mode,
        identity,
    );
    let (state_tx, state_rx) =
        broadcast::channel::<VitalsSnapshot>(runtime::DEFAULT_STATE_CHANNEL_CAPACITY);
    let publisher_handle = runtime::spawn_publisher(inputs, state_rx);
    info!(
        capacity = runtime::DEFAULT_STATE_CHANNEL_CAPACITY,
        "publisher spawned — awaiting VitalsSnapshot bridge (P3.5)"
    );

    // P3.5 (next iter): subscribe to the sensing-server's
    // `/v1/snapshot` WebSocket and republish into `state_tx`. Until
    // that lands the cog connects to MQTT, advertises discovery,
    // and just doesn't have any state to publish — exactly what an
    // HA install with no nodes online looks like.
    let _ = &state_tx;

    // P4: mDNS responder. HA's auto-discovery picks the cog up on
    // `_ruview-ha._tcp` so users don't need to type broker host/port.
    let _mdns_handle = if args.no_mdns {
        None
    } else {
        let identity = runtime::CogIdentity::default_for_build();
        let service = cog_ha_matter::mdns::build_mdns_service(
            &identity,
            cog_ha_matter::DEFAULT_CONTROL_PORT,
            args.mqtt_port,
            args.privacy_mode,
        );
        match runtime::start_mdns_responder(&service, &args.mdns_hostname, &args.mdns_ipv4) {
            Ok(h) => {
                info!(
                    fullname = h.fullname(),
                    hostname = %args.mdns_hostname,
                    ipv4 = %args.mdns_ipv4,
                    "mDNS responder registered — HA auto-discovery should find the cog now"
                );
                Some(h)
            }
            Err(e) => {
                warn!(error = ?e, "mDNS responder failed to start — discovery disabled, falling back to manual HA config");
                None
            }
        }
    };

    // Wait on Ctrl-C so the cog runs as a long-lived daemon under
    // the Seed's process supervisor.
    tokio::select! {
        _ = tokio::signal::ctrl_c() => {
            info!("ctrl-c received — shutting down");
        }
        joined = publisher_handle => {
            warn!(?joined, "publisher task exited unexpectedly");
        }
    }

    // _mdns_handle drops here, sending the mDNS goodbye packet so
    // HA's discovery integration sees the service leave cleanly.
    ExitCode::SUCCESS
}
