"""Configuration loading utilities."""

import json
import os
import secrets
from pathlib import Path
from typing import Any

from flowly.config.schema import Config


def get_config_path() -> Path:
    """Get the default configuration file path."""
    return Path.home() / ".flowly" / "config.json"


def get_data_dir() -> Path:
    """Get the flowly data directory."""
    from flowly.utils.helpers import get_data_path
    return get_data_path()


def load_config(config_path: Path | None = None) -> Config:
    """
    Load configuration from file or create default.
    
    Args:
        config_path: Optional path to config file. Uses default if not provided.
    
    Returns:
        Loaded configuration object.
    """
    path = config_path or get_config_path()
    
    if path.exists():
        try:
            with open(path, encoding="utf-8") as f:
                data = json.load(f)
            return Config.model_validate(convert_keys(data))
        except (json.JSONDecodeError, ValueError) as e:
            print(f"Warning: Failed to load config from {path}: {e}")
            print("Using default configuration.")
    
    return Config()


def save_config(config: Config, config_path: Path | None = None) -> None:
    """
    Save configuration to file using read-modify-write.

    Reads the existing JSON first and deep-merges the Pydantic-known fields on
    top of it.  Unknown / extra fields that were added manually are preserved.
    """
    path = config_path or get_config_path()
    path.parent.mkdir(parents=True, exist_ok=True)

    # 1. Load whatever is already on disk (preserves unknown fields)
    existing: dict[str, Any] = {}
    if path.exists():
        try:
            with open(path, encoding="utf-8") as f:
                existing = json.load(f)
        except (json.JSONDecodeError, OSError):
            existing = {}

    # 2. Dump only the fields Pydantic knows about, convert to camelCase
    known = convert_to_camel(config.model_dump())

    # 3. Deep-merge: existing is the base, known fields win
    merged = _deep_merge(existing, known)

    # 4. Atomic write
    tmp_path = path.with_suffix(f".tmp.{secrets.token_hex(4)}")
    try:
        tmp_path.write_text(json.dumps(merged, indent=4), encoding="utf-8")
        os.replace(str(tmp_path), str(path))
    except Exception:
        tmp_path.unlink(missing_ok=True)
        raise

    # Restrict permissions: owner read/write only (config contains API keys)
    try:
        os.chmod(path, 0o600)
    except OSError:
        pass


def _deep_merge(base: dict, override: dict) -> dict:
    """Recursively merge *override* into *base*.  override wins on conflicts."""
    result = dict(base)
    for key, val in override.items():
        if key in result and isinstance(result[key], dict) and isinstance(val, dict):
            result[key] = _deep_merge(result[key], val)
        else:
            result[key] = val
    return result


def convert_keys(data: Any) -> Any:
    """Convert camelCase keys to snake_case for Pydantic."""
    if isinstance(data, dict):
        return {camel_to_snake(k): convert_keys(v) for k, v in data.items()}
    if isinstance(data, list):
        return [convert_keys(item) for item in data]
    return data


def convert_to_camel(data: Any) -> Any:
    """Convert snake_case keys to camelCase."""
    if isinstance(data, dict):
        return {snake_to_camel(k): convert_to_camel(v) for k, v in data.items()}
    if isinstance(data, list):
        return [convert_to_camel(item) for item in data]
    return data


def camel_to_snake(name: str) -> str:
    """Convert camelCase to snake_case."""
    result = []
    for i, char in enumerate(name):
        if char.isupper() and i > 0:
            result.append("_")
        result.append(char.lower())
    return "".join(result)


def snake_to_camel(name: str) -> str:
    """Convert snake_case to camelCase."""
    components = name.split("_")
    return components[0] + "".join(x.title() for x in components[1:])
