"""Canonical dbt runtime configuration derived from Phlo settings.

This module manages dbt runtime configuration, profile generation, and target
resolution. It bridges Phlo's settings system with dbt's profile format,
enabling seamless integration between the two platforms.

Example:
    >>> from phlo_dbt.runtime_config import DbtRuntimeConfig, write_dbt_profile
    >>> config = DbtRuntimeConfig(
    ...     target_name="prod",
    ...     catalog="analytics",
    ...     schema="marts"
    ... )
    >>> profile_path = write_dbt_profile(config, Path("/app/profiles"))
    >>> print(f"Profile written to: {profile_path}")

"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any

from phlo.capabilities import (
    CapabilitySupport,
    RuntimeContext,
    resolve_runtime_ref,
    routing_from_context,
)
from phlo_dbt.settings import get_settings as get_dbt_settings
import yaml

DEFAULT_DBT_TARGET = "dev"
DBT_QUERY_ENGINE_SUPPORT = CapabilitySupport(supports_refs=True)
DEFAULT_DBT_PROFILE_NAME = "phlo"


@dataclass(frozen=True, slots=True)
class DbtRuntimeConfig:
    """Canonical dbt runtime configuration for the active execution target.

    This dataclass holds all configuration needed to generate a dbt profile,
    including connection details for the query engine (typically Trino),
    authentication settings, and execution parameters.

    Attributes:
        profile_name: Name of the dbt profile (default: "phlo").
        target_name: dbt target environment name (default: "dev").
        engine_type: Query engine adapter type (default: "trino").
        user: Database user for connections (default: "dagster").
        host: Query engine hostname (default: "trino").
        port: Query engine port (default: 8080).
        catalog: Default catalog name (default: "iceberg").
        schema: Default schema name (default: "raw").
        threads: Number of parallel threads for dbt execution (default: 2).
        http_scheme: HTTP scheme for connections (default: "http").
        method: Authentication method (default: "none").

    Example:
        >>> config = DbtRuntimeConfig(
        ...     target_name="prod",
        ...     catalog="analytics",
        ...     schema="marts",
        ...     threads=4
        ... )
        >>> payload = config.to_profile_payload()
        >>> print(yaml.dump(payload))

    """

    profile_name: str = DEFAULT_DBT_PROFILE_NAME
    target_name: str = DEFAULT_DBT_TARGET
    engine_type: str = "trino"
    user: str = "dagster"
    host: str = "trino"
    port: int = 8080
    catalog: str = "iceberg"
    schema: str = "raw"
    threads: int = 2
    http_scheme: str = "http"
    method: str = "none"

    def to_profile_payload(self) -> dict[str, Any]:
        """Return the config in dbt `profiles.yml` shape.

        Converts this configuration into the YAML structure expected by dbt,
        including the profile name, target, and output configuration.

        Returns:
            Dictionary formatted as dbt profiles.yml content.

        Example:
            >>> config = DbtRuntimeConfig(target_name="prod")
            >>> payload = config.to_profile_payload()
            >>> "phlo" in payload
            True

        """
        return {
            self.profile_name: {
                "target": self.target_name,
                "outputs": {
                    self.target_name: {
                        "type": self.engine_type,
                        "method": self.method,
                        "user": self.user,
                        "host": self.host,
                        "port": self.port,
                        "catalog": self.catalog,
                        "schema": self.schema,
                        "http_scheme": self.http_scheme,
                        "threads": self.threads,
                    }
                },
            }
        }


def resolve_dbt_target_name(
    runtime: RuntimeContext | None = None, *, target: str | None = None
) -> str:
    """Resolve the effective dbt target name from canonical routing.

    Resolution order:
    1. Explicit target argument
    2. Canonical routing environment
    3. Legacy `dbt_target` tag
    4. Default `DEFAULT_DBT_TARGET`

    Args:
        runtime: Optional runtime context for environment-based resolution.
        target: Optional explicit target name to use (highest priority).

    Returns:
        Resolved dbt target name string.

    Example:
        >>> target = resolve_dbt_target_name(target="prod")
        >>> print(target)
        prod
        >>>
        >>> # With runtime context containing environment
        >>> target = resolve_dbt_target_name(runtime=ctx)
        >>> # Returns environment name from routing context

    """
    if target:
        return target
    if runtime is not None:
        routing = routing_from_context(runtime)
        if routing.environment:
            return routing.environment
        runtime_tags = getattr(runtime, "tags", {}) or {}
        legacy_target = runtime_tags.get("dbt_target") if isinstance(runtime_tags, dict) else None
        if isinstance(legacy_target, str) and legacy_target:
            return legacy_target
    return DEFAULT_DBT_TARGET


def resolve_dbt_runtime_config(
    runtime: RuntimeContext | None = None,
    *,
    target: str | None = None,
) -> DbtRuntimeConfig:
    """Resolve canonical dbt runtime config from query-engine settings and routing.

    Combines Phlo settings with runtime context to produce a complete dbt runtime
    configuration. Handles catalog name resolution based on runtime references.

    Args:
        runtime: Optional runtime context for environment-based configuration.
        target: Optional explicit target name to use.

    Returns:
        Fully configured DbtRuntimeConfig instance.

    Example:
        >>> config = resolve_dbt_runtime_config(target="prod")
        >>> print(f"Catalog: {config.catalog}, Target: {config.target_name}")
        >>> # With runtime that has a branch reference
        >>> config = resolve_dbt_runtime_config(runtime=ctx)
        >>> # Catalog will include branch suffix if not "main"

    """
    settings = get_dbt_settings()
    target_name = resolve_dbt_target_name(runtime, target=target)
    catalog = settings.dbt_query_catalog
    ref = resolve_runtime_ref(runtime, support=DBT_QUERY_ENGINE_SUPPORT, default_ref="main")
    if ref and ref != "main":
        catalog = f"{catalog}_{ref}"

    return DbtRuntimeConfig(
        profile_name=resolve_dbt_profile_name(settings.dbt_project_path),
        target_name=target_name,
        engine_type=settings.dbt_query_engine_type,
        user=settings.dbt_query_user,
        host=settings.dbt_query_host,
        port=settings.dbt_query_port,
        catalog=catalog,
        schema=settings.dbt_query_schema,
        threads=settings.dbt_query_threads,
        http_scheme=settings.dbt_query_http_scheme,
        method=settings.dbt_query_auth_method,
    )


def resolve_dbt_profile_name(project_dir: Path) -> str:
    """Resolve the dbt profile name declared by the project, if any.

    Reads the dbt_project.yml file to extract the profile name. Falls back
    to the default profile name if the file doesn't exist or doesn't specify
    a profile.

    Args:
        project_dir: Path to the dbt project directory containing dbt_project.yml.

    Returns:
        Profile name string, either from the project file or the default.

    Example:
        >>> profile = resolve_dbt_profile_name(Path("/app/workflows/transforms/dbt"))
        >>> print(profile)
        phlo  # or the profile name from dbt_project.yml

    """
    project_file = project_dir / "dbt_project.yml"
    if not project_file.exists():
        return DEFAULT_DBT_PROFILE_NAME
    try:
        payload = yaml.safe_load(project_file.read_text(encoding="utf-8")) or {}
    except Exception:
        return DEFAULT_DBT_PROFILE_NAME
    profile_name = payload.get("profile")
    if isinstance(profile_name, str) and profile_name:
        return profile_name
    return DEFAULT_DBT_PROFILE_NAME


def render_dbt_profile_yaml(config: DbtRuntimeConfig) -> str:
    """Render canonical dbt runtime config as `profiles.yml` text.

    Args:
        config: DbtRuntimeConfig instance to serialize.

    Returns:
        YAML-formatted string suitable for writing to profiles.yml.

    Example:
        >>> config = DbtRuntimeConfig(target_name="prod")
        >>> yaml_text = render_dbt_profile_yaml(config)
        >>> print(yaml_text)
        phlo:
          target: prod
          outputs:
            prod:
              type: trino
              ...

    """
    return yaml.safe_dump(config.to_profile_payload(), sort_keys=False)


def write_dbt_profile(
    config: DbtRuntimeConfig,
    profiles_dir: Path,
    *,
    filename: str = "profiles.yml",
) -> Path:
    """Write canonical `profiles.yml` to disk and return its path.

    Creates the profiles directory if it doesn't exist, then writes the
    rendered YAML configuration.

    Args:
        config: DbtRuntimeConfig to serialize and write.
        profiles_dir: Directory path where profiles.yml should be written.
        filename: Name of the profile file (default: "profiles.yml").

    Returns:
        Path to the written profile file.

    Raises:
        OSError: If directory creation or file write fails.

    Example:
        >>> config = DbtRuntimeConfig(target_name="prod")
        >>> path = write_dbt_profile(config, Path("/app/profiles"))
        >>> print(f"Profile written to: {path}")

    """
    profiles_dir.mkdir(parents=True, exist_ok=True)
    profile_path = profiles_dir / filename
    profile_path.write_text(render_dbt_profile_yaml(config), encoding="utf-8")
    return profile_path


def ensure_dbt_profile(
    profiles_dir: Path,
    *,
    runtime: RuntimeContext | None = None,
    target: str | None = None,
) -> Path:
    """Resolve and write canonical `profiles.yml` for the active dbt target.

    Combines configuration resolution and profile writing into a single
    convenience function. Ensures a valid profiles.yml exists for the
    specified runtime context and target.

    Args:
        profiles_dir: Directory path where profiles.yml should be written.
        runtime: Optional runtime context for configuration resolution.
        target: Optional explicit target name.

    Returns:
        Path to the written (or existing) profile file.

    Example:
        >>> from phlo_dbt.runtime_config import ensure_dbt_profile
        >>> path = ensure_dbt_profile(
        ...     Path("/app/profiles"),
        ...     target="prod"
        ... )
        >>> print(f"Profile ready at: {path}")

    """
    return write_dbt_profile(
        resolve_dbt_runtime_config(runtime, target=target),
        profiles_dir,
    )
