//! Provider registration state machine.
//!
//! Handles the REGISTER cycle with upstream SIP providers:
//! - Sends periodic REGISTER messages
//! - Handles 401/407 Digest authentication challenges
//! - Detects public IP from Via received= parameter
//! - Emits registration state events to TypeScript
//!
//! Ported from ts/providerstate.ts.

use crate::config::ProviderConfig;
use crate::ipc::{emit_event, OutTx};
use sip_proto::helpers::{
    compute_digest_auth, generate_branch, generate_call_id, generate_tag, parse_digest_challenge,
};
use sip_proto::message::{RequestOptions, SipMessage};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::UdpSocket;
use tokio::sync::Mutex;
use tokio::time::{self, Duration};

/// Runtime state for a single SIP provider.
pub struct ProviderState {
    pub config: ProviderConfig,
    pub public_ip: Option<String>,
    pub is_registered: bool,
    pub registered_aor: String,

    // Registration transaction state.
    reg_call_id: String,
    reg_cseq: u32,
    reg_from_tag: String,

    // Network.
    lan_ip: String,
    lan_port: u16,
}

impl ProviderState {
    pub fn new(config: ProviderConfig, public_ip_seed: Option<&str>) -> Self {
        let aor = format!("sip:{}@{}", config.username, config.domain);
        Self {
            public_ip: public_ip_seed.map(|s| s.to_string()),
            is_registered: false,
            registered_aor: aor,
            reg_call_id: generate_call_id(None),
            reg_cseq: 0,
            reg_from_tag: generate_tag(),
            lan_ip: String::new(),
            lan_port: 0,
            config,
        }
    }

    /// Build and send a REGISTER request.
    pub fn build_register(&mut self) -> Vec<u8> {
        self.reg_cseq += 1;
        let pub_ip = self.public_ip.as_deref().unwrap_or(&self.lan_ip);

        let register = SipMessage::create_request(
            "REGISTER",
            &format!("sip:{}", self.config.domain),
            RequestOptions {
                via_host: pub_ip.to_string(),
                via_port: self.lan_port,
                via_transport: None,
                via_branch: Some(generate_branch()),
                from_uri: self.registered_aor.clone(),
                from_display_name: None,
                from_tag: Some(self.reg_from_tag.clone()),
                to_uri: self.registered_aor.clone(),
                to_display_name: None,
                to_tag: None,
                call_id: Some(self.reg_call_id.clone()),
                cseq: Some(self.reg_cseq),
                contact: Some(format!(
                    "<sip:{}@{}:{}>",
                    self.config.username, pub_ip, self.lan_port
                )),
                max_forwards: Some(70),
                body: None,
                content_type: None,
                extra_headers: Some(vec![
                    (
                        "Expires".to_string(),
                        self.config.register_interval_sec.to_string(),
                    ),
                    ("User-Agent".to_string(), "SipRouter/1.0".to_string()),
                    (
                        "Allow".to_string(),
                        "INVITE, ACK, OPTIONS, CANCEL, BYE, SUBSCRIBE, NOTIFY, INFO, REFER, UPDATE"
                            .to_string(),
                    ),
                ]),
            },
        );
        register.serialize()
    }

    /// Handle a SIP response that might be for this provider's REGISTER.
    /// Returns true if the message was consumed.
    pub fn handle_registration_response(&mut self, msg: &SipMessage) -> Option<Vec<u8>> {
        if !msg.is_response() {
            return None;
        }
        if msg.call_id() != self.reg_call_id {
            return None;
        }
        let cseq_method = msg.cseq_method().unwrap_or("");
        if !cseq_method.eq_ignore_ascii_case("REGISTER") {
            return None;
        }

        let code = msg.status_code().unwrap_or(0);

        if code == 200 {
            self.is_registered = true;
            return Some(Vec::new()); // consumed, no reply needed
        }

        if code == 401 || code == 407 {
            let challenge_header = if code == 401 {
                msg.get_header("WWW-Authenticate")
            } else {
                msg.get_header("Proxy-Authenticate")
            };

            let challenge_header = match challenge_header {
                Some(h) => h,
                None => return Some(Vec::new()), // consumed but no challenge
            };

            let challenge = match parse_digest_challenge(challenge_header) {
                Some(c) => c,
                None => return Some(Vec::new()),
            };

            let auth_value = compute_digest_auth(
                &self.config.username,
                &self.config.password,
                &challenge.realm,
                &challenge.nonce,
                "REGISTER",
                &format!("sip:{}", self.config.domain),
                challenge.algorithm.as_deref(),
                challenge.opaque.as_deref(),
            );

            // Resend REGISTER with auth credentials.
            self.reg_cseq += 1;
            let pub_ip = self.public_ip.as_deref().unwrap_or(&self.lan_ip);

            let auth_header_name = if code == 401 {
                "Authorization"
            } else {
                "Proxy-Authorization"
            };

            let register = SipMessage::create_request(
                "REGISTER",
                &format!("sip:{}", self.config.domain),
                RequestOptions {
                    via_host: pub_ip.to_string(),
                    via_port: self.lan_port,
                    via_transport: None,
                    via_branch: Some(generate_branch()),
                    from_uri: self.registered_aor.clone(),
                    from_display_name: None,
                    from_tag: Some(self.reg_from_tag.clone()),
                    to_uri: self.registered_aor.clone(),
                    to_display_name: None,
                    to_tag: None,
                    call_id: Some(self.reg_call_id.clone()),
                    cseq: Some(self.reg_cseq),
                    contact: Some(format!(
                        "<sip:{}@{}:{}>",
                        self.config.username, pub_ip, self.lan_port
                    )),
                    max_forwards: Some(70),
                    body: None,
                    content_type: None,
                    extra_headers: Some(vec![
                        (auth_header_name.to_string(), auth_value),
                        (
                            "Expires".to_string(),
                            self.config.register_interval_sec.to_string(),
                        ),
                        ("User-Agent".to_string(), "SipRouter/1.0".to_string()),
                        (
                            "Allow".to_string(),
                            "INVITE, ACK, OPTIONS, CANCEL, BYE, SUBSCRIBE, NOTIFY, INFO, REFER, UPDATE"
                                .to_string(),
                        ),
                    ]),
                },
            );
            return Some(register.serialize());
        }

        if code >= 400 {
            self.is_registered = false;
        }

        Some(Vec::new()) // consumed
    }

    /// Detect public IP from Via received= parameter.
    pub fn detect_public_ip(&mut self, via: &str) {
        if let Some(m) = via.find("received=") {
            let rest = &via[m + 9..];
            let end = rest
                .find(|c: char| !c.is_ascii_digit() && c != '.')
                .unwrap_or(rest.len());
            let ip = &rest[..end];
            if !ip.is_empty() && self.public_ip.as_deref() != Some(ip) {
                self.public_ip = Some(ip.to_string());
            }
        }
    }

    pub fn set_network(&mut self, lan_ip: &str, lan_port: u16) {
        self.lan_ip = lan_ip.to_string();
        self.lan_port = lan_port;
    }
}

/// Manages all provider states and their registration cycles.
pub struct ProviderManager {
    providers: Vec<Arc<Mutex<ProviderState>>>,
    out_tx: OutTx,
}

impl ProviderManager {
    pub fn new(out_tx: OutTx) -> Self {
        Self {
            providers: Vec::new(),
            out_tx,
        }
    }

    /// Initialize providers from config and start registration cycles.
    pub async fn configure(
        &mut self,
        configs: &[ProviderConfig],
        public_ip_seed: Option<&str>,
        lan_ip: &str,
        lan_port: u16,
        socket: Arc<UdpSocket>,
    ) {
        self.providers.clear();

        for cfg in configs {
            let mut ps = ProviderState::new(cfg.clone(), public_ip_seed);
            ps.set_network(lan_ip, lan_port);
            let ps = Arc::new(Mutex::new(ps));
            self.providers.push(ps.clone());

            // Start the registration cycle.
            let socket = socket.clone();
            let out_tx = self.out_tx.clone();
            tokio::spawn(async move {
                provider_register_loop(ps, socket, out_tx).await;
            });
        }
    }

    /// Try to handle a SIP response as a provider registration response.
    /// Returns true if consumed.
    pub async fn handle_response(&self, msg: &SipMessage, socket: &UdpSocket) -> bool {
        for ps_arc in &self.providers {
            let mut ps = ps_arc.lock().await;
            let was_registered = ps.is_registered;
            if let Some(reply) = ps.handle_registration_response(msg) {
                // If there's a reply to send (e.g. auth retry).
                if !reply.is_empty() {
                    if let Some(dest) = ps.config.outbound_proxy.to_socket_addr() {
                        let _ = socket.send_to(&reply, dest).await;
                    }
                }
                // Emit registration state change.
                if ps.is_registered != was_registered {
                    emit_event(
                        &self.out_tx,
                        "provider_registered",
                        serde_json::json!({
                            "provider_id": ps.config.id,
                            "registered": ps.is_registered,
                            "public_ip": ps.public_ip,
                        }),
                    );
                }
                return true;
            }
        }
        false
    }

    /// Find which provider sent a packet by matching source address.
    pub async fn find_by_address(&self, addr: &SocketAddr) -> Option<Arc<Mutex<ProviderState>>> {
        for ps_arc in &self.providers {
            let ps = ps_arc.lock().await;
            let proxy_addr = format!(
                "{}:{}",
                ps.config.outbound_proxy.address, ps.config.outbound_proxy.port
            );
            if let Ok(expected) = proxy_addr.parse::<SocketAddr>() {
                if expected == *addr {
                    return Some(ps_arc.clone());
                }
            }
            // Also match by IP only (port may differ).
            if ps.config.outbound_proxy.address == addr.ip().to_string() {
                return Some(ps_arc.clone());
            }

            // Hostname-based providers (e.g. sipgate.de) often deliver inbound
            // INVITEs from resolved IPs rather than the literal configured host.
            // Resolve the proxy host and accept any matching IP/port variant.
            use std::net::ToSocketAddrs;
            if let Ok(resolved) = format!(
                "{}:{}",
                ps.config.outbound_proxy.address, ps.config.outbound_proxy.port
            )
            .to_socket_addrs()
            {
                for resolved_addr in resolved {
                    if resolved_addr == *addr || resolved_addr.ip() == addr.ip() {
                        return Some(ps_arc.clone());
                    }
                }
            }
        }
        None
    }

    /// Find a provider by its config ID (e.g. "easybell").
    pub async fn find_by_provider_id(
        &self,
        provider_id: &str,
    ) -> Option<Arc<Mutex<ProviderState>>> {
        for ps_arc in &self.providers {
            let ps = ps_arc.lock().await;
            if ps.config.id == provider_id {
                return Some(ps_arc.clone());
            }
        }
        None
    }
}

/// Registration loop for a single provider.
async fn provider_register_loop(
    ps: Arc<Mutex<ProviderState>>,
    socket: Arc<UdpSocket>,
    _out_tx: OutTx,
) {
    // Initial registration.
    {
        let mut state = ps.lock().await;
        let register_buf = state.build_register();
        if let Some(dest) = state.config.outbound_proxy.to_socket_addr() {
            let _ = socket.send_to(&register_buf, dest).await;
        }
    }

    // Re-register periodically (85% of the interval).
    let interval_sec = {
        let state = ps.lock().await;
        (state.config.register_interval_sec as f64 * 0.85) as u64
    };
    let mut interval = time::interval(Duration::from_secs(interval_sec.max(30)));
    interval.tick().await; // skip first immediate tick

    loop {
        interval.tick().await;
        let mut state = ps.lock().await;
        let register_buf = state.build_register();
        if let Some(dest) = state.config.outbound_proxy.to_socket_addr() {
            let _ = socket.send_to(&register_buf, dest).await;
        }
    }
}
