{

    package MockRESTClient;
    use URI;
    use Moo;
    extends 'GitLab::API::v4::RESTClient';

    has _mocks => (
        is       => 'ro',
        default  => sub { [] },
        init_arg => undef,
    );

    sub mock_endpoints {
        my $self = shift;

        while (@_) {
            my $method  = shift;
            my $path_re = shift;
            my $sub     = shift;

            push @{ $self->_mocks() }, [$method, $path_re, $sub];
        }

        return;
    }

    sub _http_tiny_request {
        my ($self, $req_method, $req) = @_;

        die "req_method may only be 'request' at this time"
          if $req_method ne 'request';

        my ($method, $url, $options) = @$req;

        my $path = URI->new($url)->path();
        $path =~ s{^.*api/v4/}{};

        foreach my $mock (@{ $self->_mocks() }) {
            my ($handler_method, $path_re, $sub) = @$mock;

            next if $method ne $handler_method;

            my @captures = ($path =~ $path_re);
            next if !@captures;    # No captures still returns a 1.

            my ($status, $content)
              = $sub->([$method, $url, $options], @captures);
            $content = JSON::to_json($content) if ref $content;

            return {
                status  => $status,
                success => ($status =~ m{^2\d\d$}) ? 1 : 0,
                defined($content) ? (content => $content) : (),
            };
        }

        die "No mock endpoint matched the $method '$path' endpoint";
    }
}

sub api {
    my ($gitdir) = @_;
    my @users = ({
        id       => 11,
        username => 'me',
        name     => 'Me',
        email    => 'me@debian.org',
        state    => 'active'
    });
    my @teams = ({
        id        => 2099,
        name      => 'Debian JavaScript Maintainers',
        full_name => 'Debian JavaScript Maintainers',
        full_path => 'js-team',
    });
    my @projects;
    my $next_id = 1;

    my $api = GitLab::API::v4->new(
        url               => 'https://example.com/api/v4',
        rest_client_class => 'MockRESTClient',
    );

    $api->rest_client->mock_endpoints(
        GET  => qr{^user$}  => sub { 200, $users[0] },
        GET  => qr{^users$} => sub { 200, \@users },
        POST => qr{^users$} => sub {
            my ($req) = @_;
            my $user = decode_json($req->[2]->{content});
            $user->{id} = $next_id;
            $next_id++;
            push @users, $user;
            return 204;
        },
        GET => qr{^users?/(\d+)$} => sub {
            my ($req, $id) = @_;
            foreach my $user (@users) {
                next if $user->{id} != $id;
                return 200, $user;
            }
            return 404;
        },
        GET => qr{^users/(\D+)$} => sub {
            my ($req, $id) = @_;
            foreach my $user (@users) {
                next if $user->{username} != $id;
                return 200, $user;
            }
            return 404;
        },
        GET => qr{^groups$} => sub {
            200, \@teams;
        },
        GET => qr{^groups/([^/]+)$} => sub {
            my ($req, $name) = @_;
            foreach my $team (@teams) {
                next if $team->{full_path} ne $name;
                return 200, $team;
            }
            return 404;
        },
        PUT => qr{^users/(\d+)$} => sub {
            my ($req, $id) = @_;
            my $data = decode_json($req->[2]->{content});
            foreach my $user (@users) {
                next if $user->{id} != $id;
                %$user = (%$user, %$data,);
                return 204;
            }
            return 404;
        },
        DELETE => qr{^users/(\d+)$} => sub {
            my ($req, $id) = @_;
            my @new;
            foreach my $user (@users) {
                next if $user->{id} == $id;
                push @new, $user;
            }
            return 404 if @new == @users;
            @users = @new;
            return 204;
        },
        # Projects
        POST => qr{^projects$} => sub {
            my $content = JSON::from_json($_[0]->[2]->{content});
            mkdir "$gitdir/me/$content->{path}";
            print
`cd $gitdir/me/$content->{path};git init;git config receive.denyCurrentBranch ignore;cd -`;
            $content->{id}        = scalar @projects + 1;
            $content->{hooks}     = [];
            $content->{namespace} = {
                kind => 'user',
                id   => 11,
                name => 'me',
            };
            $content->{path_with_namespace} = 'me/' . $content->{path};
            $content->{web_url} = 'http://no.org/me/' . $content->{path};
            push @projects, $content;
            return 200, $content;
        },
        GET => qr{^projects/(\d+)/hooks} => sub {
            my ($req, $id) = @_;
            my $res = eval { $projects[$id - 1]->{hooks} };
            return ($res ? (200, $res) : (404));
        },
        GET => qr{^projects/(\d+)/services/(\w+)} => sub {
            my ($req, $id, $service) = @_;
            return 404;
        },
        GET => qr{^projects$} => sub {
            my ($req) = @_;
            return (200, \@projects) unless ($req->[1] =~ /search=([^&]+)/);
            my $str = $1;
            my @res;
            foreach (@projects) {
                if ($_->{name} =~ /\Q$str\E/) {
                    push @res, $_;
                }
            }
            return 200, \@res;
        },
        GET => qr{^projects/([a-z]+)(?:%2F(\w+))*$} => sub {
            my ($req, @path) = @_;
            my $repo = pop @path;
            my $path = join '/', @path;
            foreach (@projects) {
                if ($_->{namespace}->{name} eq $path and $_->{path} eq $repo) {
                    return 200, $_;
                }
            }
            return 404;
        },
        GET => qr{^projects/(\d+)$} => sub {
            my ($req, $id) = @_;
            return 404 unless ($_ = $projects[$id - 1]);
            return 200, $_;
        },
        PUT => qr{^projects/(\d+)} => sub {
            my ($req, $id) = @_;
            return 404 unless ($_ = $projects[$id - 1]);
            my $content = JSON::from_json($req->[2]->{content});
            foreach my $k (keys %$content) {
                $_->{$k} = $content->{$k};
            }
            return 200, {};
        },
        POST => qr{^projects/(\d+)/hooks} => sub {
            my ($req, $id) = @_;
            return 404 unless ($_ = $projects[$id - 1]);
            my $content = JSON::from_json($req->[2]->{content});
            push @{ $_->{hooks} }, $content;
            return 200, {};
        },
        POST => qr{^projects/(\d+)/repository/branches} => sub {
            return 200, {};
        },
        DELETE => qr{^projects/(\d+)/repository/branches/([\w\-\.]+)$} => sub {
            return 200, {};
        },
    );
    return $api;
}

1;
