"""
coco runtime paths and user-level configuration.

Agent directories store replaceable personalities and public memory.
The coco home stores resources that belong to the software user, such as
API credentials and user-agent relationship memory.
"""

import os
import secrets
import shutil
import sys
from datetime import datetime
from pathlib import Path

import yaml


def project_root() -> Path:
    if getattr(sys, "frozen", False):
        return Path(sys.executable).resolve().parent
    return Path(__file__).resolve().parent.parent


def resource_root() -> Path:
    core = os.environ.get("coco_core_dir")
    if core:
        return Path(core).expanduser().resolve()
    if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
        return Path(sys._MEIPASS)
    return project_root()


def coco_home() -> Path:
    home = os.environ.get("coco_home")
    path = Path(home).expanduser() if home else project_root() / ".coco"
    path.mkdir(parents=True, exist_ok=True)
    return path


def bridge_token_path() -> Path:
    return coco_home() / "bridge-token"


def load_or_create_bridge_token() -> str:
    token = os.environ.get("coco_bridge_token", "").strip()
    if token:
        return token

    path = bridge_token_path()
    if path.exists():
        try:
            token = path.read_text(encoding="utf-8").strip()
            if token:
                return token
        except OSError:
            pass

    token = secrets.token_urlsafe(32)
    path.write_text(token, encoding="utf-8")
    try:
        os.chmod(path, 0o600)
    except OSError:
        pass
    return token


def _safe_account_id(account_id: str = "local") -> str:
    account_id = str(account_id or "local").strip()
    if not account_id:
        return "local"
    return "".join(ch for ch in account_id if ch.isalnum() or ch in ("-", "_")) or "local"


def safe_account_id(account_id: str = "local") -> str:
    return _safe_account_id(account_id)


def _safe_resource_id(resource_id: str, fallback: str = "default") -> str:
    resource_id = str(resource_id or fallback).strip()
    safe = "".join(ch for ch in resource_id if ch.isalnum() or ch in ("-", "_"))
    return safe or fallback


def users_dir() -> Path:
    path = coco_home() / "users"
    path.mkdir(parents=True, exist_ok=True)
    return path


def user_dir(account_id: str = "local") -> Path:
    path = users_dir() / _safe_account_id(account_id)
    path.mkdir(parents=True, exist_ok=True)
    return path


def relationships_dir(account_id: str = "local") -> Path:
    path = user_dir(account_id) / "relationships"
    path.mkdir(parents=True, exist_ok=True)
    return path


def relationship_dir(agent_id: str, account_id: str = "local") -> Path:
    path = relationships_dir(account_id) / _safe_resource_id(agent_id)
    path.mkdir(parents=True, exist_ok=True)
    return path


def relationship_memory_dir(agent_id: str, account_id: str = "local") -> Path:
    path = relationship_dir(agent_id, account_id) / "memory"
    for sub in ["episodic", "semantic", "working", "procedural", "summaries"]:
        (path / sub).mkdir(parents=True, exist_ok=True)
    return path


def migrate_legacy_agent_memory(agent_id: str, legacy_memory_dir: Path, account_id: str = "local") -> Path:
    """Copy old agent-local private memory into the user-agent relationship store."""
    target = relationship_memory_dir(agent_id, account_id)
    legacy_memory_dir = Path(legacy_memory_dir)
    if not legacy_memory_dir.exists():
        return target

    has_target_data = any(path.is_file() for path in target.rglob("*") if path.name != "manifest.json")
    if not has_target_data:
        shutil.copytree(legacy_memory_dir, target, dirs_exist_ok=True)
        for sub in ["episodic", "semantic", "working", "procedural", "summaries"]:
            (target / sub).mkdir(parents=True, exist_ok=True)

    _archive_legacy_agent_memory(agent_id, legacy_memory_dir, account_id)
    return target


def _archive_legacy_agent_memory(agent_id: str, legacy_memory_dir: Path, account_id: str = "local") -> None:
    """Move the old private memory folder out of the shareable agent directory."""
    if not legacy_memory_dir.exists():
        return

    try:
        if legacy_memory_dir.resolve() == relationship_memory_dir(agent_id, account_id).resolve():
            return
    except OSError:
        return

    relation_dir = relationship_dir(agent_id, account_id)
    archive = relation_dir / "legacy_agent_memory"
    if archive.exists():
        stamp = datetime.now().strftime("%Y%m%d%H%M%S")
        archive = relation_dir / f"legacy_agent_memory_{stamp}"
    try:
        shutil.move(str(legacy_memory_dir), str(archive))
    except OSError:
        return


def profile_path(account_id: str = "local") -> Path:
    return user_dir(account_id) / "profile.yaml"


def models_path(account_id: str = "local") -> Path:
    return user_dir(account_id) / "models.yaml"


def _legacy_models_path() -> Path:
    return coco_home() / "models.yaml"


def normalize_api_base(api_base: str) -> str:
    value = str(api_base or "").strip().rstrip("/")
    for suffix in ("/chat/completions",):
        if value.endswith(suffix):
            value = value[: -len(suffix)].rstrip("/")
    return value


def session_path() -> Path:
    return coco_home() / "session.yaml"


def load_auth_session() -> dict:
    path = session_path()
    if path.exists():
        with open(path, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
    else:
        data = {}

    account_id = _safe_account_id(
        data.get("account_id")
        or data.get("user_id")
        or data.get("id")
        or "local"
    )
    return {
        "account_id": account_id,
        "email": str(data.get("email") or ""),
        "display_name": str(data.get("display_name") or ""),
        "token": str(data.get("token") or data.get("access_token") or ""),
        "refresh_token": str(data.get("refresh_token") or ""),
        "expires_at": int(float(data.get("expires_at") or 0)),
        "authenticated": bool(data.get("authenticated") or data.get("token") or data.get("access_token")),
    }


def save_auth_session(data: dict) -> dict:
    session = {
        "account_id": _safe_account_id(
            data.get("account_id")
            or data.get("user_id")
            or data.get("id")
            or data.get("email")
            or "local"
        ),
        "email": str(data.get("email") or ""),
        "display_name": str(data.get("display_name") or ""),
        "token": str(data.get("token") or data.get("access_token") or ""),
        "refresh_token": str(data.get("refresh_token") or ""),
        "expires_at": int(float(data.get("expires_at") or 0)),
        "authenticated": bool(data.get("authenticated", True)),
    }

    path = session_path()
    with open(path, "w", encoding="utf-8") as f:
        yaml.dump(session, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
    return load_auth_session()


def clear_auth_session() -> dict:
    session_path().unlink(missing_ok=True)
    return load_auth_session()


def current_account_id() -> str:
    return load_auth_session().get("account_id") or "local"


def load_user_profile(account_id: str = "local") -> dict:
    path = profile_path(account_id)
    if path.exists():
        with open(path, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
    else:
        data = {}
    return {
        "account_id": _safe_account_id(account_id),
        "display_name": str(data.get("display_name", "")),
        "notes": str(data.get("notes", "")),
        "preferences": data.get("preferences") if isinstance(data.get("preferences"), dict) else {},
    }


def save_user_profile(data: dict, account_id: str = "local") -> dict:
    current = load_user_profile(account_id)
    current["display_name"] = str(data.get("display_name", current["display_name"])).strip()
    current["notes"] = str(data.get("notes", current["notes"])).strip()
    if isinstance(data.get("preferences"), dict):
        current["preferences"] = data["preferences"]

    path = profile_path(account_id)
    with open(path, "w", encoding="utf-8") as f:
        yaml.dump(current, f, allow_unicode=True, default_flow_style=False)
    return current


def load_user_models(account_id: str = "local") -> dict:
    path = models_path(account_id)
    legacy_path = _legacy_models_path()
    has_user_file = path.exists()
    if path.exists():
        with open(path, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
    else:
        data = {}
    if not has_user_file and legacy_path.exists():
        with open(legacy_path, "r", encoding="utf-8") as f:
            legacy = yaml.safe_load(f) or {}
    else:
        legacy = {}
    source = data if has_user_file else legacy

    def _model_id(item: dict) -> str:
        return str(item.get("id") or item.get("model") or item.get("name") or "").strip()

    merged_models: list[dict] = []
    seen: set[str] = set()
    models = source.get("models") if isinstance(source.get("models"), list) else []
    for item in models:
        if not isinstance(item, dict):
            continue
        model_id = _model_id(item)
        if not model_id or model_id in seen:
            continue
        merged_models.append(item)
        seen.add(model_id)

    active_model_config = (
        source.get("active_model_config")
        if isinstance(source.get("active_model_config"), dict)
        else {}
    )
    active_model = str(source.get("active_model") or "").strip()
    if not active_model and isinstance(active_model_config, dict):
        active_model = str(active_model_config.get("id") or "").strip()
    merged = {
        "account_id": _safe_account_id(account_id),
        "active_model": active_model,
        "active_model_config": active_model_config,
        "models": merged_models,
    }

    if merged != data:
        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            yaml.dump(merged, f, allow_unicode=True, default_flow_style=False, sort_keys=False)

    return {
        "account_id": _safe_account_id(account_id),
        "active_model": active_model,
        "active_model_config": active_model_config,
        "models": [item for item in merged_models if isinstance(item, dict)],
    }


def save_user_models(data: dict, account_id: str = "local") -> dict:
    current = {
        "account_id": _safe_account_id(account_id),
        "active_model": str(data.get("active_model") or ""),
        "models": data.get("models") if isinstance(data.get("models"), list) else [],
    }
    if isinstance(data.get("active_model_config"), dict) and data.get("active_model_config"):
        current["active_model_config"] = data.get("active_model_config")

    path = models_path(account_id)
    with open(path, "w", encoding="utf-8") as f:
        yaml.dump(current, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
    return current


def active_user_model(account_id: str = "local", fallback_model: str = "gpt-4o") -> dict:
    """Return the selected user-level model config with safe fallbacks."""
    config = load_user_models(account_id)
    models = [item for item in config.get("models", []) if isinstance(item, dict)]
    active = str(config.get("active_model") or "").strip()

    selected = None
    if active:
        selected = next((item for item in models if str(item.get("id") or "") == active), None)
        active_model_config = config.get("active_model_config") if isinstance(config.get("active_model_config"), dict) else {}
        if selected is None and str(active_model_config.get("id") or "") == active:
            selected = active_model_config
    if selected is None and models:
        selected = models[0]
    configured = selected is not None
    if selected is None:
        selected = {}

    model_name = str(selected.get("model") or selected.get("name") or fallback_model or "gpt-4o").strip() or "gpt-4o"
    api_base = normalize_api_base(selected.get("api_base") or "https://api.openai.com/v1")
    api_key = str(selected.get("api_key") or "").strip()
    source = str(selected.get("source") or "local").strip() or "local"
    credit_unit = str(
        selected.get("credit_unit")
        or selected.get("model_credit_unit")
        or selected.get("price")
        or selected.get("credits")
        or ""
    ).strip()

    return {
        "id": str(selected.get("id") or model_name).strip() or model_name,
        "name": str(selected.get("name") or model_name).strip() or model_name,
        "model": model_name,
        "api_base": api_base or "https://api.openai.com/v1",
        "api_key": api_key,
        "source": source,
        "credit_unit": credit_unit,
        "configured": configured,
    }


def global_config_path() -> Path:
    return coco_home() / "config.yaml"


def load_global_config() -> dict:
    path = global_config_path()
    if not path.exists():
        return {}
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f) or {}


def auth_config() -> dict:
    config = load_global_config()
    auth = config.get("auth") if isinstance(config.get("auth"), dict) else {}
    server_url = (
        os.environ.get("coco_auth_server_url", "")
        or str(auth.get("server_url") or auth.get("base_url") or config.get("auth_server_url") or "https://coco.mssay.com")
    ).strip().rstrip("/")
    return {
        "server_url": server_url,
        "login_path": str(auth.get("login_path") or "/api/auth/login"),
        "register_path": str(auth.get("register_path") or "/api/auth/register"),
        "me_path": str(auth.get("me_path") or "/api/auth/me"),
        "password_path": str(auth.get("password_path") or "/api/auth/password"),
        "credit_balance_path": str(auth.get("credit_balance_path") or "/api/credit/balance"),
        "credit_consume_path": str(auth.get("credit_consume_path") or "/api/credit/consume"),
        "cloud_models_path": str(auth.get("cloud_models_path") or "/api/model/models"),
        "legacy_cloud_models_path": str(auth.get("legacy_cloud_models_path") or "/api/admin/models"),
        "pay_create_path": str(auth.get("pay_create_path") or "/api/pay/create"),
        "pay_status_path": str(auth.get("pay_status_path") or "/api/pay/status"),
        "pay_recharge_path": str(auth.get("pay_recharge_path") or "/recharge.php"),
    }


def global_api_key() -> str:
    config = load_global_config()
    return (
        os.environ.get("coco_api_key", "")
        or os.environ.get("OPENAI_API_KEY", "")
        or config.get("api_key", "")
    )


def tencent_asr_config() -> dict:
    """Return user-level Tencent Cloud ASR credentials and defaults."""
    config = load_global_config()
    tencent = config.get("tencent") if isinstance(config.get("tencent"), dict) else {}
    asr = config.get("tencent_asr") if isinstance(config.get("tencent_asr"), dict) else {}
    nested_asr = tencent.get("asr") if isinstance(tencent.get("asr"), dict) else {}

    return {
        "secret_id": (
            os.environ.get("TENCENTCLOUD_SECRET_ID", "")
            or os.environ.get("TENCENT_SECRET_ID", "")
            or asr.get("secret_id", "")
            or tencent.get("secret_id", "")
        ),
        "secret_key": (
            os.environ.get("TENCENTCLOUD_SECRET_KEY", "")
            or os.environ.get("TENCENT_SECRET_KEY", "")
            or asr.get("secret_key", "")
            or tencent.get("secret_key", "")
        ),
        "region": (
            os.environ.get("TENCENTCLOUD_REGION", "")
            or os.environ.get("TENCENT_REGION", "")
            or asr.get("region", "")
            or nested_asr.get("region", "")
            or tencent.get("region", "")
        ),
        "engine_model_type": (
            asr.get("engine_model_type", "")
            or nested_asr.get("engine_model_type", "")
        ),
    }
