use crate::sandbox::Sandbox;
use anyhow::{Context, Result};
use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use std::os::unix::fs::PermissionsExt;
use tokio::fs;

#[derive(Debug, Serialize, Deserialize)]
pub struct MetadataResults {
    pub permissions: String,
    pub size: u64,
    pub created: Option<String>,
    pub modified: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InfoResult {
    pub message: String,
    pub metadata: Option<MetadataResults>,
    pub success: bool,
}

fn format_system_time(time: std::time::SystemTime) -> String {
    DateTime::<Local>::from(time)
        .format("%b %d %H:%M")
        .to_string()
}

pub async fn get_file_info(sandbox: &Sandbox, path: &str) -> Result<MetadataResults> {
    tracing::info!(path = %path, "getting file metadata");

    let canon_path = sandbox.resolve_path(path).await?;

    let metadata = fs::metadata(&canon_path)
        .await
        .with_context(|| format!("failed to read metadata for '{}'", canon_path.display()))?;

    let permissions = format!("{:o}", metadata.permissions().mode() & 0o777);
    let size = metadata.len();

    let created = metadata.created().ok().map(format_system_time);
    let modified = metadata.modified().ok().map(format_system_time);

    let result = MetadataResults {
        permissions,
        size,
        created,
        modified,
    };

    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::os::unix::fs::PermissionsExt;
    use std::sync::Arc;
    use tempfile::TempDir;
    use tokio::fs as async_fs;

    async fn setup_sandbox(temp_dir: &TempDir) -> Arc<Sandbox> {
        let root = temp_dir.path().to_string_lossy().to_string();
        let sandbox = Sandbox::new(vec![root]).await.expect("sandbox init failed");
        Arc::new(sandbox)
    }

    #[tokio::test]
    async fn test_get_file_info_basic() {
        let temp_dir = TempDir::new().unwrap();
        let sandbox = setup_sandbox(&temp_dir).await;
        let file_path = temp_dir.path().join("test.txt");

        async_fs::write(&file_path, b"hello").await.unwrap();

        let metadata = get_file_info(&sandbox, file_path.to_str().unwrap())
            .await
            .unwrap();

        assert_eq!(metadata.size, 5);
        assert!(!metadata.permissions.is_empty());
        assert!(metadata.created.is_some() || metadata.modified.is_some());
    }

    #[tokio::test]
    async fn test_get_file_info_symlink() {
        use std::os::unix::fs::symlink;

        let temp_dir = TempDir::new().unwrap();
        let sandbox = setup_sandbox(&temp_dir).await;
        let target = temp_dir.path().join("target.txt");
        let link = temp_dir.path().join("link.txt");

        async_fs::write(&target, b"content").await.unwrap();
        symlink(&target, &link).unwrap();

        // Should follow symlink
        let metadata = get_file_info(&sandbox, link.to_str().unwrap())
            .await
            .unwrap();

        assert_eq!(metadata.size, 7);
    }

    #[tokio::test]
    async fn test_get_file_info_file_not_found() {
        let temp_dir = TempDir::new().unwrap();
        let sandbox = setup_sandbox(&temp_dir).await;
        let missing = temp_dir.path().join("missing.txt");

        let result = get_file_info(&sandbox, missing.to_str().unwrap()).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_get_file_info_permissions() {
        let temp_dir = TempDir::new().unwrap();
        let sandbox = setup_sandbox(&temp_dir).await;
        let file_path = temp_dir.path().join("perm.txt");

        async_fs::write(&file_path, b"x").await.unwrap();
        let mut perms = async_fs::metadata(&file_path).await.unwrap().permissions();
        perms.set_mode(0o644);
        async_fs::set_permissions(&file_path, perms).await.unwrap();

        let metadata = get_file_info(&sandbox, file_path.to_str().unwrap())
            .await
            .unwrap();

        assert_eq!(metadata.permissions, "644");
    }
}
