"""Authentication credential resolution for the Sika SDK.

Implements a credential chain that resolves API keys from multiple
sources in priority order:

1. **Explicit key** — passed directly to ``SikaClient(api_key=...)``.
2. **Product-specific env var** — ``SIKA_{PRODUCT}_API_KEY``
   (e.g. ``SIKA_RECON_API_KEY``).
3. **Global env var** — ``SIKA_API_KEY``.
4. **Credential file** — ``~/.sika/credentials`` (TOML format).

If no credentials are found, ``resolve_api_key`` returns ``None``.

Credential file format (TOML)::

    [default]
    api_key = "st_recon_..."

    [profiles.staging]
    api_key = "st_recon_..."

The file must have permissions ``0600`` (owner read/write only).
A warning is logged if the file is group- or world-readable.
"""

from __future__ import annotations

import logging
import os
import stat
import tomllib
from pathlib import Path
from typing import Any

logger = logging.getLogger("sika.auth")

_DEFAULT_CREDENTIALS_PATH = Path.home() / ".sika" / "credentials"


def resolve_api_key(
    explicit_key: str | None = None,
    *,
    product: str | None = None,
    profile: str | None = None,
    credentials_path: Path | None = None,
) -> str | None:
    """Resolve an API key from the credential chain.

    Checks sources in priority order: explicit key, product-specific
    environment variable, global environment variable, credential file.

    Args:
        explicit_key: API key passed directly by the caller.
        product: Product name for product-specific env var lookup
            (e.g. ``"recon"`` checks ``SIKA_RECON_API_KEY``).
        profile: Named profile in the credential file. Defaults to
            the ``[default]`` section.
        credentials_path: Override path to the credential file.
            Defaults to ``~/.sika/credentials``.

    Returns:
        The resolved API key, or ``None`` if no credentials are found.
    """
    # 1. Explicit key
    if explicit_key is not None:
        return explicit_key

    # 2. Product-specific env var (e.g. SIKA_RECON_API_KEY)
    if product is not None:
        product_var = f"SIKA_{product.upper()}_API_KEY"
        product_key = os.environ.get(product_var)
        if product_key is not None:
            return product_key

    # 3. Global env var
    global_key = os.environ.get("SIKA_API_KEY")
    if global_key is not None:
        return global_key

    # 4. Credential file
    cred_file = CredentialFile(credentials_path)
    return cred_file.read_api_key(profile=profile or "default")


class CredentialFile:
    """Read and write the Sika credential file (``~/.sika/credentials``).

    The credential file uses TOML format with a ``[default]`` section
    and optional named profiles under ``[profiles.<name>]``.

    Args:
        path: Path to the credential file. Defaults to
            ``~/.sika/credentials``.
    """

    def __init__(self, path: Path | None = None) -> None:
        self._path = path or _DEFAULT_CREDENTIALS_PATH

    @property
    def path(self) -> Path:
        """The path to the credential file."""
        return self._path

    def read_api_key(self, profile: str = "default") -> str | None:
        """Read an API key from the credential file.

        Args:
            profile: Profile name to read. ``"default"`` reads from
                the ``[default]`` section. Any other value reads from
                ``[profiles.<name>]``.

        Returns:
            The API key string, or ``None`` if the file does not exist
            or the profile is not found.
        """
        if not self._path.exists():
            return None

        self._check_permissions()
        data = self._read_toml()
        if data is None:
            return None

        if profile == "default":
            section = data.get("default")
        else:
            profiles = data.get("profiles")
            if not isinstance(profiles, dict):
                return None
            section = profiles.get(profile)

        if not isinstance(section, dict):
            return None
        key = section.get("api_key")
        return str(key) if key is not None else None

    def delete_api_key(self, profile: str = "default") -> None:
        """Remove an API key (and its profile data) from the credential file.

        Removes the ``[default]`` section or the ``[profiles.<name>]``
        section for the given profile.  If the file does not exist or
        the profile is not present, this is a no-op.

        Args:
            profile: Profile name to delete.  ``"default"`` removes the
                ``[default]`` section.  Any other value removes
                ``[profiles.<name>]``.
        """
        if not self._path.exists():
            return

        data = self._read_toml()
        if data is None:
            return

        changed = False
        if profile == "default":
            if "default" in data:
                del data["default"]
                changed = True
        else:
            profiles = data.get("profiles")
            if isinstance(profiles, dict) and profile in profiles:
                del profiles[profile]
                # Remove empty profiles table
                if not profiles:
                    del data["profiles"]
                changed = True

        if changed:
            self._write_toml(data)

    def read_email(self, profile: str = "default") -> str | None:
        """Read an email address from the credential file.

        Args:
            profile: Profile name to read. ``"default"`` reads from
                the ``[default]`` section. Any other value reads from
                ``[profiles.<name>]``.

        Returns:
            The email string, or ``None`` if not stored.
        """
        if not self._path.exists():
            return None

        data = self._read_toml()
        if data is None:
            return None

        if profile == "default":
            section = data.get("default")
        else:
            profiles = data.get("profiles")
            if not isinstance(profiles, dict):
                return None
            section = profiles.get(profile)

        if not isinstance(section, dict):
            return None
        email = section.get("email")
        return str(email) if email is not None else None

    def write_email(self, email: str, profile: str = "default") -> None:
        """Write an email address to the credential file.

        Creates the parent directory and file if they do not exist.
        Sets file permissions to ``0600`` (owner read/write only).

        Args:
            email: The email address to store.
            profile: Profile name to write to.
        """
        data: dict[str, Any] = {}
        if self._path.exists():
            data = self._read_toml() or {}

        if profile == "default":
            if "default" not in data:
                data["default"] = {}
            data["default"]["email"] = email
        else:
            if "profiles" not in data:
                data["profiles"] = {}
            if profile not in data["profiles"]:
                data["profiles"][profile] = {}
            data["profiles"][profile]["email"] = email

        self._write_toml(data)

    def write_api_key(self, api_key: str, profile: str = "default") -> None:
        """Write an API key to the credential file.

        Creates the parent directory and file if they do not exist.
        Sets file permissions to ``0600`` (owner read/write only).

        Args:
            api_key: The API key to store.
            profile: Profile name to write to. ``"default"`` writes to
                the ``[default]`` section. Any other value writes to
                ``[profiles.<name>]``.
        """
        # Read existing data or start fresh
        data: dict[str, Any] = {}
        if self._path.exists():
            data = self._read_toml() or {}

        if profile == "default":
            if "default" not in data:
                data["default"] = {}
            data["default"]["api_key"] = api_key
        else:
            if "profiles" not in data:
                data["profiles"] = {}
            if profile not in data["profiles"]:
                data["profiles"][profile] = {}
            data["profiles"][profile]["api_key"] = api_key

        self._write_toml(data)

    def _check_permissions(self) -> None:
        """Warn if the credential file has insecure permissions."""
        try:
            mode = self._path.stat().st_mode
            # Check for group or other read/write/execute bits
            if mode & (stat.S_IRWXG | stat.S_IRWXO):
                perms = f"{mode & 0o777:04o}"
                logger.warning(
                    "Credential file %s has insecure permissions %s. "
                    "Expected 0600 (owner read/write only). "
                    "Fix with: chmod 600 %s",
                    self._path,
                    perms,
                    self._path,
                )
        except OSError:
            pass

    def _read_toml(self) -> dict[str, Any] | None:
        """Read and parse the credential file as TOML."""
        try:
            return tomllib.loads(self._path.read_text(encoding="utf-8"))
        except (OSError, tomllib.TOMLDecodeError) as exc:
            logger.warning("Failed to read credential file %s: %s", self._path, exc)
            return None

    def _write_toml(self, data: dict[str, Any]) -> None:
        """Write data as TOML to the credential file with 0600 permissions."""
        self._path.parent.mkdir(parents=True, exist_ok=True)

        lines: list[str] = []
        self._serialize_toml(data, lines, prefix="")

        self._path.write_text("\n".join(lines) + "\n", encoding="utf-8")
        self._path.chmod(0o600)

    @staticmethod
    def _serialize_toml(
        data: dict[str, Any],
        lines: list[str],
        prefix: str,
    ) -> None:
        """Serialize a dict to TOML lines.

        Handles simple key-value pairs and nested tables. Sufficient
        for the credential file format (no arrays, inline tables, etc.).
        """
        # First pass: write simple key-value pairs
        for key, value in data.items():
            if not isinstance(value, dict):
                lines.append(f'{key} = "{value}"')

        # Second pass: write nested tables
        for key, value in data.items():
            if isinstance(value, dict):
                section = f"{prefix}{key}" if not prefix else f"{prefix}.{key}"
                lines.append(f"\n[{section}]")
                CredentialFile._serialize_toml(value, lines, prefix=section)
