"""
Credential Store — Secure .env file writer.

Security guarantees:
  - .env written with 0600 permissions (owner-only on Unix)
  - .gitignore verified to include .env
  - Full keys NEVER printed, logged, or included in exceptions
  - Symlink attacks prevented (refuse to write to symlinks)
  - Atomic writes via tempfile + os.replace
  - Future roadmap: OS keychain integration (macOS/Windows/Linux)
"""

import os
import stat
import logging
import tempfile
from pathlib import Path
from typing import Dict, Optional

logger = logging.getLogger(__name__)


def _find_project_root(start: Optional[Path] = None) -> Path:
    """Walk up from start dir to find the project root (has pyproject.toml or .git)."""
    current = start or Path.cwd()
    for parent in [current, *current.parents]:
        if (parent / "pyproject.toml").exists() or (parent / ".git").exists():
            return parent
    return current


def _build_env_content(merged: Dict[str, str]) -> str:
    """Build the .env file content string from merged vars."""
    merged = merged.copy()  # Avoid mutating caller's dict
    lines = [
        "# QWED Environment Configuration",
        "# Generated by: qwed init",
        "# WARNING: Contains secrets. NEVER commit this file.",
        "",
    ]

    if "ACTIVE_PROVIDER" in merged:
        lines.append("# Active LLM Provider")
        lines.append(f"ACTIVE_PROVIDER={merged.pop('ACTIVE_PROVIDER')}")
        lines.append("")

    for key, value in sorted(merged.items()):
        lines.append(f"{key}={value}")

    lines.append("")  # trailing newline
    return "\n".join(lines)


def _read_existing_env(env_path: Path) -> Dict[str, str]:
    """Read existing .env content, preserving other vars."""
    existing_vars: Dict[str, str] = {}
    if env_path.exists():
        with open(env_path, "r", encoding="utf-8") as f:
            for line in f:
                stripped = line.strip()
                if stripped and not stripped.startswith("#") and "=" in stripped:
                    key, _, value = stripped.partition("=")
                    existing_vars[key.strip()] = value.strip()
    return existing_vars


def _write_secure(env_path: Path, content: str) -> None:
    """Write content to env_path securely (atomic + restrictive permissions)."""
    parent_dir = str(env_path.parent)

    if hasattr(os, "O_NOFOLLOW"):
        # Unix: atomic write via temp file with 0600 + os.replace
        fd, tmp_path = tempfile.mkstemp(dir=parent_dir, prefix=".env.", suffix=".tmp")
        
        try:
            os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR)  # 0600
        except Exception:
            try:
                os.close(fd)
            except OSError as close_err:
                logger.debug("Failed to close temp file descriptor %s: %s", fd, close_err)
            try:
                os.unlink(tmp_path)
            except OSError as cleanup_err:
                logger.debug("Failed to clean up temp file %s: %s", tmp_path, cleanup_err)
            raise
            
        try:
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                f.write(content)
                f.flush()
                os.fsync(f.fileno())
            os.replace(tmp_path, str(env_path))
            logger.info(".env written securely with 0600 permissions")
        except Exception:
            # os.fdopen context manager handled closing the fd for us
            try:
                os.unlink(tmp_path)
            except OSError as cleanup_err:
                logger.debug("Failed to clean up temp file %s: %s", tmp_path, cleanup_err)
            raise
    else:
        # Windows: direct write (no symlink risk, no O_NOFOLLOW)
        with open(env_path, "w", encoding="utf-8") as f:
            f.write(content)
        logger.info(".env written (Windows — set permissions manually if needed)")


def write_env_file(
    env_vars: Dict[str, str],
    project_root: Optional[Path] = None,
    active_provider: Optional[str] = None,
) -> Path:
    """
    Write or update .env file with provider credentials.

    Args:
        env_vars: Dict of VAR_NAME -> value to write
        project_root: Project root directory (auto-detected if None)
        active_provider: Provider slug to set as ACTIVE_PROVIDER

    Returns:
        Path to the written .env file
    """
    root = project_root or _find_project_root()
    env_path = root / ".env"

    # Security: refuse symlinks and non-regular files
    if env_path.is_symlink():
        raise ValueError(f"Refusing to write symlinked .env: {env_path}")
    if env_path.exists() and not env_path.is_file():
        raise ValueError(f"Refusing to write non-regular .env path: {env_path}")

    existing_vars = _read_existing_env(env_path)

    # Merge: new vars override existing
    merged = {**existing_vars, **env_vars}
    if active_provider:
        merged["ACTIVE_PROVIDER"] = active_provider

    content = _build_env_content(merged)
    _write_secure(env_path, content)

    return env_path


def verify_gitignore(project_root: Optional[Path] = None) -> bool:
    """
    Check that .gitignore includes .env.

    Returns True if .env is properly gitignored.
    """
    root = project_root or _find_project_root()
    gitignore_path = root / ".gitignore"

    if not gitignore_path.exists():
        return False

    with open(gitignore_path, "r", encoding="utf-8") as f:
        for line in f:
            stripped = line.strip()
            if stripped in {".env", "*.env", ".env*", "**/.env"}:
                return True

    return False


def add_env_to_gitignore(project_root: Optional[Path] = None) -> bool:
    """Add .env to .gitignore if not already present."""
    root = project_root or _find_project_root()
    gitignore_path = root / ".gitignore"

    if verify_gitignore(root):
        return True  # Already there

    try:
        with open(gitignore_path, "a", encoding="utf-8") as f:
            f.write("\n# Environment variables (contains secrets)\n.env\n")
        return True
    except OSError as e:
        logger.error("Could not update .gitignore: %s", e)
        return False
