# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import filecmp
import os
from pathlib import Path
from typing import Union

MOZ_MYCONFIG_ERROR = """
The MOZ_MYCONFIG environment variable to define the location of mozconfigs
is deprecated. If you wish to define the mozconfig path via an environment
variable, use MOZCONFIG instead.
""".strip()

MOZCONFIG_LEGACY_PATH_ERROR = """
You currently have a mozconfig at %s. This implicit location is no longer
supported. Please move it to %s/.mozconfig or set an explicit path
via the $MOZCONFIG environment variable.
""".strip()

DEFAULT_TOPSRCDIR_PATHS = (".mozconfig", "mozconfig")
DEPRECATED_TOPSRCDIR_PATHS = ("mozconfig.sh", "myconfig.sh")
DEPRECATED_HOME_PATHS = (".mozconfig", ".mozconfig.sh", ".mozmyconfig.sh")


class MozconfigFindException(Exception):
    """Raised when a mozconfig location is not defined properly."""


class MozconfigBuilder:
    def __init__(self):
        self._lines = []

    def append(self, block):
        self._lines.extend([line.strip() for line in block.split("\n") if line.strip()])

    def generate(self):
        return "".join(line + "\n" for line in self._lines)


def find_mozconfig(topsrcdir: Union[str, Path], env=os.environ):
    """Find the active mozconfig file for the current environment.

    This emulates the logic in mozconfig-find.

    1) If ENV[MOZCONFIG] is set, use that
    2) If $TOPSRCDIR/mozconfig or $TOPSRCDIR/.mozconfig exists, use it.
    3) If both exist or if there are legacy locations detected, error out.

    The absolute path to the found mozconfig will be returned on success.
    None will be returned if no mozconfig could be found. A
    MozconfigFindException will be raised if there is a bad state,
    including conditions from #3 above.
    """
    topsrcdir = Path(topsrcdir)

    # Check for legacy methods first.
    if "MOZ_MYCONFIG" in env:
        raise MozconfigFindException(MOZ_MYCONFIG_ERROR)

    env_path = env.get("MOZCONFIG", None) or None

    if env_path is not None:
        env_path = Path(env_path)

    if env_path is not None:
        if not env_path.is_absolute():
            potential_roots = [topsrcdir, Path.cwd()]
            # Attempt to eliminate duplicates for e.g.
            # self.topsrcdir == Path.cwd().
            potential_roots_strings = set(str(p.resolve()) for p in potential_roots)
            existing = [
                root
                for root in potential_roots_strings
                if (Path(root) / env_path).exists()
            ]
            if len(existing) > 1:
                # There are multiple files, but we might have a setup like:
                #
                # somedirectory/
                #   srcdir/
                #   objdir/
                #
                # MOZCONFIG=../srcdir/some/path/to/mozconfig
                #
                # and be configuring from the objdir.  So even though we
                # have multiple existing files, they are actually the same
                # file.
                mozconfigs = [root / env_path for root in existing]
                if not all(
                    map(
                        lambda p1, p2: filecmp.cmp(p1, p2, shallow=False),
                        mozconfigs[:-1],
                        mozconfigs[1:],
                    )
                ):
                    raise MozconfigFindException(
                        "MOZCONFIG environment variable refers to a path that "
                        + "exists in more than one of "
                        + ", ".join(potential_roots_strings)
                        + ". Remove all but one."
                    )
            elif not existing:
                raise MozconfigFindException(
                    "MOZCONFIG environment variable refers to a path that "
                    + "does not exist in any of "
                    + ", ".join(potential_roots_strings)
                )

            env_path = existing[0] / env_path
        elif not env_path.exists():  # non-relative path
            raise MozconfigFindException(
                "MOZCONFIG environment variable refers to a path that "
                f"does not exist: {env_path}"
            )

        if not env_path.is_file():
            raise MozconfigFindException(
                "MOZCONFIG environment variable refers to a " f"non-file: {env_path}"
            )

    srcdir_paths = [topsrcdir / p for p in DEFAULT_TOPSRCDIR_PATHS]
    existing = [p for p in srcdir_paths if p.is_file()]

    if env_path is None and len(existing) > 1:
        raise MozconfigFindException(
            "Multiple default mozconfig files "
            "present. Remove all but one. " + ", ".join(str(p) for p in existing)
        )

    path = None

    if env_path is not None:
        path = env_path
    elif len(existing):
        assert len(existing) == 1
        path = existing[0]

    if path is not None:
        return Path.cwd() / path

    deprecated_paths = [topsrcdir / s for s in DEPRECATED_TOPSRCDIR_PATHS]

    home = env.get("HOME", None)
    if home is not None:
        home = Path(home)
        deprecated_paths.extend([home / s for s in DEPRECATED_HOME_PATHS])

    for path in deprecated_paths:
        if path.exists():
            raise MozconfigFindException(
                MOZCONFIG_LEGACY_PATH_ERROR % (path, topsrcdir)
            )

    return None
