use qcheck::Arbitrary;
use radicle::identity::RepoId;
use radicle::node::NodeId;
use radicle::test::arbitrary::{oid, refstring};

use super::EventFilter;

impl Arbitrary for EventFilter {
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
        use EventFilter::*;

        #[derive(Clone, Copy)]
        enum Variants {
            Repository,
            Branch,
            BranchCreated,
            BranchUpdated,
            BranchDeleted,
            Patch,
            PatchCreated,
            PatchUpdated,
            Node,
            Allow,
            Deny,
            Not,
            And,
            Or,
        }
        let leaves = [
            Variants::Repository,
            Variants::Branch,
            Variants::BranchCreated,
            Variants::BranchUpdated,
            Variants::BranchDeleted,
            Variants::Patch,
            Variants::PatchCreated,
            Variants::PatchUpdated,
            Variants::Node,
            Variants::Allow,
            Variants::Deny,
        ];
        let branches = [Variants::Not, Variants::And, Variants::Or];

        let n = i8::arbitrary(g).clamp(1, 10);

        // Choose a branch 7 out of 10 times, to ensure that we get more complex
        // filters when generating them
        //
        // SAFETY: the leaves and branches slices are non-empty so this will
        // always return a value
        let variant = if n < 7 {
            *g.choose(&branches)
                .expect("BUG: will always provide an EventFilter")
        } else {
            *g.choose(&leaves)
                .expect("BUG: will always provide an EventFilter")
        };

        // N.b. reduce the size to so that the recursive type eventually reduces
        // to pick a leaf branch
        let size = (3 * g.size() / 4).max(1);
        match variant {
            Variants::Repository => Repository(RepoId::arbitrary(g)),
            Variants::Branch => Branch(refstring(10)),
            Variants::BranchCreated => BranchCreated,
            Variants::BranchUpdated => BranchUpdated,
            Variants::BranchDeleted => BranchDeleted,
            Variants::Patch => Patch(oid()),
            Variants::PatchCreated => PatchCreated,
            Variants::PatchUpdated => PatchUpdated,
            Variants::Node => Node(NodeId::arbitrary(g)),
            Variants::Allow => Allow,
            Variants::Deny => Deny,
            Variants::Not => {
                g.set_size(size);
                Not(vec![Self::arbitrary(g)])
            }
            Variants::And => {
                g.set_size(size);
                And(Vec::arbitrary(g))
            }
            Variants::Or => {
                g.set_size(size);
                Or(Vec::arbitrary(g))
            }
        }
    }
}
