class MozReview(object):
    """Interface to MozService service.

    This class can be used to create and control MozReview instances.
    """

    def __init__(self, path):
        if not path:
            raise Exception('You must specify a path to create an instance')
        path = os.path.abspath(path)
        self._path = path

        self._name = os.path.dirname(path)

        if not os.path.exists(path):
            os.mkdir(path)

        docker_state = os.path.join(path, 'docker-state.json')

        self._docker_state = docker_state

        docker_url, tls = params_from_env(os.environ)
        self._docker = Docker(docker_state, docker_url, tls=tls)

        if not self._docker.is_alive():
            raise Exception('Docker is not available.')

    def get_bugzilla(self, url, username='******', password='******'):
        return Bugzilla(url, username=username, password=password)

    def start(self, bugzilla_port=None, reviewboard_port=None,
            mercurial_port=None, verbose=False):
        """Start a MozReview instance."""
        if not bugzilla_port:
            bugzilla_port = get_available_port()
        if not reviewboard_port:
            reviewboard_port = get_available_port()
        if not mercurial_port:
            mercurial_port = get_available_port()

        db_image, web_image = self._docker.build_bmo(verbose=verbose)

        bugzilla_url = self._docker.start_bmo(cluster=self._name,
                hostname=None, http_port=bugzilla_port,
                db_image=db_image, web_image=web_image)[0]
        with open(self._bugzilla_url_path, 'wb') as fh:
            fh.write(bugzilla_url)

        bugzilla = self.get_bugzilla(bugzilla_url)

        rb = MozReviewBoard(self._path, bugzilla_url=bugzilla_url)
        rb.create()
        reviewboard_pid = rb.start(reviewboard_port)

        reviewboard_url = 'http://localhost:%s/' % reviewboard_port

        self.bugzilla_url = bugzilla_url
        self.reviewboard_url = reviewboard_url
        self.reviewboard_pid = reviewboard_pid
        self.admin_username = bugzilla.username
        self.admin_password = bugzilla.password

        mercurial_pid = self._start_mercurial_server(mercurial_port)

        self.mercurial_url = 'http://localhost:%s/' % mercurial_port
        with open(self._hg_url_path, 'w') as fh:
            fh.write(self.mercurial_url)

        self.mercurial_pid = mercurial_pid

    def stop(self):
        """Stop all services associated with this MozReview instance."""
        if os.path.exists(self._hg_pid_path):
            with open(self._hg_pid_path, 'rb') as fh:
                pid = int(fh.read().strip())
                kill(pid)

            os.unlink(self._hg_pid_path)

        rb = MozReviewBoard(self._path)
        rb.stop()

        self._docker.stop_bmo(self._name)

    def create_repository(self, path):
        with open(self._hg_url_path, 'rb') as fh:
            url = '%s%s' % (fh.read(), path)

        full_path = os.path.join(self._path, 'repos', path)

        env = dict(os.environ)
        env['HGRCPATH'] = '/dev/null'
        subprocess.check_call([self._hg, 'init', full_path], env=env)

        rb = MozReviewBoard(self._path)
        rbid = rb.add_repository(os.path.dirname(path), url)

        with open(os.path.join(full_path, '.hg', 'hgrc'), 'w') as fh:
            fh.write('\n'.join([
                '[reviewboard]',
                'repoid = %s' % rbid,
                '',
            ]))

        return url, rbid

    def create_user(self, email, password, fullname):
        with open(self._bugzilla_url_path, 'r') as fh:
            url = fh.read()

        b = self.get_bugzilla(url)
        return b.create_user(email, password, fullname)

    @property
    def _hg_pid_path(self):
        return os.path.join(self._path, 'hg.pid')

    @property
    def _hg_url_path(self):
        return os.path.join(self._path, 'hg.url')

    @property
    def _hg(self):
        return os.path.join(ROOT, 'venv', 'bin', 'hg')

    @property
    def _bugzilla_url_path(self):
        return os.path.join(self._path, 'bugzilla.url')

    def _start_mercurial_server(self, port):
        repos_path = os.path.join(self._path, 'repos')
        if not os.path.exists(repos_path):
            os.mkdir(repos_path)

        rb_ext_path = os.path.join(ROOT, 'hgext', 'reviewboard', 'server.py')
        dummyssh = os.path.join(ROOT, 'pylib', 'mercurial-support', 'dummyssh')

        global_hgrc = os.path.join(self._path, 'hgrc')
        with open(global_hgrc, 'w') as fh:
            fh.write('\n'.join([
                '[phases]',
                'publish = False',
                '',
                '[ui]',
                'ssh = python "%s"' % dummyssh,
                '',
                '[reviewboard]',
                'url = %s' % self.reviewboard_url,
                '',
                '[extensions]',
                'reviewboard = %s' % rb_ext_path,
                '',
                '[web]',
                'push_ssl = False',
                'allow_push = *',
                '',
                '[paths]',
                '/ = %s/**' % repos_path,
                '',
            ]))

        env = os.environ.copy()
        env['HGRCPATH'] = '/dev/null'
        env['HGENCODING'] = 'UTF-8'
        args = [
            self._hg,
            'serve',
            '-d',
            '-p', str(port),
            '--pid-file', self._hg_pid_path,
            '--web-conf', global_hgrc,
            '--accesslog', os.path.join(self._path, 'hg.access.log'),
            '--errorlog', os.path.join(self._path, 'hg.error.log'),
        ]
        # We execute from / so Mercurial doesn't pick up config files
        # from parent directories.
        subprocess.check_call(args, env=env, cwd='/')

        with open(self._hg_pid_path, 'rb') as fh:
            pid = fh.read().strip()

        return pid