use nu_ansi_term::Style;
use uzers::{Groups, Users};

use crate::fs::fields as f;
use crate::fs::fields::User;
use crate::output::cell::TextCell;
use crate::output::table::{GroupFormat, UserFormat};

pub trait Render {
    fn render<C: Colours, U: Users + Groups>(
        self,
        colours: &C,
        users: &U,
        user_format: UserFormat,
        group_format: GroupFormat,
        file_user: Option<User>,
    ) -> TextCell;
}

impl Render for Option<f::Group> {
    fn render<C: Colours, U: Users + Groups>(
        self,
        colours: &C,
        users: &U,
        user_format: UserFormat,
        group_format: GroupFormat,
        file_user: Option<User>,
    ) -> TextCell {
        use uzers::os::unix::GroupExt;

        let mut style = colours.not_yours();

        let group = match self {
            Some(g) => match users.get_group_by_gid(g.0) {
                Some(g) => (*g).clone(),
                None => return TextCell::paint(style, g.0.to_string()),
            },
            None => return TextCell::blank(colours.no_group()),
        };

        let current_uid = users.get_current_uid();
        if let Some(current_user) = users.get_user_by_uid(current_uid) {
            if current_user.primary_group_id() == group.gid()
                || group.members().iter().any(|u| u == current_user.name())
            {
                style = colours.yours();
            }
        }

        if group.gid() == 0 && style != colours.yours() {
            style = colours.root_group();
        }

        let mut group_name = match user_format {
            UserFormat::Name => group.name().to_string_lossy().into(),
            UserFormat::Numeric => group.gid().to_string(),
        };

        if let GroupFormat::Smart = group_format {
            if let Some(file_uid) = file_user {
                if let Some(file_user) = users.get_user_by_uid(file_uid.0) {
                    if file_user.name().to_string_lossy() == group.name().to_string_lossy() {
                        group_name = ":".to_string();
                    }
                }
            }
        }

        TextCell::paint(style, group_name)
    }
}

pub trait Colours {
    fn yours(&self) -> Style;
    fn not_yours(&self) -> Style;
    fn no_group(&self) -> Style;
    fn root_group(&self) -> Style;
}

#[cfg(test)]
#[allow(unused_results)]
pub mod test {
    use super::{Colours, Render};
    use crate::fs::fields as f;
    use crate::output::cell::TextCell;
    use crate::output::table::{GroupFormat, UserFormat};

    use nu_ansi_term::Color::*;
    use nu_ansi_term::Style;
    use uzers::mock::MockUsers;
    use uzers::os::unix::GroupExt;
    use uzers::{Group, User};

    struct TestColours;

    #[rustfmt::skip]
    impl Colours for TestColours {
        fn yours(&self)     -> Style { Fixed(80).normal() }
        fn not_yours(&self) -> Style { Fixed(81).normal() }
        fn no_group(&self)   -> Style { Black.italic() }
        fn root_group(&self) -> Style { Fixed(82).normal() }
    }

    #[test]
    fn named() {
        let mut users = MockUsers::with_current_uid(1000);
        users.add_group(Group::new(100, "folk"));

        let group = Some(f::Group(100));
        let file_user = Some(f::User(1000));
        let expected = TextCell::paint_str(TestColours.not_yours(), "folk");
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Regular,
                file_user
            )
        );

        let expected = TextCell::paint_str(TestColours.not_yours(), "100");
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &users,
                UserFormat::Numeric,
                GroupFormat::Regular,
                file_user
            )
        );
    }

    #[test]
    fn unnamed() {
        let users = MockUsers::with_current_uid(1000);

        let group = Some(f::Group(100));
        let file_user = Some(f::User(1000));
        let expected = TextCell::paint_str(TestColours.not_yours(), "100");
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Regular,
                file_user
            )
        );
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &users,
                UserFormat::Numeric,
                GroupFormat::Regular,
                file_user
            )
        );
    }

    #[test]
    fn primary() {
        let mut users = MockUsers::with_current_uid(2);
        users.add_user(User::new(2, "eve", 100));
        users.add_group(Group::new(100, "folk"));

        let group = Some(f::Group(100));
        let file_user = Some(f::User(2));
        let expected = TextCell::paint_str(TestColours.yours(), "folk");
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Regular,
                file_user
            )
        );
    }

    #[test]
    fn secondary() {
        let mut users = MockUsers::with_current_uid(2);
        users.add_user(User::new(2, "eve", 666));

        let test_group = Group::new(100, "folk").add_member("eve");
        users.add_group(test_group);

        let group = Some(f::Group(100));
        let file_user = Some(f::User(2));
        let expected = TextCell::paint_str(TestColours.yours(), "folk");
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Regular,
                file_user
            )
        );
    }

    #[test]
    fn overflow() {
        let group = Some(f::Group(2_147_483_648));
        let file_user = Some(f::User(1000));
        let expected = TextCell::paint_str(TestColours.not_yours(), "2147483648");
        assert_eq!(
            expected,
            group.render(
                &TestColours,
                &MockUsers::with_current_uid(0),
                UserFormat::Numeric,
                GroupFormat::Regular,
                file_user
            )
        );
    }

    #[test]
    fn smart() {
        let mut users = MockUsers::with_current_uid(1000);
        users.add_user(User::new(1000, "user", 100));
        users.add_user(User::new(1001, "http", 101));
        users.add_group(Group::new(100, "user"));
        users.add_group(Group::new(101, "http"));

        let user_group = Some(f::Group(100));
        let user_file = Some(f::User(1000));
        let expected = TextCell::paint_str(TestColours.yours(), ":");
        assert_eq!(
            expected,
            user_group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Smart,
                user_file
            )
        );

        let expected = TextCell::paint_str(TestColours.yours(), ":");
        assert_eq!(
            expected,
            user_group.render(
                &TestColours,
                &users,
                UserFormat::Numeric,
                GroupFormat::Smart,
                user_file
            )
        );

        let http_group = Some(f::Group(101));
        let expected = TextCell::paint_str(TestColours.not_yours(), "http");
        assert_eq!(
            expected,
            http_group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Smart,
                user_file
            )
        );

        let http_file = Some(f::User(1001));
        let expected = TextCell::paint_str(TestColours.not_yours(), ":");
        assert_eq!(
            expected,
            http_group.render(
                &TestColours,
                &users,
                UserFormat::Name,
                GroupFormat::Smart,
                http_file
            )
        );
    }
}
