// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Test this library's compatibility by running original Tridge rsync.
//!
//! This requires 'rsync' be available on the path.

use std::fmt;
use std::fs::{create_dir, File};

use anyhow::Result;
use chrono::prelude::*;
use tempdir::TempDir;

use rsyn::Client;

/// List files from a newly-created temporary directory.
#[test]
fn list_files() {
    install_test_logger();

    let tmp = TempDir::new("rsyn_interop_list_files").unwrap();
    File::create(tmp.path().join("a")).unwrap();
    File::create(tmp.path().join("b")).unwrap();
    create_dir(tmp.path().join("subdir")).unwrap();
    File::create(tmp.path().join("subdir").join("galah")).unwrap();

    let mut client = Client::local(tmp.path());
    client.set_recursive(true);
    let (flist, stats) = client.list_files().unwrap();

    assert_eq!(flist.len(), 5);
    let names: Vec<String> = flist
        .iter()
        .map(|fe| fe.name_lossy_string().into_owned())
        .collect();
    // Names should already be sorted.
    assert_eq!(names[0], ".");
    assert_eq!(names[1], "a");
    assert_eq!(names[2], "b");
    assert_eq!(names[3], "subdir");
    assert_eq!(names[4], "subdir/galah");

    // Check file types.
    assert!(flist[0].is_dir());
    assert!(!flist[0].is_file());
    assert!(
        flist[1].is_file(),
        "expected {:?} would be a file",
        &flist[1]
    );
    assert!(flist[2].is_file());
    assert!(flist[3].is_dir());
    assert!(flist[4].is_file());

    // Check mtimes. We don't control them precisely, but they should be close
    // to the current time. (Probably within a couple of seconds, but allow
    // some slack for debugging, thrashing machines, etc.)
    let now = Local::now();
    assert!((now - flist[0].mtime()).num_minutes() < 5);
    assert!((now - flist[1].mtime()).num_minutes() < 5);

    // All the files are empty.
    assert_eq!(stats.total_file_size, 0);
}

/// Only on Unix, check we can list a directory containing a symlink, and see
/// the symlink.
#[cfg(unix)]
#[test]
fn list_symlink() -> rsyn::Result<()> {
    install_test_logger();

    let tmp = TempDir::new("rsyn_interop_list_symlink")?;
    std::os::unix::fs::symlink("dangling link", tmp.path().join("a link"))?;

    let mut client = Client::local(tmp.path());
    client.mut_options().list_only = true;
    let (flist, _stats) = client.list_files()?;

    assert_eq!(flist.len(), 2);
    assert_eq!(flist[0].name_lossy_string(), ".");
    assert_eq!(flist[1].name_lossy_string(), "a link");

    assert!(!flist[0].is_symlink());
    assert!(flist[1].is_symlink());

    Ok(())
}

/// Only on Unix: list `/etc`, a good natural source of files with different
/// permissions, including some probably not readable to the non-root
/// user running this test.
#[cfg(unix)]
#[test]
fn list_files_etc() -> Result<()> {
    install_test_logger();
    let (_flist, _stats) = Client::local("/etc").set_recursive(true).list_files()?;
    Ok(())
}

/// Only on Unix: list `/dev`, a good source of devices and unusual files.
#[cfg(unix)]
#[test]
fn list_files_dev() -> Result<()> {
    install_test_logger();
    let mut client = Client::local("/dev");
    client.set_recursive(true);
    let (_flist, _stats) = client.list_files()?;
    Ok(())
}

fn install_test_logger() {
    // The global logger can only be installed once per process, but this'll be called for
    // many tests within the same process. They all try to install the same thing, so don't
    // worry if it fails.

    let _ = fern::Dispatch::new()
        .format(format_log)
        .level(log::LevelFilter::Debug)
        .chain(fern::Output::call(|record| println!("{}", record.args())))
        .apply();
}

/// Format a `log::Record`.
fn format_log(out: fern::FormatCallback<'_>, args: &fmt::Arguments<'_>, record: &log::Record<'_>) {
    out.finish(format_args!(
        "[{:<30}][{}] {}",
        record.target(),
        record.level().to_string().chars().next().unwrap(),
        args
    ))
}