"""Configuration manager for the conda plugin.

This file is used as a central location to manage global configuration
settings and deliver them to the locations where they are needed, including
other modules and YAML configuration files. It is designed to be callable as
a standalone script or as a module. It can install a conda configuration file
in the prefix, and to verify its existence and expected behavioral impact.
These functions are both used by the conda recipe itself.

It is imperative that this module not include any other anaconda_auth imports
so that it can be run during the conda build process
"""

import sys
from enum import Enum
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
from typing import NamedTuple
from typing import Set
from typing import Union

__all__ = []


class CredentialType(Enum):
    API_KEY = "api-key"
    REPO_TOKEN = "repo-token"


PREFIX_CONDARC_PATH = Path(sys.prefix) / "condarc.d" / "anaconda-auth.yml"


# This list is now serving THREE purposes. The keys are used in the conda
# plugin module to determine which hosts should be hardcoded to use
# anaconda-auth for authentication. The values are used to provide the
# keyring domain where the legacy token will be stored, as well as
# whether or not the destination should receive a proper API key.
# Finally, the list is used to generate the master anaconda-auth.yml
# configuration for conda.
class TokenDomainSetting(NamedTuple):
    token_domain: str
    default_credential_type: CredentialType = CredentialType.API_KEY
    autoconfigure_conda_channel_settings: bool = False


TOKEN_DOMAIN_MAP = {
    "repo.continuum.io": TokenDomainSetting("anaconda.com"),
    "repo.anaconda.com": TokenDomainSetting("anaconda.com"),
    "repo.anaconda.cloud": TokenDomainSetting(
        "anaconda.com", CredentialType.REPO_TOKEN, True
    ),
}


def _dictify(channel_settings: List[Dict[str, str]]) -> Dict[str, str]:
    """
    Extract the authentication information for each channel
    for better comparison with expectations.
    """
    return {c["channel"]: c.get("auth", "") for c in channel_settings}


def _assert_settings(
    context: Any, expected: Dict[str, str], filtered: bool = False
) -> None:
    """
    Compare just the channel authentication information for each
    channel with the expectation, with order independence
    """
    found = _dictify(context.channel_settings)
    if filtered:
        found = {k: v for k, v in expected.items() if k in expected}
    assert found == expected, f"{found=}, {expected=}"


def _build_channel_settings(
    include_defaults: bool = True, include_sites: bool = True
) -> List[Dict[str, str]]:
    hosts: Set[str] = set()
    if include_defaults:
        hosts.update(
            repo_domain
            for repo_domain, settings in TOKEN_DOMAIN_MAP.items()
            if settings.autoconfigure_conda_channel_settings
        )
    if include_sites:
        # We are delaying this import so this file can be run standalone
        from anaconda_auth.config import AnacondaAuthSitesConfig

        hosts.update(s.domain for s in AnacondaAuthSitesConfig().sites.root.values())
    return [{"channel": f"https://{host}/*", "auth": "anaconda-auth"} for host in hosts]


def _build_channel_yaml(
    include_defaults: bool = True, include_sites: bool = True
) -> str:
    settings = _build_channel_settings(include_defaults, include_sites)
    # Hand-construct the YAML so we can avoid a library dependency, and
    # easily include the warning comment.
    lines = [
        "# DO NOT EDIT THIS FILE.",
        "# This file was generated automatically for anaconda-auth.",
    ]
    lines.append("channel_settings:")
    for record in settings:
        pfx = "- "
        for key, value in record.items():
            lines.append(f'{pfx}{key}: "{value}"')
            pfx = "  "
    if not settings:
        lines[-1] += " []"
    lines.append("")
    return "\n".join(lines)


def _write_channel_settings(
    fpath: Union[Path, str],
    include_defaults: bool = True,
    include_sites: bool = True,
    overwrite: bool = False,
) -> None:
    with Path(fpath).open(mode="w" if overwrite else "x") as fp:
        fp.write(_build_channel_yaml(include_defaults, include_sites))


def _write_condarc_d_settings(overwrite: bool = False) -> None:
    PREFIX_CONDARC_PATH.parent.mkdir(parents=True, exist_ok=True)
    _write_channel_settings(
        PREFIX_CONDARC_PATH, include_sites=False, overwrite=overwrite
    )


def _verify_channel_settings(filtered: bool = True) -> None:
    from conda.base.context import context

    context.__init__()  # type: ignore[misc]
    expected = _build_channel_settings(include_sites=False)
    _assert_settings(context, _dictify(expected), filtered=filtered)


if __name__ == "__main__":
    import sys

    invalid = [
        x for x in sys.argv[1:] if x not in ("--install", "--verify", "--overwrite")
    ]
    if len(sys.argv) == 1 or invalid:
        print(
            f"Usage: {sys.argv[0]} [--install] [--verify] [--overwrite]",
            file=sys.stderr,
        )
        if invalid:
            print(f"Invalid option(s): {' '.join(invalid)}", file=sys.stderr)
        sys.exit(1)
    if "--install" in sys.argv:
        _write_condarc_d_settings(overwrite="--overwrite" in sys.argv)
    if "--verify" in sys.argv:
        _verify_channel_settings()
