"""Configuration, secure token storage, and CLI settings resolution.

Settings precedence (highest to lowest):
  CLI flags  →  env vars  →  config file  →  stored credentials  →  defaults

No dotenv, no import-time env mutation.
"""

from __future__ import annotations

import fcntl
import json
import os
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import yaml
from cryptography.fernet import Fernet

_PROD_BASE = "https://mcp.mcpbundles.com"
_DEV_BASE = "http://localhost:8000"

if os.getenv("ENVIRONMENT") == "development":
    CONFIG_DIR = Path.home() / ".mcpbundles-dev"
else:
    CONFIG_DIR = Path.home() / ".mcpbundles"

TOKEN_FILE = CONFIG_DIR / "token.json"
KEY_FILE = CONFIG_DIR / ".key"
STATUS_FILE = CONFIG_DIR / "status.json"
CONFIG_FILE = CONFIG_DIR / "config.yaml"


# ---------------------------------------------------------------------------
# YAML config file (persistent user preferences)
# ---------------------------------------------------------------------------

_CONFIG_DEFAULTS: dict[str, Any] = {
    "output": "auto",
    "server": _PROD_BASE,
    "completion_ttl": 60,
}


def load_config() -> dict[str, Any]:
    if CONFIG_FILE.exists():
        try:
            return yaml.safe_load(CONFIG_FILE.read_text()) or {}
        except Exception:
            return {}
    return {}


def save_config(cfg: dict[str, Any]) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    CONFIG_FILE.write_text(yaml.dump(cfg, default_flow_style=False))


def config_with_defaults() -> dict[str, Any]:
    return {**_CONFIG_DEFAULTS, **load_config()}


# ---------------------------------------------------------------------------
# Encrypted token storage
# ---------------------------------------------------------------------------

def get_or_create_key() -> bytes:
    CONFIG_DIR.mkdir(exist_ok=True)
    if not KEY_FILE.exists():
        key = Fernet.generate_key()
        KEY_FILE.write_bytes(key)
        KEY_FILE.chmod(0o600)
    return KEY_FILE.read_bytes()


def save_token(token_data: dict[str, Any]) -> None:
    CONFIG_DIR.mkdir(exist_ok=True)
    key = get_or_create_key()
    cipher = Fernet(key)
    encrypted = cipher.encrypt(json.dumps(token_data).encode())

    lock_file = CONFIG_DIR / ".token.lock"
    max_retries = 5
    for attempt in range(max_retries):
        try:
            with open(lock_file, "w") as lock:
                fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
                try:
                    TOKEN_FILE.write_bytes(encrypted)
                    TOKEN_FILE.chmod(0o600)
                finally:
                    fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
            break
        except (IOError, OSError):
            if attempt < max_retries - 1:
                time.sleep(0.1 * (attempt + 1))
            else:
                TOKEN_FILE.write_bytes(encrypted)
                TOKEN_FILE.chmod(0o600)


def load_token() -> dict[str, Any] | None:
    if not TOKEN_FILE.exists():
        return None

    lock_file = CONFIG_DIR / ".token.lock"
    max_retries = 5
    for attempt in range(max_retries):
        try:
            with open(lock_file, "w") as lock:
                fcntl.flock(lock.fileno(), fcntl.LOCK_SH | fcntl.LOCK_NB)
                try:
                    key = get_or_create_key()
                    cipher = Fernet(key)
                    decrypted = cipher.decrypt(TOKEN_FILE.read_bytes())
                    return json.loads(decrypted)
                finally:
                    fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
        except (IOError, OSError):
            if attempt < max_retries - 1:
                time.sleep(0.1 * (attempt + 1))
            else:
                try:
                    key = get_or_create_key()
                    cipher = Fernet(key)
                    decrypted = cipher.decrypt(TOKEN_FILE.read_bytes())
                    return json.loads(decrypted)
                except Exception:
                    return None
        except Exception:
            return None
    return None


def save_status(status_data: dict[str, Any]) -> None:
    CONFIG_DIR.mkdir(exist_ok=True)
    with open(STATUS_FILE, "w") as f:
        json.dump(status_data, f)


def delete_token() -> None:
    TOKEN_FILE.unlink(missing_ok=True)


# ---------------------------------------------------------------------------
# CLISettings — single resolved config object for the session
# ---------------------------------------------------------------------------

class AuthError(Exception):
    pass


@dataclass(frozen=True)
class CLISettings:
    """Immutable, fully-resolved settings for a CLI invocation.

    Precedence: CLI flags → env vars → config file → defaults.
    """

    base_url: str
    config_dir: Path
    output_format: str

    @classmethod
    def resolve(cls, *, url_override: str | None = None) -> CLISettings:
        file_cfg = load_config()

        base_url = (
            url_override
            or os.getenv("MCPBUNDLES_BASE_URL")
            or file_cfg.get("server")
            or (_DEV_BASE if os.getenv("ENVIRONMENT") == "development" else _PROD_BASE)
        )

        output_format = (
            os.getenv("MCPBUNDLES_OUTPUT")
            or file_cfg.get("output", "auto")
        )

        return cls(
            base_url=base_url.rstrip("/"),
            config_dir=CONFIG_DIR,
            output_format=output_format,
        )

    def require_auth(self) -> str:
        """Return an auth token or raise AuthError."""
        env_key = os.getenv("MCPBUNDLES_API_KEY")
        if env_key:
            return env_key
        token_data = load_token()
        if token_data:
            access_token = token_data.get("access_token")
            if access_token:
                return access_token
        raise AuthError("Not authenticated. Run 'mcpbundles login' first.")

    @property
    def is_local(self) -> bool:
        return self.base_url.startswith("http://localhost")

    @property
    def oauth_base_url(self) -> str:
        """Derive OAuth endpoint base from the MCP base URL."""
        if self.is_local:
            return "http://localhost:8000"
        return "https://api.mcpbundles.com"

