"""
Configuration management for iq-sdk.

Supports:
- Site-based configuration (host, user, password)
- Custom environment variables per site with runtime interpolation
- Multiple sites per config file
- Default config file (config/settings.yml)
- Environment variable overrides (IQ_ prefix)
- Secrets via ENV or file reference (K8s style)
- Worker ID (auto-derived from hostname or configured)
- Tool/Worker configuration per site
"""

from __future__ import annotations

import json
import os
import re
import socket
from pathlib import Path
from typing import Any

import yaml
from pydantic import BaseModel, ConfigDict, Field
from pydantic_settings import BaseSettings, SettingsConfigDict


def slugify(text: str) -> str:
    """Convert text to URL-safe slug."""
    text = text.lower()
    text = re.sub(r"[^a-z0-9]+", "-", text)
    text = text.strip("-")
    return text


def get_hostname_slug() -> str:
    """Get slugified hostname for worker ID."""
    hostname = socket.gethostname()
    return slugify(hostname)


def interpolate_env(value: str) -> str:
    """
    Interpolate environment variables in a string.

    Supports:
    - ${VAR} - replaced with ENV value, empty if not set
    - ${VAR:-default} - replaced with ENV value or default
    - ${VAR:?error} - replaced with ENV value or raises error

    Args:
        value: String potentially containing ${VAR} patterns

    Returns:
        String with interpolated values
    """

    def replace_var(match: re.Match) -> str:
        var_expr = match.group(1)

        # Check for default value syntax ${VAR:-default}
        if ":-" in var_expr:
            var_name, default = var_expr.split(":-", 1)
            return os.environ.get(var_name, default)

        # Check for required syntax ${VAR:?error}
        if ":?" in var_expr:
            var_name, error_msg = var_expr.split(":?", 1)
            val = os.environ.get(var_name)
            if val is None:
                raise ValueError(f"Required environment variable {var_name}: {error_msg}")
            return val

        # Simple substitution
        return os.environ.get(var_expr, "")

    return re.sub(r"\$\{([^}]+)\}", replace_var, value)


def interpolate_dict(data: dict[str, Any]) -> dict[str, Any]:
    """Recursively interpolate environment variables in dict values."""
    result: dict[str, Any] = {}
    for key, value in data.items():
        if isinstance(value, str):
            result[key] = interpolate_env(value)
        elif isinstance(value, dict):
            result[key] = interpolate_dict(value)
        elif isinstance(value, list):
            result[key] = [interpolate_env(v) if isinstance(v, str) else v for v in value]
        else:
            result[key] = value
    return result


# Default config file path (no fallbacks, single source of truth)
DEFAULT_CONFIG_FILE = "config/settings.yml"


class ToolConfig(BaseModel):
    """
    Configuration for a single tool.

    Supports multiple naming conventions:
    - Simple string: "my_tool" (all versions)
    - With version: "my_tool.1.0" (specific version)
    - With versions list: name="my_tool", versions=["1.0", "1.1"]
    """

    model_config = ConfigDict(extra="allow")

    name: str = Field(..., description="Tool name (base name or with version)")
    versions: list[str] | None = Field(
        default=None,
        description="Specific versions to enable (None = all versions)",
    )
    queue: str | None = Field(default=None, description="Queue name (defaults to tool name)")
    threads: int = Field(default=1, description="Number of threads for this tool")
    enabled: bool = Field(default=True, description="Whether tool is enabled")

    def get_queue(self) -> str:
        """Get queue name, defaulting to tool name."""
        return self.queue or self.name

    def matches_version(self, version: str) -> bool:
        """
        Check if a version matches this tool config.

        Args:
            version: Version string (e.g. "1.0")

        Returns:
            True if version is allowed by this config
        """
        # If no versions specified, all versions are allowed
        if self.versions is None:
            return True

        # Check if version is in the allowed list
        return version in self.versions


class SiteConfig(BaseModel):
    """
    Configuration for a single site.

    A site represents a connection target with:
    - host: Socket.IO server URL
    - api_key: API key for authentication
    - api_secret: API secret (loaded from ENV or secrets file)
    - env: Custom environment variables (interpolated from runtime ENVs)
    - tools: Tools to run on this site with their configuration
    """

    model_config = ConfigDict(extra="ignore")

    # Site identity
    id: str = Field(default="default", description="Unique site identifier")

    # Active state (can be overridden by IQ_SITE_{ID}_ACTIVE env)
    active: bool = Field(default=True, description="Whether this site is active")

    # Connection settings
    host: str = Field(default="http://localhost:3000", description="Server URL")

    # Authentication
    api_key: str = Field(default="", description="API key for authentication")
    api_secret: str = Field(
        default="", description="API secret (prefer ENV: IQ_SITE_{ID}_API_SECRET)"
    )

    # Custom environment variables (can interpolate from runtime ENVs)
    env: dict[str, str] = Field(
        default_factory=dict, description="Custom environment variables with ${VAR} interpolation"
    )

    # Tools configuration for this site
    tools: list[ToolConfig | str] = Field(
        default_factory=list, description="Tools to run on this site"
    )

    # Worker settings for this site (overrides global defaults)
    threads: int | None = Field(default=None, description="Default threads per tool")

    # Connection behavior
    reconnect: bool = Field(default=True, description="Auto-reconnect on disconnect")
    reconnect_attempts: int = Field(default=5, description="Max reconnection attempts")

    def is_active(self) -> bool:
        """
        Check if this site is active, considering ENV override.

        ENV logic:
        - IQ_SITE_{ID}_ACTIVE=0 → deactivates site (overrides config)
        - IQ_SITE_{ID}_ACTIVE=1 → activates site
        - ENV not set → uses config value (self.active)
        """
        site_id_upper = self.id.upper().replace("-", "_")
        env_key = f"IQ_SITE_{site_id_upper}_ACTIVE"
        env_value = os.environ.get(env_key)

        if env_value is not None:
            return env_value.lower() in ("1", "true", "yes", "on")

        return self.active

    def load_api_secret(self) -> str:
        """
        Load API secret from environment or secrets file.

        Priority:
        1. IQ_SITE_{ID}_API_SECRET environment variable
        2. IQ_SITE_{ID}_API_SECRET_FILE with optional _API_SECRET_KEY
        3. Configured api_secret value

        File-based secrets (K8s/Docker style):
        - IQ_SITE_DEFAULT_API_SECRET_FILE=/run/secrets/api_secret
          → File contains secret directly (most common)

        - IQ_SITE_DEFAULT_API_SECRET_FILE=/etc/secrets/creds.json
          IQ_SITE_DEFAULT_API_SECRET_KEY=credentials.api_secret
          → Extract from JSON/YAML using dot-notation key
        """
        site_id_upper = self.id.upper().replace("-", "_")

        # Check direct ENV
        env_key = f"IQ_SITE_{site_id_upper}_API_SECRET"
        env_secret = os.environ.get(env_key)
        if env_secret:
            return env_secret

        # Check file reference (K8s/Docker secrets style)
        file_key = f"IQ_SITE_{site_id_upper}_API_SECRET_FILE"
        secret_file = os.environ.get(file_key)
        if secret_file:
            file_path = Path(secret_file)
            if file_path.exists():
                content = file_path.read_text().strip()

                # Check if we need to extract a key from JSON/YAML
                key_env = f"IQ_SITE_{site_id_upper}_API_SECRET_KEY"
                key_path = os.environ.get(key_env)

                if key_path:
                    return _extract_key_from_content(content, key_path, file_path)

                return content

        # Fall back to configured api_secret
        return self.api_secret

    def get_auth(self) -> dict[str, str]:
        """Get authentication credentials."""
        return {
            "api_key": self.get_api_key(),
            "api_secret": self.load_api_secret(),
        }

    def get_api_key(self) -> str:
        """Load API key from environment or config."""
        site_id_upper = self.id.upper().replace("-", "_")
        env_key = f"IQ_SITE_{site_id_upper}_API_KEY"
        return os.environ.get(env_key, self.api_key)

    def get_host(self) -> str:
        """Load host from environment or config."""
        site_id_upper = self.id.upper().replace("-", "_")
        env_key = f"IQ_SITE_{site_id_upper}_HOST"
        return os.environ.get(env_key, self.host)

    def get_env(self) -> dict[str, str]:
        """Get interpolated environment variables."""
        return interpolate_dict(self.env)

    def get_tools(self) -> list[ToolConfig]:
        """Get normalized tool configurations."""
        result = []
        for tool in self.tools:
            if isinstance(tool, str):
                result.append(ToolConfig(name=tool))
            else:
                result.append(tool)
        return result

    def get_tool_queues(self) -> dict[str, str]:
        """Get mapping of tool names to queue names."""
        return {t.name: t.get_queue() for t in self.get_tools()}

    def discover_tools(self, tools_dir: Path) -> list[tuple[ToolConfig, Any]]:
        """
        Discover available tools for this site.

        Scans the tools directory and filters based on site config.
        If no tools are configured, all local tools are used.

        Supports multiple config formats:
        - No tools: all local tools are used
        - Simple string: "my_tool" (all versions)
        - With version: "my_tool.1.0" (specific version)
        - With versions list: {name: "my_tool", versions: ["1.0", "1.1"]}
        - Disabled: {name: "my_tool", enabled: false}

        Args:
            tools_dir: Path to the tools directory

        Returns:
            List of (ToolConfig, LocalToolInfo) tuples
        """
        from iq_sdk.cli.tool import get_local_tools

        if not tools_dir.exists():
            return []

        local_tools = get_local_tools(tools_dir)
        if not local_tools:
            return []

        configured_tools = self.get_tools()
        result: list[tuple[ToolConfig, Any]] = []

        if not configured_tools:
            # No tools configured - use all local tools
            for base_name, versions in local_tools.items():
                for info in versions:
                    # Create default ToolConfig
                    tool_config = ToolConfig(
                        name=f"{base_name}.{info.version}",
                        enabled=True,
                    )
                    result.append((tool_config, info))
        else:
            # Filter by configured tools
            for tool_config in configured_tools:
                if not tool_config.enabled:
                    continue

                # Match by name (with or without version)
                for base_name, versions in local_tools.items():
                    for info in versions:
                        version_str = str(info.version)
                        full_name = f"{base_name}.{version_str}"

                        # Check if this tool matches the config
                        name_matches = (
                            tool_config.name == full_name  # Exact match with version
                            or tool_config.name == base_name  # Base name match
                        )

                        if name_matches:
                            # Check version filter
                            if tool_config.matches_version(version_str):
                                result.append((tool_config, info))

        return result


class RetryConfig(BaseModel):
    """Configuration for output queue retry behavior."""

    model_config = ConfigDict(extra="ignore")

    max_retries: int = Field(
        default=10, description="Maximum retry attempts before moving to failed queue"
    )
    initial_delay: int = Field(default=10, description="Initial retry delay in seconds")
    max_delay: int = Field(default=3600, description="Maximum retry delay in seconds (1 hour)")
    backoff_multiplier: float = Field(default=2.0, description="Exponential backoff multiplier")


class WorkerConfig(BaseModel):
    """Default configuration for worker processes."""

    model_config = ConfigDict(extra="ignore")

    name: str = Field(default="worker", description="Worker name identifier")
    threads: int = Field(default=1, description="Default threads per tool")
    min_threads: int = Field(default=1, description="Minimum threads for autoscaling")
    max_threads: int = Field(default=16, description="Maximum threads for autoscaling")
    autoscale: bool = Field(default=False, description="Enable autoscaling")
    autoscale_interval: int = Field(default=30, description="Autoscale check interval (seconds)")
    heartbeat_interval: int = Field(default=10, description="Heartbeat interval (seconds)")

    # Retry settings for failed output submissions
    retry: RetryConfig = Field(
        default_factory=RetryConfig, description="Retry settings for failed outputs"
    )


class LoggingConfig(BaseModel):
    """Logging configuration."""

    model_config = ConfigDict(extra="ignore")

    level: str = Field(default="INFO", description="Log level")
    format: str = Field(
        default="%(asctime)s - %(name)s - %(levelname)s - %(message)s", description="Log format"
    )
    file: str | None = Field(default=None, description="Log file path")


class Settings(BaseSettings):
    """
    Main settings for iq-sdk projects.

    Config file structure (config/settings.yml):

        project_name: "my-project"
        worker_id: "my-worker"  # optional, auto-derived from hostname

        # Default worker settings
        worker:
          threads: 2
          autoscale: false

        # Default site (used when no --site specified)
        site:
          id: "default"
          host: "http://localhost:3000"
          api_key: "worker"
          env:
            API_KEY: "${API_KEY:-default-key}"
            DEBUG: "true"
          tools:
            - name: "my-tool"
              queue: "my-queue"
              threads: 2
            - "another-tool"  # uses defaults

        # Additional sites
        sites:
          - id: "production"
            host: "${PROD_HOST:?Production host required}"
            api_key: "prod-worker"
            tools:
              - "my-tool"
    """

    model_config = SettingsConfigDict(
        env_prefix="IQ_",
        env_nested_delimiter="__",
        extra="ignore",
    )

    # Project settings
    project_name: str = Field(default="my-project", description="Project name")
    version: str = Field(default="0.1.0", description="Project version")

    # Worker identification
    worker_id: str | None = Field(
        default=None,
        description="Worker ID for host registration (auto-derived from hostname if not set)",
    )

    # Default site configuration
    site: SiteConfig = Field(default_factory=SiteConfig)

    # Additional sites
    sites: list[SiteConfig] = Field(default_factory=list, description="List of site configurations")

    # Default worker configuration
    worker: WorkerConfig = Field(default_factory=WorkerConfig)

    # Logging
    logging: LoggingConfig = Field(default_factory=LoggingConfig)

    # Tools directory
    tools_dir: str = Field(default="tools", description="Directory containing tools")

    def get_site(self, site_id: str | None = None) -> SiteConfig | None:
        """
        Get a site configuration by ID.

        Args:
            site_id: Site identifier. If None, returns default site.

        Returns:
            SiteConfig or None if not found
        """
        if site_id is None or site_id == "default":
            return self.site

        for s in self.sites:
            if s.id == site_id:
                return s

        # Check if default site matches
        if self.site.id == site_id:
            return self.site

        return None

    def get_sites(self, site_ids: list[str] | None = None) -> list[SiteConfig]:
        """
        Get multiple site configurations (including inactive).

        Args:
            site_ids: List of site IDs. If None, returns all sites.

        Returns:
            List of matching SiteConfig objects
        """
        all_sites = [self.site] + self.sites

        if site_ids is None:
            return all_sites

        return [s for s in all_sites if s.id in site_ids]

    def get_active_sites(self, site_ids: list[str] | None = None) -> list[SiteConfig]:
        """
        Get only active site configurations.

        Considers both config `active` field and ENV override:
        - IQ_SITE_{ID}_ACTIVE=0 deactivates a site
        - IQ_SITE_{ID}_ACTIVE=1 activates a site
        - ENV not set → uses config value

        Args:
            site_ids: List of site IDs to filter. If None, returns all active sites.

        Returns:
            List of active SiteConfig objects
        """
        sites = self.get_sites(site_ids)
        return [s for s in sites if s.is_active()]

    def list_site_ids(self, active_only: bool = False) -> list[str]:
        """
        Get list of all site IDs.

        Args:
            active_only: If True, only return IDs of active sites
        """
        if active_only:
            return [s.id for s in self.get_active_sites()]

        ids = [self.site.id]
        ids.extend(s.id for s in self.sites)
        return ids

    # Internal: project root path (set during load_settings)
    _project_root: Path | None = None

    def get_worker_id(self) -> str:
        """
        Get the worker ID for host registration.

        Priority:
        1. IQ_WORKER_ID environment variable
        2. Configured worker_id value
        3. Auto-derived from hostname (slugified)
        """
        # Check ENV override
        env_worker_id = os.environ.get("IQ_WORKER_ID")
        if env_worker_id:
            return env_worker_id

        # Check configured value
        if self.worker_id:
            return self.worker_id

        # Auto-derive from hostname
        return get_hostname_slug()

    def get_project_root(self) -> Path:
        """
        Get the project root directory.

        Returns the directory where config/settings.yml is located.
        Falls back to cwd if no project root was set during load.
        """
        if self._project_root:
            return self._project_root
        return get_project_root()

    def get_tools_path(self) -> Path:
        """
        Get the absolute path to the tools directory.

        Resolves tools_dir relative to project root.
        """
        root = self.get_project_root()
        tools = Path(self.tools_dir)
        if tools.is_absolute():
            return tools
        return root / tools

    def get_workers_registry_path(self) -> Path:
        """
        Get the path to the workers registry directory.

        Returns .iq-workers/ in project root.
        """
        return self.get_project_root() / ".iq-workers"

    def get_all_tools(self) -> list[tuple[SiteConfig, ToolConfig]]:
        """
        Get all tools across all sites.

        Returns:
            List of (site, tool) tuples
        """
        result = []
        for site in self.get_sites():
            for tool in site.get_tools():
                result.append((site, tool))
        return result


def get_project_root() -> Path:
    """
    Get project root directory.

    Uses active project from registry, or current directory as fallback.
    """
    # Import here to avoid circular import
    from iq_sdk.cli.project import get_active_project

    active = get_active_project()
    if active:
        return active
    return Path.cwd()


def find_config_file(config_file: str | Path | None = None) -> Path | None:
    """
    Find the configuration file to use.

    Paths are resolved relative to IQ_PROJECT_DIR if set,
    otherwise relative to current directory.

    Args:
        config_file: Explicit config file path, or None to use default

    Returns:
        Path to config file, or None if not found
    """
    project_root = get_project_root()

    if config_file:
        path = Path(config_file)
        # If relative path, resolve relative to project root
        if not path.is_absolute():
            path = project_root / path
        if path.exists():
            return path
        return None

    # Check default location (config/settings.yml)
    path = project_root / DEFAULT_CONFIG_FILE
    if path.exists():
        return path

    return None


def load_settings(
    config_file: str | Path | None = None,
    overrides: dict[str, Any] | None = None,
) -> Settings:
    """
    Load settings from YAML file with environment variable overrides.

    Priority (highest to lowest):
    1. Explicit overrides dict
    2. Environment variables (IQ_ prefix)
    3. YAML config file
    4. Default values

    Args:
        config_file: Path to YAML configuration file (config/settings.yml)
        overrides: Dictionary of override values

    Returns:
        Settings instance
    """
    config_data: dict[str, Any] = {}

    # Find and load config file
    found_file = find_config_file(config_file)
    if found_file:
        with open(found_file) as f:
            config_data = yaml.safe_load(f) or {}

    # Apply overrides
    if overrides:
        _deep_merge(config_data, overrides)

    # Parse site config
    if "site" in config_data and isinstance(config_data["site"], dict):
        config_data["site"] = _parse_site_config(config_data["site"])

    # Parse sites list
    if "sites" in config_data and config_data["sites"]:
        config_data["sites"] = [
            _parse_site_config(s) if isinstance(s, dict) else s for s in config_data["sites"]
        ]
    else:
        config_data["sites"] = []

    # Parse worker config
    if "worker" in config_data and isinstance(config_data["worker"], dict):
        config_data["worker"] = WorkerConfig(**config_data["worker"])

    # Parse logging config
    if "logging" in config_data and isinstance(config_data["logging"], dict):
        config_data["logging"] = LoggingConfig(**config_data["logging"])

    # Load sites from environment (IQ_SITES=site1,site2,site3)
    env_sites = _parse_sites_from_env()
    if env_sites:
        if "sites" not in config_data:
            config_data["sites"] = []
        # Merge env sites (env takes precedence for same ID)
        existing_ids = {s.id for s in config_data.get("sites", [])}
        for env_site in env_sites:
            if env_site.id not in existing_ids:
                config_data["sites"].append(env_site)

    settings = Settings(**config_data)

    # Set project root (where config was found, or current project)
    project_root = get_project_root()
    if found_file:
        # If config was found in config/ directory, project root is its parent
        config_parent = found_file.parent
        if config_parent.name == "config":
            project_root = config_parent.parent

    # Use object.__setattr__ to bypass Pydantic's frozen check
    object.__setattr__(settings, "_project_root", project_root)

    return settings


def _extract_key_from_content(content: str, key_path: str, file_path: Path) -> str:
    """
    Extract a value from JSON/YAML content using dot-notation key path.

    Args:
        content: Raw file content (JSON or YAML)
        key_path: Dot-notation path like "database.password" or "credentials.api_key"
        file_path: Original file path (for error messages)

    Returns:
        The extracted string value

    Raises:
        ValueError: If parsing fails or key not found
    """
    # Try to parse as JSON first, then YAML
    data: dict[str, Any] | None = None

    try:
        data = json.loads(content)
    except json.JSONDecodeError:
        try:
            data = yaml.safe_load(content)
        except yaml.YAMLError:
            pass

    if not isinstance(data, dict):
        raise ValueError(f"Cannot parse {file_path} as JSON or YAML for key extraction")

    # Navigate the key path
    keys = key_path.split(".")
    current: Any = data

    for key in keys:
        if isinstance(current, dict) and key in current:
            current = current[key]
        else:
            raise ValueError(f"Key '{key_path}' not found in {file_path} (failed at '{key}')")

    if not isinstance(current, str):
        current = str(current)

    return current


def _parse_site_config(data: dict[str, Any]) -> SiteConfig:
    """Parse a site configuration dict."""
    # Parse tools
    if "tools" in data and data["tools"]:
        data["tools"] = [ToolConfig(**t) if isinstance(t, dict) else t for t in data["tools"]]
    return SiteConfig(**data)


def _parse_sites_from_env() -> list[SiteConfig]:
    """
    Parse sites from environment variables using ID-based format.

    ID-based format (preferred):
        # Activate a site defined only via ENV
        IQ_SITE_PRODUCTION_ACTIVE=1
        IQ_SITE_PRODUCTION_HOST=http://prod:3000
        IQ_SITE_PRODUCTION_API_KEY=my-worker

        IQ_SITE_STAGING_ACTIVE=1
        IQ_SITE_STAGING_HOST=http://stage:3000

    The site ID is derived from the ENV variable name:
        IQ_SITE_{SITE_ID}_* where SITE_ID is uppercased with underscores

    Only sites with IQ_SITE_{ID}_ACTIVE=1 are created from ENV.
    Sites from config file are merged separately.
    """
    sites: list[SiteConfig] = []
    found_site_ids: set[str] = set()

    # Scan all ENV vars for IQ_SITE_*_ACTIVE=1 pattern
    active_pattern = re.compile(r"^IQ_SITE_([A-Z0-9_]+)_ACTIVE$")

    for key, value in os.environ.items():
        match = active_pattern.match(key)
        if match and value.lower() in ("1", "true", "yes", "on"):
            site_id_upper = match.group(1)
            # Convert to lowercase with hyphens for standard ID format
            site_id = site_id_upper.lower().replace("_", "-")

            if site_id in found_site_ids:
                continue
            found_site_ids.add(site_id)

            # Get site properties from ENV
            prefix = f"IQ_SITE_{site_id_upper}_"
            host = os.environ.get(f"{prefix}HOST", "http://localhost:3000")
            api_key = os.environ.get(f"{prefix}API_KEY", "")

            sites.append(
                SiteConfig(
                    id=site_id,
                    active=True,
                    host=host,
                    api_key=api_key,
                )
            )

    return sites


def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> None:
    """Deep merge override dict into base dict."""
    for key, value in override.items():
        if key in base and isinstance(base[key], dict) and isinstance(value, dict):
            _deep_merge(base[key], value)
        else:
            base[key] = value


def generate_env_template(settings: Settings) -> str:
    """
    Generate .env template content with site API credentials.

    Args:
        settings: Settings instance

    Returns:
        .env file content as string
    """
    lines = [
        "# iq-sdk Environment Variables",
        "# Generated template - fill in your secrets",
        "",
        "# Worker ID (optional, defaults to hostname)",
        "# IQ_WORKER_ID=my-worker",
        "",
        "# Global settings",
        "# IQ_WORKER__THREADS=4",
        "# IQ_LOGGING__LEVEL=INFO",
        "",
        "# Site credentials",
        "# Use either API_SECRET (direct) or API_SECRET_FILE (K8s secrets)",
        "",
    ]

    all_sites = [settings.site] + settings.sites
    for site in all_sites:
        site_id_upper = site.id.upper().replace("-", "_")
        lines.append(f"# Site: {site.id}")
        lines.append(f"# IQ_SITE_{site_id_upper}_HOST={site.host}")
        lines.append(f"# IQ_SITE_{site_id_upper}_API_KEY={site.api_key}")
        lines.append(f"IQ_SITE_{site_id_upper}_API_SECRET=")
        lines.append(f"# IQ_SITE_{site_id_upper}_API_SECRET_FILE=/run/secrets/{site.id}-secret")
        lines.append("")

    return "\n".join(lines)


def save_env_template(settings: Settings, output_path: str | Path = ".env.template") -> Path:
    """
    Save .env template file.

    Args:
        settings: Settings instance
        output_path: Output file path

    Returns:
        Path to created file
    """
    path = Path(output_path)
    content = generate_env_template(settings)
    path.write_text(content)
    return path
