"""
ConfigStore: reads and writes ~/.skilark/config.yaml.

The config file is created on first setup and contains:
  user_id  - UUID assigned by the API on registration
  topics   - list of skill topics the user cares about
  api_url  - base URL for the Skilark API (default: production)
"""

import os
import stat
from pathlib import Path
from urllib.parse import urlparse

import yaml

DEFAULT_API_URL = "https://api.skilark.com"

ALLOWED_API_HOSTS = {"api.skilark.com"}
_DEV_HOSTS = {"localhost", "127.0.0.1"}


def validate_api_url(url: str) -> str:
    """Validate that api_url uses HTTPS and points to a known host.

    Allows HTTP for localhost/127.0.0.1 during development.
    Raises ValueError if the URL is invalid or points to an untrusted host.
    """
    parsed = urlparse(url)
    if parsed.hostname in _DEV_HOSTS:
        if parsed.scheme not in ("http", "https"):
            raise ValueError(f"api_url must use http or https, got: {url!r}")
        return url
    if parsed.scheme != "https":
        raise ValueError(f"api_url must use HTTPS, got: {url!r}")
    if parsed.hostname not in ALLOWED_API_HOSTS:
        raise ValueError(f"api_url host not allowed: {parsed.hostname!r}")
    return url


class ConfigStore:
    """Manages the on-disk configuration for the Skilark CLI.

    Uses a configurable config_dir so that tests can point at a tmp_path
    instead of touching the real ~/.skilark directory.
    """

    def __init__(self, config_dir: Path | None = None):
        if config_dir is not None:
            # Caller-supplied path is trusted (programmatic / test use).
            self.config_dir = Path(config_dir)
        else:
            env_val = os.environ.get("SKILARK_CONFIG_DIR")
            if env_val is not None:
                # Environment variable is untrusted — validate it resolves within
                # the user's home directory to prevent path-traversal attacks.
                resolved = Path(env_val).resolve()
                home = Path.home().resolve()
                try:
                    resolved.relative_to(home)
                except ValueError:
                    raise ValueError(
                        f"SKILARK_CONFIG_DIR must be inside your home directory, got: {env_val!r}"
                    )
                self.config_dir = resolved
            else:
                self.config_dir = Path.home() / ".skilark"
        self.config_file = self.config_dir / "config.yaml"

    def is_first_run(self) -> bool:
        """Return True if no config file exists yet (i.e. not yet set up)."""
        return not self.config_file.exists()

    def save(
        self,
        user_id: str,
        topics: list[str],
        api_url: str = DEFAULT_API_URL,
    ) -> None:
        """Write a fresh config file, creating the directory if needed.

        Overwrites any existing config entirely — intended for initial setup
        or a full reset.
        """
        validate_api_url(api_url)
        self.config_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
        data = {"user_id": user_id, "topics": topics, "api_url": api_url}
        self._write_config(data)

    def load(self) -> dict:
        """Load and return the config as a plain dict.

        Raises FileNotFoundError if is_first_run() is True (file not yet
        created).  Callers should check is_first_run() before calling load().
        Raises ValueError if api_url is invalid or points to an untrusted host.
        """
        config = yaml.safe_load(self.config_file.read_text())
        if "api_url" in config:
            validate_api_url(config["api_url"])
        return config

    def update_topics(self, topics: list[str]) -> None:
        """Replace the stored topic list without touching other fields.

        Raises FileNotFoundError if config has not been created yet.
        """
        config = self.load()
        config["topics"] = topics
        self._write_config(config)

    def update_coding_language(self, language: str | None) -> None:
        """Set the preferred coding problem language (python/java/None).

        Pass None to clear the preference. Raises FileNotFoundError if
        config has not been created yet.
        """
        config = self.load()
        config["coding_language"] = language
        self._write_config(config)

    def update_profile(
        self,
        current_role: str | None = None,
        primary_language: str | None = None,
        career_direction: str | None = None,
    ) -> None:
        """Update profile fields in the config file."""
        config = self.load()
        if current_role is not None:
            config["current_role"] = current_role
        if primary_language is not None:
            config["primary_language"] = primary_language
        if career_direction is not None:
            config["career_direction"] = career_direction
        self._write_config(config)

    def update_theme(self, theme: str) -> None:
        """Set the TUI theme preference (any Textual theme name).

        Raises FileNotFoundError if config has not been created yet.
        """
        config = self.load()
        config["theme"] = theme
        self._write_config(config)

    def has_profile(self) -> bool:
        """Return True if the config file has profile fields set."""
        if self.is_first_run():
            return False
        config = self.load()
        return bool(config.get("current_role"))

    def _write_config(self, data: dict) -> None:
        """Write config data to disk with owner-only permissions (0600)."""
        content = yaml.dump(data, default_flow_style=False)
        fd = os.open(self.config_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
        try:
            os.write(fd, content.encode())
        finally:
            os.close(fd)
