//! Device registrar — accepts REGISTER from SIP phones and tracks contacts.
//!
//! When a device sends REGISTER, the registrar responds with 200 OK
//! and stores the device's current contact (source IP:port).
//!
//! Ported from ts/registrar.ts.

use crate::config::DeviceConfig;
use crate::ipc::{emit_event, OutTx};
use sip_proto::helpers::generate_tag;
use sip_proto::message::{ResponseOptions, SipMessage};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::{Duration, Instant};

const MAX_EXPIRES: u32 = 300;

/// A registered device entry.
#[derive(Debug, Clone)]
pub struct RegisteredDevice {
    pub device_id: String,
    // These fields are populated at REGISTER time for logging/debugging but are
    // not read back — device identity flows via the `device_registered` push
    // event, not via struct queries. Kept behind allow(dead_code) because
    // removing them would churn handle_register for no runtime benefit.
    #[allow(dead_code)]
    pub display_name: String,
    #[allow(dead_code)]
    pub extension: String,
    pub contact_addr: SocketAddr,
    #[allow(dead_code)]
    pub registered_at: Instant,
    pub expires_at: Instant,
    #[allow(dead_code)]
    pub aor: String,
}

/// Manages device registrations.
pub struct Registrar {
    /// Known device configs (from app config).
    devices: Vec<DeviceConfig>,
    /// Currently registered devices, keyed by device ID.
    registered: HashMap<String, RegisteredDevice>,
    out_tx: OutTx,
}

impl Registrar {
    pub fn new(out_tx: OutTx) -> Self {
        Self {
            devices: Vec::new(),
            registered: HashMap::new(),
            out_tx,
        }
    }

    /// Update the known device list from config.
    pub fn configure(&mut self, devices: &[DeviceConfig]) {
        self.devices = devices.to_vec();
    }

    /// Try to handle a SIP REGISTER from a device.
    /// Returns Some(response_bytes) if handled, None if not a known device.
    pub fn handle_register(&mut self, msg: &SipMessage, from_addr: SocketAddr) -> Option<Vec<u8>> {
        if msg.method() != Some("REGISTER") {
            return None;
        }

        // Find the device by matching the source IP against expectedAddress.
        let from_ip = from_addr.ip().to_string();
        let device = self
            .devices
            .iter()
            .find(|d| d.expected_address == from_ip)?;

        let from_header = msg.get_header("From").unwrap_or("");
        let aor = SipMessage::extract_uri(from_header)
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("sip:{}@{}", device.extension, from_ip));

        let expires_header = msg.get_header("Expires");
        let requested: u32 = expires_header.and_then(|s| s.parse().ok()).unwrap_or(3600);
        let expires = requested.min(MAX_EXPIRES);

        let entry = RegisteredDevice {
            device_id: device.id.clone(),
            display_name: device.display_name.clone(),
            extension: device.extension.clone(),
            contact_addr: from_addr,
            registered_at: Instant::now(),
            expires_at: Instant::now() + Duration::from_secs(expires as u64),
            aor: aor.clone(),
        };
        self.registered.insert(device.id.clone(), entry);

        // Emit event to TypeScript.
        emit_event(
            &self.out_tx,
            "device_registered",
            serde_json::json!({
                "device_id": device.id,
                "display_name": device.display_name,
                "address": from_ip,
                "port": from_addr.port(),
                "aor": aor,
                "expires": expires,
            }),
        );

        // Build 200 OK response.
        let contact = msg
            .get_header("Contact")
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("<sip:{}:{}>", from_ip, from_addr.port()));

        let response = SipMessage::create_response(
            200,
            "OK",
            msg,
            Some(ResponseOptions {
                to_tag: Some(generate_tag()),
                contact: Some(contact),
                extra_headers: Some(vec![("Expires".to_string(), expires.to_string())]),
                ..Default::default()
            }),
        );

        Some(response.serialize())
    }

    /// Get the contact address for a registered device.
    pub fn get_device_contact(&self, device_id: &str) -> Option<SocketAddr> {
        let entry = self.registered.get(device_id)?;
        if Instant::now() > entry.expires_at {
            return None;
        }
        Some(entry.contact_addr)
    }

    /// Find a registered device by its source IP address.
    pub fn find_by_address(&self, addr: &SocketAddr) -> Option<&RegisteredDevice> {
        let ip = addr.ip().to_string();
        self.registered
            .values()
            .find(|e| e.contact_addr.ip().to_string() == ip && Instant::now() <= e.expires_at)
    }
}
