# Copyright 2022 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.
"""Debusine server controller."""

import hashlib
import logging
import re

import requests

from utils import common
from utils.common import Configuration, RunResult
from utils.waiter import Waiter

logger = logging.getLogger(__name__)


class DebusineServer:
    """Interacts with the debusine-server: execute debusine-admin commands."""

    @staticmethod
    def wait_for_server_ready():
        """
        Wait up to 5 seconds for the server to be ready.

        Return True/False depending on if the server is ready or not.
        """

        def is_ready():
            """Return True if {self._api_url}/admin/ is available."""
            response = requests.get(f'{Configuration.BASE_URL}/admin/')
            return response.status_code < 400

        return Waiter.wait_for_success(15, is_ready)

    @staticmethod
    def restart() -> RunResult:
        """Restart (via systemctl) debusine-server."""
        return common.run(["systemctl", "restart", "debusine-server"])

    @staticmethod
    def execute_command(
        command, *args, user=Configuration.DEBUSINE_SERVER_USER
    ) -> RunResult:
        """Execute a debusine server management command."""
        cmd = ["debusine-admin"] + [command] + [*args]
        result = common.run_as(user, cmd)

        return result

    @classmethod
    def verify_worker(
        cls, token: str, connected: bool, enabled: bool, list_workers=None
    ) -> bool:
        """
        Return True if list_workers has token with the specified state.

        :param token: token that is being verified
        :param connected: True if it is expected that the worker is connected
        :param enabled: True if it is expected that the worker's token enabled
        :param list_workers: output of the command 'list-workers' or None.
          If None it executes list-workers
        :return: True if a token is connected and enabled as per connected
          and enabled parameters

        list_workers looks like:

        Name Registered           Connected            Token hash  Enabled
        ---- -------------------  -------------------  ----------  -------
        pin  date and time+00:00  date and time+00:00  10f14       False

        Connected is a date and time with a timezone or "-"
        """
        if list_workers is None:
            list_workers = cls.execute_command('list_workers').stdout

        regexp = ''
        if connected:
            regexp += '[0-9]{2}:[0-9]{2} +'
        else:
            regexp += '- +'

        token_hash = hashlib.sha256(token.encode()).hexdigest()

        regexp += token_hash + ' +'

        if enabled:
            regexp += 'True'
        else:
            regexp += 'False'

        return re.search(regexp, list_workers) is not None

    @classmethod
    def wait_for_worker_connected(cls, token):
        """Wait up to 15 seconds for the worker to be connected."""

        def token_is_connected() -> bool:
            return cls.verify_worker(token, connected=True, enabled=True)

        return Waiter.wait_for_success(15, token_is_connected)
