from __future__ import annotations

import base64
import json
import os
import re
from pathlib import Path
from typing import Any, Dict, Optional

import yaml

from actor.authentication.exceptions import MissingConfigError

# ---------------------------------------------------------------------------
# Sensitive key detection (shared across CLI, config cache, error sanitization)
# ---------------------------------------------------------------------------

SENSITIVE_KEY_PATTERNS = (
    "password",
    "key",
    "token",
    "secret",
    "credential",
    "private",
    "jwt",
    "auth",
)


def is_sensitive_key(key: str) -> bool:
    """Return True if *key* looks like it holds a secret value."""
    lower = key.lower()
    return any(pat in lower for pat in SENSITIVE_KEY_PATTERNS)


# ---------------------------------------------------------------------------
# Error message sanitization (for Redis publish / CLI output)
# ---------------------------------------------------------------------------

_URL_CRED_RE = re.compile(r"://[^@/]*@")


def sanitize_error_message(exc: BaseException) -> str:
    """Return a string safe for Redis / logs: no embedded credentials, bounded length."""
    msg = str(exc)
    # Strip credentials embedded in URLs (e.g. postgresql://user:pass@host)
    msg = _URL_CRED_RE.sub("://<redacted>@", msg)
    # Remove any substring that looks like a raw secret value
    # (key=value or key="value" patterns where key is sensitive)
    msg = re.sub(
        r"(?i)(" + "|".join(SENSITIVE_KEY_PATTERNS) + r")['\"]?\s*[:=]\s*['\"]?[^\s,;'\"]+",
        r"\1=<redacted>",
        msg,
    )
    if len(msg) > _MAX_ERROR_LEN:
        msg = "...(truncated)" + msg[-_MAX_ERROR_LEN:]
    return msg


# ---------------------------------------------------------------------------
# Redis password resolution (shared across CLI commands)
# ---------------------------------------------------------------------------

def resolve_redis_password(redis_cfg: Dict[str, Any]) -> Optional[str]:
    """Resolve Redis password from env var or direct config value."""
    if isinstance(redis_cfg.get("password_env"), str):
        return os.getenv(redis_cfg["password_env"], "")
    return redis_cfg.get("password")


# ---------------------------------------------------------------------------
# Token / config helpers
# ---------------------------------------------------------------------------

def decode_connection_token(token: str) -> Dict[str, Any]:
    """Decode a base64url-encoded JSON token."""
    if not token:
        raise ValueError("Connection token is empty")
    padded = token + "=" * (-len(token) % 4)
    try:
        raw = base64.urlsafe_b64decode(padded.encode()).decode()
        data = json.loads(raw)
    except Exception as exc:
        raise ValueError(f"Invalid connection token: {exc}") from exc
    return data


def load_config(config_path: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
    """Load configuration from YAML or connection token."""
    if config_path:
        cfg = yaml.safe_load(Path(config_path).read_text())
        return cfg or {}
    token = token or os.getenv("AGENT_KEY") or os.getenv("AGENT_CONNECTION_TOKEN")
    if token:
        decoded = decode_connection_token(token)
        return decoded
    raise MissingConfigError("No config provided: set AGENT_KEY or AGENT_CONNECTION_TOKEN, or use --config")


def redact(value: str) -> str:
    if value is None:
        return ""
    if len(value) <= 4:
        return "***"
    return f"{value[:2]}***{value[-2:]}"


def generate_config_template(with_examples: bool = False) -> str:
    data = {
        "redis": {
            "url": "rediss://your-redis-host:6379" if with_examples else "",
            "password_env": "REDIS_PASSWORD",
        },
        "agent": {
            "key": "agent_your_key_here" if with_examples else "",
            "instance_name": None,
        },
        "secrets": {
            "provider": "env",
            "vault": {
                "url": "https://vault.company.com" if with_examples else "",
                "token_path": "/var/run/secrets/vault-token" if with_examples else "",
            },
            "file": {"path": "/var/secrets" if with_examples else ""},
            "k8s": {"namespace": "default", "default_secret": "agent-secrets"} if with_examples else {},
        },
        "logging": {
            "level": "INFO",
            "format": "json",
            "log_dir": "/var/log/agent" if with_examples else None,
        },
    }
    return yaml.safe_dump(data, sort_keys=False)


def get_log_file_path(log_dir: Optional[str]) -> Path:
    base = Path(log_dir) if log_dir else Path.cwd() / "logs"
    return base / "agent.log"


# ---------------------------------------------------------------------------
# CLIContext — shared boilerplate for CLI commands that need Redis
# ---------------------------------------------------------------------------

class CLIContext:
    """Encapsulates load_config + redis setup shared by validate, health, debug, etc."""

    def __init__(self, config_path: Optional[str] = None) -> None:
        self.cfg = load_config(config_path=config_path)
        self.redis_cfg: Dict[str, Any] = self.cfg.get("redis", {})
        self.agent_cfg: Dict[str, Any] = self.cfg.get("agent", {})
        self.redis_url: str = self.redis_cfg.get("url", "")
        self.password: Optional[str] = resolve_redis_password(self.redis_cfg)
        self.agent_key: str = self.agent_cfg.get("key", "")

    def require_redis_and_key(self) -> None:
        if not self.redis_url or not self.agent_key:
            raise RuntimeError("redis url and agent key are required")

    def build_redis_client(self):
        """Build a redis-py client from the resolved config."""
        try:
            import redis as _redis  # type: ignore
        except ImportError as exc:  # pragma: no cover
            raise RuntimeError("redis library is required for queue operations") from exc
        return _redis.Redis.from_url(self.redis_url, password=self.password, decode_responses=True)
_MAX_ERROR_LEN = 1024
