use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;

use rand::RngCore;

/// Holds the 256-bit authentication token for this daemon process.
#[derive(Clone)]
pub struct AuthToken {
    hex: String,
}

impl AuthToken {
    /// Generate a fresh random token and write it to `<socket_dir>/ptyd.token`
    /// with 0600 permissions so only the owning user can read it.
    pub fn generate_and_persist(socket_dir: &Path) -> Result<Self, String> {
        let mut raw = [0u8; 32];
        rand::thread_rng().fill_bytes(&mut raw);
        let hex = hex::encode(raw);

        let token_path = socket_dir.join("ptyd.token");
        fs::write(&token_path, &hex)
            .map_err(|e| format!("failed to write token file {token_path:?}: {e}"))?;
        fs::set_permissions(&token_path, fs::Permissions::from_mode(0o600))
            .map_err(|e| format!("failed to chmod token file: {e}"))?;

        log::info!("token written to {:?}", token_path);
        Ok(Self { hex })
    }

    /// Return true if the provided string matches the stored token.
    /// Comparison is done in constant time via `hex::decode` + byte-level loop
    /// to avoid short-circuit timing leaks.
    pub fn verify(&self, presented: &str) -> bool {
        // Decode both sides; if either fails the token is wrong.
        let expected = match hex::decode(&self.hex) {
            Ok(b) => b,
            Err(_) => return false,
        };
        let provided = match hex::decode(presented) {
            Ok(b) => b,
            Err(_) => return false,
        };
        if expected.len() != provided.len() {
            return false;
        }
        // Constant-time compare.
        let mut diff = 0u8;
        for (a, b) in expected.iter().zip(provided.iter()) {
            diff |= a ^ b;
        }
        diff == 0
    }

    /// Expose the hex string for logging (first 8 chars only, for safety).
    pub fn redacted(&self) -> String {
        format!("{}...", &self.hex[..8])
    }
}
