use std::time::{Duration, Instant};

#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
mod platform {
    use super::{Duration, Instant};
    use pcsc::{Card, Context, Error, Protocols, Scope, ShareMode};
    use std::ffi::CString;

    pub fn list_readers() -> Result<Vec<String>, String> {
        let ctx = Context::establish(Scope::User)
            .map_err(|e| format!("NFC service unavailable (establish context): {e}"))?;
        let mut readers_buf = [0; 4096];
        let readers = ctx
            .list_readers(&mut readers_buf)
            .map_err(map_list_error)?;

        let names: Vec<String> = readers
            .map(|reader| reader.to_string_lossy().to_string())
            .collect();

        Ok(names)
    }

    fn list_readers_from_context(ctx: &Context) -> Result<Vec<String>, String> {
        let mut readers_buf = [0; 4096];
        let readers = ctx
            .list_readers(&mut readers_buf)
            .map_err(map_list_error)?;
        Ok(readers
            .map(|reader| reader.to_string_lossy().to_string())
            .collect())
    }

    fn map_list_error(error: Error) -> String {
        match error {
            Error::NoService => {
                "NFC service is not running. Start PC/SC service and try again.".to_string()
            }
            other => format!("Could not list NFC readers: {other}"),
        }
    }

    // -----------------------------------------------------------------------
    // Persistent NFC connection handle for multi-command sessions
    // -----------------------------------------------------------------------

    /// Persistent connection to an NFC tag for multi-command sessions
    /// (authentication, enrollment, key rotation).
    pub struct NfcConnection {
        card: Card,
        reader_name: String,
    }

    const TRANSMIT_RETRIES: usize = 2;
    const RETRY_DELAY_MS: u64 = 60;

    fn transmit_once(
        card: &pcsc::Card,
        apdu: &[u8],
        buf: &mut [u8; 258],
    ) -> Result<Vec<u8>, pcsc::Error> {
        let response = card.transmit(apdu, buf)?;
        Ok(response.to_vec())
    }

    impl NfcConnection {
        /// Transmit a raw APDU and return the full response (including SW bytes).
        /// Retries once on transport-level failures (RF coupling glitches).
        pub fn transmit(&self, apdu: &[u8]) -> Result<Vec<u8>, String> {
            let mut buf = [0u8; 258];
            for attempt in 0..TRANSMIT_RETRIES {
                match transmit_once(&self.card, apdu, &mut buf) {
                    Ok(resp) => return Ok(resp),
                    Err(e) if attempt + 1 < TRANSMIT_RETRIES => {
                        std::thread::sleep(Duration::from_millis(RETRY_DELAY_MS));
                        continue;
                    }
                    Err(e) => return Err(format!("NFC transmit failed: {e}")),
                }
            }
            unreachable!()
        }

        /// Run multiple transmits within a PC/SC transaction. Use this for multi-step
        /// flows (auth + ChangeFileSettings) to avoid "non-existent transaction" errors
        /// on some readers (e.g. ACR1252).
        pub fn with_transaction<F, T>(&mut self, f: F) -> Result<T, String>
        where
            F: FnOnce(&mut dyn FnMut(&[u8]) -> Result<Vec<u8>, String>) -> Result<T, String>,
        {
            let tx = self
                .card
                .transaction()
                .map_err(|e| format!("NFC transaction failed: {e}"))?;
            let mut buf = [0u8; 258];
            let mut transmit = |apdu: &[u8]| -> Result<Vec<u8>, String> {
                for attempt in 0..TRANSMIT_RETRIES {
                    match tx.transmit(apdu, &mut buf) {
                        Ok(response) => return Ok(response.to_vec()),
                        Err(e) if attempt + 1 < TRANSMIT_RETRIES => {
                            std::thread::sleep(Duration::from_millis(RETRY_DELAY_MS));
                            continue;
                        }
                        Err(e) => return Err(format!("NFC transmit failed: {e}")),
                    }
                }
                unreachable!()
            };
            f(&mut transmit)
        }

        pub fn reader_name(&self) -> &str {
            &self.reader_name
        }
    }

    /// Connect to an NFC tag and return a persistent connection handle.
    /// Polls for card presence up to `timeout_ms`. The returned handle keeps
    /// the card connection alive for multi-command sessions.
    pub fn connect_tag(
        preferred_reader_name: Option<&str>,
        timeout_ms: u64,
    ) -> Result<NfcConnection, String> {
        let timeout_ms = timeout_ms.clamp(500, 60_000);
        let ctx = Context::establish(Scope::User)
            .map_err(|e| format!("NFC service unavailable: {e}"))?;
        let deadline = Instant::now() + Duration::from_millis(timeout_ms);

        loop {
            let available = list_readers_from_context(&ctx)?;
            if available.is_empty() {
                if Instant::now() >= deadline {
                    return Err("No NFC readers detected.".to_string());
                }
                std::thread::sleep(Duration::from_millis(250));
                continue;
            }

            let readers: Vec<String> = if let Some(preferred) = preferred_reader_name {
                let preferred = preferred.trim();
                if preferred.is_empty() {
                    available
                } else {
                    let matches: Vec<String> = available
                        .into_iter()
                        .filter(|name| name.eq_ignore_ascii_case(preferred))
                        .collect();
                    if matches.is_empty() {
                        return Err(format!("Requested NFC reader not found: {preferred}"));
                    }
                    matches
                }
            } else {
                available
            };

            for reader_name in &readers {
                let c_reader = match CString::new(reader_name.as_str()) {
                    Ok(c) => c,
                    Err(_) => continue,
                };
                match ctx.connect(c_reader.as_c_str(), ShareMode::Shared, Protocols::ANY) {
                    Ok(card) => {
                        return Ok(NfcConnection {
                            card,
                            reader_name: reader_name.clone(),
                        });
                    }
                    Err(Error::NoSmartcard) => continue,
                    Err(Error::NoService) => {
                        return Err("NFC service is not running.".to_string());
                    }
                    Err(e) => {
                        if Instant::now() >= deadline {
                            return Err(format!("Could not connect to NFC reader: {e}"));
                        }
                    }
                }
            }

            if Instant::now() >= deadline {
                return Err("No NFC tag detected within timeout.".to_string());
            }
            std::thread::sleep(Duration::from_millis(250));
        }
    }
}

#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
pub use platform::NfcConnection;

#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
pub fn connect_tag(
    preferred_reader_name: Option<&str>,
    timeout_ms: u64,
) -> Result<NfcConnection, String> {
    platform::connect_tag(preferred_reader_name, timeout_ms)
}

#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
pub fn list_readers() -> Result<Vec<String>, String> {
    platform::list_readers()
}

#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
pub fn list_readers() -> Result<Vec<String>, String> {
    Err("NFC scanning is not supported on this platform.".to_string())
}

// Unsupported platform stubs for NfcConnection
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
pub struct NfcConnection;

#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
impl NfcConnection {
    pub fn transmit(&self, _apdu: &[u8]) -> Result<Vec<u8>, String> {
        Err("NFC is not supported on this platform.".to_string())
    }
    pub fn reader_name(&self) -> &str {
        ""
    }
}

#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
pub fn connect_tag(
    _preferred_reader_name: Option<&str>,
    _timeout_ms: u64,
) -> Result<NfcConnection, String> {
    Err("NFC is not supported on this platform.".to_string())
}
