# nocheck: comments-valid  explanatory WHY-comments in this SPOT lookup predate the stricter lint
from __future__ import annotations

from pathlib import Path
from typing import Any

from ansible.errors import AnsibleError
from ansible.plugins.loader import lookup_loader
from ansible.plugins.lookup import LookupBase

from plugins.lookup.applications import LookupModule as ApplicationsLookup
from plugins.lookup.domain import LookupModule as DomainLookup
from plugins.lookup.users import LookupModule as UsersLookup
from utils.domains.primary_domain import get_domain
from utils.mail.provider import deployed_roles, resolve_active_provider
from utils.roles.entity.name import get_entity_name

SYSTEM_EMAIL_PREFIX = "SYSTEM_EMAIL_"

# Default mail provider role; overridable per inventory via the MAIL_PROVIDER var.
DEFAULT_MAIL_PROVIDER = "web-app-stalwart"

# Declared in resolution order so each computed default sees already-resolved
# predecessors (for example ``port`` depends on ``tls``).
RESOLUTION_ORDER = (
    "enabled",
    "timeout",
    "external",
    "environment",
    "domain",
    "host",
    "tls",
    "port",
    "auth",
    "auth_mechanism",
    "start_tls",
    "smtp",
    "from",
    "username",
    "password",
)

_TRUE_STRINGS = frozenset({"true", "yes", "1", "on"})
_FALSE_STRINGS = frozenset({"false", "no", "0", "off"})


def _short_to_var(short_key: str) -> str:
    return SYSTEM_EMAIL_PREFIX + short_key.upper()


def _as_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return bool(value)
    if isinstance(value, str):
        lowered = value.strip().lower()
        if lowered in _TRUE_STRINGS:
            return True
        if lowered in _FALSE_STRINGS:
            return False
    return bool(value)


def _render(
    value: Any,
    templar: Any | None,
    variables: dict[str, Any] | None = None,
) -> Any:
    if templar is None:
        return value
    if not isinstance(value, str) or "{{" not in value:
        return value
    # Loop: inventory expressions like DOMAIN_PRIMARY="{{ lookup('env','INFINITO_DOMAIN') }}" need multiple substitution passes.
    from utils.templating.ansible import _templar_render_best_effort

    base_vars: dict[str, Any] = dict(variables or {})
    for _ in range(4):
        if not isinstance(value, str) or "{{" not in value:
            break
        prev = value
        try:
            value = _templar_render_best_effort(templar, value, base_vars)
        except Exception:
            break
        if value == prev:
            break
    return value


class LookupModule(LookupBase):
    def run(
        self,
        terms: list[Any] | None,
        variables: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> list[Any]:
        terms = terms or []
        if len(terms) > 1:
            raise AnsibleError(
                "email: expected 0 or 1 terms: lookup('email'[, application_id])"
            )

        variables = variables or getattr(self._templar, "available_variables", {}) or {}
        self._kwargs = kwargs

        templar = getattr(self, "_templar", None)
        resolved: dict[str, Any] = {}
        for short_key in RESOLUTION_ORDER:
            var_name = _short_to_var(short_key)
            raw = variables.get(var_name)
            if raw is None or raw == "":
                resolved[short_key] = _render(
                    self._compute(short_key, resolved, variables),
                    templar,
                    variables,
                )
            else:
                resolved[short_key] = _render(raw, templar, variables)

        if len(terms) == 0:
            return [resolved]

        application_id = str(terms[0]).strip()
        overrides = self._app_email_overrides(application_id, variables)
        merged = dict(resolved)
        for key, value in overrides.items():
            merged[str(key).lower()] = _render(value, templar, variables)
        return [merged]

    def _compute(
        self,
        short_key: str,
        resolved: dict[str, Any],
        variables: dict[str, Any],
    ) -> Any:
        if short_key == "enabled":
            return True
        if short_key == "timeout":
            return "30"
        if short_key == "external":
            # Cluster-wide, not per-host: in swarm the mail provider is
            # manager-pinned so worker group_names lack it, yet every node
            # relays through the central provider (routing mesh). Gate on the
            # group having a host so workers use the authenticated config, not
            # a localhost root sender the provider rejects
            # ("Sender address rejected: Domain not found").
            groups = variables.get("groups") or {}
            return bool(groups.get(self._mail_provider(variables)))
        if short_key == "environment":
            external = _as_bool(resolved.get("external"))
            tls_enabled = _as_bool(variables.get("TLS_ENABLED"))
            docker_in_container = _as_bool(variables.get("DOCKER_IN_CONTAINER"))
            base = "external" if (external or tls_enabled) else "localhost"
            suffix = "_container" if (docker_in_container and not tls_enabled) else ""
            return base + suffix
        if short_key == "domain":
            return variables.get("DOMAIN_PRIMARY")
        if short_key == "tls":
            external = _as_bool(resolved.get("external"))
            if not external:
                return False
            mail_host = str(resolved.get("host") or "").lower()
            if mail_host.endswith(".onion") or self._provider_onion_primary(variables):
                return False
            return _as_bool(variables.get("TLS_ENABLED"))
        if short_key == "port":
            external = _as_bool(resolved.get("external"))
            if not external:
                return 25
            ports = self._provider_ports(variables)
            if _as_bool(resolved.get("tls")):
                return ports.get("smtps", 465)
            # Plaintext submission is the onion/no-TLS path, but a provider is
            # only reachable there if it binds one. Stalwart's default config
            # binds the implicit-TLS client ports plus the MX, so it advertises
            # no `submission`; fall back to the MX port rather than a listener
            # nothing answers on (`auth` drops with it -- an MX takes local
            # recipients without SMTP AUTH).
            return ports.get("submission") or ports.get("smtp", 25)
        if short_key == "host":
            env = resolved.get("environment")
            # `environment` folds TLS_ENABLED into its "external" base, so a
            # non-external relay under TLS would otherwise resolve to the mailu
            # domain while auth/tls/from stay in localhost mode -- an unreachable,
            # inconsistent render that aborts the msmtp health unit. Gate host on
            # `external` directly, like tls/auth/from do.
            if not _as_bool(resolved.get("external")) or env in (
                "external_container",
                "localhost",
                "localhost_container",
            ):
                return "localhost"
            return self._lookup_mail_provider_domain(variables)
        if short_key == "auth":
            env = resolved.get("environment")
            if env in ("external_container", "localhost"):
                return False
            # The MX listener offers no SMTP AUTH: when `port` fell back to it
            # because the provider binds no plaintext submission, announcing
            # AUTH aborts msmtp with EX_UNAVAILABLE before it ever sends.
            ports = self._provider_ports(variables)
            if not ports.get("submission") and resolved.get("port") == ports.get(
                "smtp", 25
            ):
                return False
            return _as_bool(resolved.get("external"))
        if short_key == "auth_mechanism":
            if not _as_bool(resolved.get("auth")):
                return "off"
            return "on" if _as_bool(resolved.get("tls")) else "plain"
        if short_key == "start_tls":
            return False
        if short_key == "smtp":
            return True
        if short_key == "from":
            external = _as_bool(resolved.get("external"))
            if external:
                no_reply = self._lookup_no_reply_user(variables)
                if isinstance(no_reply, dict):
                    email = no_reply.get("email")
                    if email:
                        return email
            inventory_hostname = variables.get("inventory_hostname") or "localhost"
            return f"root@{inventory_hostname}.localdomain"
        if short_key == "username":
            return resolved.get("from")
        if short_key == "password":
            no_reply = self._lookup_no_reply_user(variables)
            if isinstance(no_reply, dict):
                tokens = no_reply.get("tokens") or {}
                if isinstance(tokens, dict):
                    return tokens.get(self._mail_provider(variables), "") or ""
            return ""
        raise AnsibleError(f"email: unknown key {short_key!r}")

    def _mail_provider(self, variables: dict[str, Any]) -> str:
        value = variables.get("MAIL_PROVIDER")
        configured = str(value).strip() if value else DEFAULT_MAIL_PROVIDER
        return resolve_active_provider(
            configured,
            deployed_roles(variables.get("groups")),
            Path.cwd() / "roles",
        )

    def _provider_services(self, variables: dict[str, Any]) -> dict[str, Any]:
        """The active provider's ``services`` block, or ``{}`` when it cannot be
        resolved. The port probe reads the provider's own self-declaration."""
        apps = ApplicationsLookup()
        apps._templar = getattr(self, "_templar", None)
        forwarded = {
            k: v for k, v in getattr(self, "_kwargs", {}).items() if k == "roles_dir"
        }
        try:
            entry = apps.run(
                [self._mail_provider(variables), {}],
                variables=variables,
                **forwarded,
            )[0]
        except AnsibleError:
            return {}
        if not isinstance(entry, dict):
            return {}
        services = entry.get("services") or {}
        return services if isinstance(services, dict) else {}

    def _provider_ports(self, variables: dict[str, Any]) -> dict[str, int]:
        """The SMTP ports the active provider publishes, keyed by protocol.

        Read from the provider's own ``services.<entity>.ports.public`` rather
        than assumed, so a provider that binds no plaintext ``submission``
        (Stalwart) resolves differently from one that does (Mailu) without
        either being named here.
        """
        services = self._provider_services(variables)
        entity = get_entity_name(self._mail_provider(variables))
        entry = services.get(entity) or {}
        if not isinstance(entry, dict):
            return {}
        ports = (entry.get("ports") or {}).get("public") or {}
        if not isinstance(ports, dict):
            return {}
        out: dict[str, int] = {}
        for name, value in ports.items():
            try:
                out[str(name)] = int(value)
            except (TypeError, ValueError):
                continue
        return out

    def _provider_onion_primary(self, variables: dict[str, Any]) -> bool:
        """True when the provider's own host serves it onion-first.

        Resolved from the provider host's ``group_names`` rather than this
        host's: in swarm only the manager is in ``svc-net-tor``, and the
        provider's ``tor.enabled`` renders against ``group_names``, so a
        worker would otherwise see the clearnet name the provider never
        serves a certificate for.
        """
        groups = variables.get("groups") or {}
        provider = self._mail_provider(variables)
        tor_hosts = set(groups.get("svc-net-tor") or [])
        provider_hosts = [h for h in groups.get(provider) or [] if h in tor_hosts]
        if not provider_hosts:
            return False
        own_groups = list(variables.get("group_names") or [])
        fallback = own_groups + [
            g for g in ("svc-net-tor", provider) if g not in own_groups
        ]
        try:
            provider_groups = variables["hostvars"][provider_hosts[0]]["group_names"]
        except (KeyError, TypeError):
            provider_groups = fallback
        provider_view = dict(variables)
        provider_view["group_names"] = list(provider_groups or fallback)
        forwarded = {
            k: v for k, v in getattr(self, "_kwargs", {}).items() if k == "roles_dir"
        }
        try:
            domains = lookup_loader.get(
                "domains",
                loader=getattr(self, "_loader", None),
                templar=getattr(self, "_templar", None),
            ).run([], variables=provider_view, **forwarded)[0]
            domain = get_domain(domains, provider)
        except Exception:
            return False
        return str(domain or "").lower().endswith(".onion")

    def _lookup_mail_provider_domain(self, variables: dict[str, Any]) -> Any:
        domain_lookup = DomainLookup()
        domain_lookup._templar = getattr(self, "_templar", None)
        try:
            return domain_lookup.run(
                [self._mail_provider(variables)], variables=variables
            )[0]
        except Exception:
            return "localhost"

    def _lookup_no_reply_user(self, variables: dict[str, Any]) -> dict[str, Any]:
        users_lookup = UsersLookup()
        users_lookup._templar = getattr(self, "_templar", None)
        forwarded = {
            k: v for k, v in getattr(self, "_kwargs", {}).items() if k == "roles_dir"
        }
        try:
            entry = users_lookup.run(
                ["no-reply", {}], variables=variables, **forwarded
            )[0]
        except AnsibleError:
            return {}
        return entry if isinstance(entry, dict) else {}

    def _app_email_overrides(
        self,
        application_id: str,
        variables: dict[str, Any],
    ) -> dict[str, Any]:
        apps = ApplicationsLookup()
        apps._templar = getattr(self, "_templar", None)
        forwarded = {
            k: v for k, v in getattr(self, "_kwargs", {}).items() if k == "roles_dir"
        }
        try:
            entry = apps.run(
                [application_id, {}],
                variables=variables,
                **forwarded,
            )[0]
        except AnsibleError:
            return {}
        if not isinstance(entry, dict):
            return {}
        # Per the materialised payload exposes services under the
        # bare ``services`` key (no ``compose.services`` envelope).
        services = entry.get("services") or {}
        email = services.get("email") or {} if isinstance(services, dict) else {}
        if not isinstance(email, dict):
            return {}
        return email
