use anyhow::Result;
use rsipstack::rsip::{HostWithPort, Response, StatusCodeKind, Transport};
use rsipstack::transport::SipAddr;
use rsipstack::{
    dialog::{authenticate::Credential, registration::Registration},
};
use serde::{Deserialize, Serialize};
use std::time::Instant;
use tokio_util::sync::CancellationToken;
use tracing::{debug, warn};

use super::public_address::{normalize_transport, should_update_address};

#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct UserCredential {
    pub username: String,
    pub password: String,
    pub realm: Option<String>,
}

#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct RegisterOption {
    pub server: String,
    pub username: String,
    pub display_name: Option<String>,
    pub disabled: Option<bool>,
    pub credential: Option<UserCredential>,
}

impl From<UserCredential> for Credential {
    fn from(val: UserCredential) -> Self {
        Credential {
            username: val.username,
            password: val.password,
            realm: val.realm,
        }
    }
}

impl RegisterOption {
    pub fn aor(&self) -> String {
        format!("{}@{}", self.username, self.server)
    }
}

pub struct RegistrationHandle {
    pub registration: Registration,
    pub option: RegisterOption,
    pub cancel_token: CancellationToken,
    pub start_time: Instant,
    pub last_update: Instant,
    pub last_response: Option<Response>,
}

impl RegistrationHandle {
    pub fn should_retry_registration_now(
        &self,
        local_addr: &SipAddr,
        current_contact_address: &HostWithPort,
        discovered_public_address: Option<&HostWithPort>,
    ) -> bool {
        if normalize_transport(local_addr.r#type.as_ref()) != Transport::Udp {
            return false;
        }

        discovered_public_address
            .is_some_and(|address| should_update_address(current_contact_address, address))
    }

    pub async fn do_register(
        &mut self,
        sip_server: &rsipstack::rsip::Uri,
        expires: Option<u32>,
        contact: &rsipstack::rsip::typed::Contact,
    ) -> Result<(u32, Option<HostWithPort>)> {
        self.registration.contact = Some(contact.clone());
        let resp = match self
            .registration
            .register(sip_server.clone(), expires)
            .await
            .map_err(|e| anyhow::anyhow!("Registration failed: {}", e))
        {
            Ok(resp) => resp,
            Err(e) => {
                warn!("registration failed: {}", e);
                return Err(anyhow::anyhow!("Registration failed: {}", e));
            }
        };
        debug!(
            user = self.option.aor(),
            "registration response: {:?}", resp
        );
        match resp.status_code().kind() {
            StatusCodeKind::Successful => {
                self.last_update = Instant::now();
                self.last_response = Some(resp);
                Ok((
                    self.registration.expires(),
                    self.registration.public_address.clone(),
                ))
            }
            _ => Err(anyhow::anyhow!("{:?}", resp.reason_phrase())),
        }
    }
}
