use sha2::{Digest, Sha256};
use std::path::PathBuf;

/// Compute UUID from file path using SHA256
/// UUID = SHA256(user_path + filename)[0:32]
pub fn compute_uuid(user_path: &str, filename: &str) -> String {
    let key = format!("{}/{}", user_path.trim_end_matches('/'), filename);
    let hash = Sha256::digest(key.as_bytes());
    let hash_str = hex::encode(hash);
    hash_str[0..32].to_string()
}

/// Compute UUID from full file path
pub fn compute_uuid_from_path(full_path: &str) -> String {
    let path = PathBuf::from(full_path);
    let parent = path
        .parent()
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or_default();
    let filename = path
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_default();

    compute_uuid(&parent, &filename)
}

/// Extract username and filepath from relative path
/// Input: ./demo/video.mp4 or ./demo/path/to/video.mp4
/// Returns: (username, filepath) e.g., ("demo", "video.mp4") or ("demo", "path/to/video.mp4")
pub fn extract_user_from_relative_path(relative_path: &str) -> (String, String) {
    let path = relative_path.strip_prefix("./").unwrap_or(relative_path);

    let path_buf = PathBuf::from(path);

    let mut components = path_buf.components();
    let username = components
        .next()
        .map(|c| c.as_os_str().to_string_lossy().to_string())
        .unwrap_or_default();

    let filepath: String = components
        .map(|c| c.as_os_str().to_string_lossy().to_string())
        .collect::<Vec<_>>()
        .join("/");

    (username, filepath)
}

/// Compute UUID from relative path (like ./demo/video.mp4)
/// The username is extracted from the first path component
pub fn compute_uuid_from_relative_path(relative_path: &str) -> String {
    let (username, filepath) = extract_user_from_relative_path(relative_path);
    compute_uuid(&username, &filepath)
}

/// Get MAC address of primary network interface
/// Returns MAC address in format: a1:b2:c3:d4:e5:f6
pub fn get_mac_address() -> String {
    use mac_address::get_mac_address;

    match get_mac_address() {
        Ok(Some(mac)) => {
            let bytes = mac.bytes();
            format!(
                "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
            )
        }
        Ok(None) => "00:00:00:00:00:00".to_string(),
        Err(_) => "00:00:00:00:00:00".to_string(),
    }
}

/// Compute Birth UUID (Stable Identity with Location)
/// UUID = SHA256(mac_address|birthday|path|filename)[0:32]
///
/// This UUID format ensures:
/// - Location Encoding: Path is part of the identity (like location code in ID card).
/// - Stability: Uses the original Birthday, not the current timestamp.
/// - Uniqueness: Different MAC, Birthday, Path, or Filename produces different UUIDs.
pub fn compute_birth_uuid(
    mac_address: &str,
    birthday: &str, // Fixed timestamp of original registration
    path: &str,     // Canonical file path (Location)
    filename: &str,
) -> String {
    let key = format!(
        "{}|{}|{}|{}",
        mac_address,
        birthday,
        path.trim_end_matches('/'),
        filename
    );
    let hash = Sha256::digest(key.as_bytes());
    hex::encode(hash)[0..32].to_string()
}

/// Check if UUID is Birth UUID format (32 characters)
pub fn is_birth_uuid(uuid: &str) -> bool {
    uuid.len() == 32 && !uuid.contains('_')
}

/// Extract username from sftpgo user home path
/// Input: ./demo/video.mp4 or /Users/.../demo/video.mp4
/// Returns: username (e.g., "demo")
pub fn extract_username_from_path(path: &str) -> String {
    let relative = path.strip_prefix("./").unwrap_or(path);
    let parts: Vec<&str> = relative.split('/').collect();
    parts.first().copied().unwrap_or("demo").to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_uuid_computation() {
        let uuid = compute_uuid("/Users/test/Videos", "video.mp4");
        assert_eq!(uuid.len(), 32);
        println!("UUID: {}", uuid);
    }

    #[test]
    fn test_uuid_from_path() {
        let uuid = compute_uuid_from_path("/Users/test/Videos/video.mp4");
        assert_eq!(uuid.len(), 32);
    }

    #[test]
    fn test_extract_user_from_relative_path() {
        let (username, filepath) = extract_user_from_relative_path("./demo/video.mp4");
        assert_eq!(username, "demo");
        assert_eq!(filepath, "video.mp4");

        let (username, filepath) = extract_user_from_relative_path("./demo/path/to/video.mp4");
        assert_eq!(username, "demo");
        assert_eq!(filepath, "path/to/video.mp4");
    }

    #[test]
    fn test_uuid_from_relative_path() {
        let uuid1 = compute_uuid_from_relative_path("./demo/video.mp4");
        let uuid2 = compute_uuid_from_relative_path("./demo/video.mp4");
        assert_eq!(uuid1, uuid2);
        assert_eq!(uuid1.len(), 32);

        let uuid_demo = compute_uuid_from_relative_path("./demo/video.mp4");
        let uuid_warren = compute_uuid_from_relative_path("./warren/video.mp4");
        assert_ne!(uuid_demo, uuid_warren);
    }

    #[test]
    fn test_get_mac_address() {
        let mac = get_mac_address();
        assert_eq!(mac.len(), 17); // a1:b2:c3:d4:e5:f6
        assert!(mac.contains(':'));
        println!("MAC Address: {}", mac);
    }

    #[test]
    fn test_birth_uuid_generation() {
        let uuid = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T22:00:00+08:00",
            "/Users/test/Videos",
            "video.mp4",
        );
        assert_eq!(uuid.len(), 32);
        println!("Birth UUID: {}", uuid);
    }

    #[test]
    fn test_birth_uuid_different_mac() {
        let uuid1 = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T10:00:00",
            "/Users/test/Videos",
            "video.mp4",
        );
        let uuid2 = compute_birth_uuid(
            "d4:e5:f6:a1:b2:c3",
            "2026-04-27T10:00:00",
            "/Users/test/Videos",
            "video.mp4",
        );
        assert_ne!(uuid1, uuid2);
    }

    #[test]
    fn test_birth_uuid_different_path() {
        // Moving file to different path creates new identity
        let uuid1 = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T10:00:00",
            "/Users/test/Videos",
            "video.mp4",
        );
        let uuid2 = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T10:00:00",
            "/Users/test/Archive",
            "video.mp4",
        );
        assert_ne!(uuid1, uuid2);
    }

    #[test]
    fn test_birth_uuid_same_elements() {
        let uuid1 = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T10:00:00",
            "/Users/test/Videos",
            "video.mp4",
        );
        let uuid2 = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T10:00:00",
            "/Users/test/Videos",
            "video.mp4",
        );
        assert_eq!(uuid1, uuid2); // Same elements = same UUID
    }

    #[test]
    fn test_is_birth_uuid() {
        let birth_uuid = compute_birth_uuid(
            "a1:b2:c3:d4:e5:f6",
            "2026-04-27T10:00:00",
            "/Users/demo",
            "video.mp4",
        );
        assert!(is_birth_uuid(&birth_uuid));
    }

    #[test]
    fn test_extract_username_from_path() {
        let username = extract_username_from_path("./demo/video.mp4");
        assert_eq!(username, "demo");

        let username = extract_username_from_path("./warren/path/to/video.mp4");
        assert_eq!(username, "warren");
    }
}
