# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Sets up the isolated environment for running tests.

Note: Although this file and class are named after "isolate daemon", it now
runs ffx in daemonless mode. A follow-up cleanup is planned to rename and
refactor this.
"""

import os
import tempfile

from typing import Optional

from contextlib import AbstractContextManager

from common import get_ffx_isolate_dir, has_ffx_isolate_dir, set_ffx_isolate_dir
from ffx_integration import ScopedFfxConfig
from modification_waiter import ModificationWaiter


class IsolateDaemon(AbstractContextManager):
    """Sets up the isolated environment for ffx."""

    class IsolateDir(AbstractContextManager):
        """Sets up the ffx isolate dir to a temporary folder if it's not set."""

        def __init__(self):
            if has_ffx_isolate_dir():
                self._temp_dir = None
            else:
                self._temp_dir = tempfile.TemporaryDirectory()

        def __enter__(self):
            if self._temp_dir:
                set_ffx_isolate_dir(self._temp_dir.__enter__())
            return self

        def __exit__(self, exc_type, exc_value, traceback):
            if self._temp_dir:
                try:
                    self._temp_dir.__exit__(exc_type, exc_value, traceback)
                except OSError:
                    # Ignore the errors when cleaning up the temporary folder.
                    pass
            return False

    class RepoProcessDir(AbstractContextManager):
        """Sets up a temporary folder for the repository server process dir.
        The default location, $XDG_STATE_HOME turns out to be in the
        a binding to a directory on the host machine. The isolate directory is
        a docker Volume. The performance of the isolate dir is much better than
        the performance of using the volume based directory, especially on
        arm64 hosts.
        """

        def __init__(self):
            # don't try to access the isolate dir at this point, it may not be
            # set up yet.
            self._process_dir_config = None

        def __enter__(self):
            self._process_dir_config = ScopedFfxConfig(
                'repository.process_dir', f'{get_ffx_isolate_dir()}/repo_proc'
            )
            self._process_dir_config.__enter__()
            return self

        def __exit__(self, exc_type, exc_value, traceback):
            return self._process_dir_config.__exit__(
                exc_type, exc_value, traceback
            )

    def __init__(self, logs_dir: Optional[str]):
        assert not has_ffx_isolate_dir()
        self._inits = [
            self.IsolateDir(),
            # The RepoProcess dir must be 'entered' after the IsolateDir, so the
            # iso directory is set up first.
            self.RepoProcessDir(),
            ModificationWaiter(logs_dir),
            # Keep the alphabetical order.
            ScopedFfxConfig('ffx.isolated', 'true'),
            # fxb/126212: The timeout rate determines the timeout for each file
            # transfer based on the size of the file / this rate (in MB).
            # Decreasing the rate to 1 (from 5) increases the timeout in
            # swarming, where large files can take longer to transfer.
            ScopedFfxConfig('fastboot.flash.timeout_rate', '1'),
            ScopedFfxConfig('fastboot.flash.min_timeout_secs', '600'),
            ScopedFfxConfig('fastboot.reboot.reconnect_timeout', '120'),
            ScopedFfxConfig('fastboot.usb.disabled', 'true'),
            ScopedFfxConfig('log.level', 'debug'),
        ]
        if logs_dir:
            self._inits.append(ScopedFfxConfig('log.dir', logs_dir))

    # Updating configurations to meet the requirement of isolate.
    def __enter__(self):
        # This environment variable needs to be set before running ffx commands
        # to avoid sending unnecessary analytics.
        os.environ['FUCHSIA_ANALYTICS_DISABLED'] = '1'
        for init in self._inits:
            init.__enter__()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        for init in self._inits:
            init.__exit__(exc_type, exc_value, traceback)
