"""OK ITHAKI Argos reference-language service (R10F8).

English is the reference language.  A non-English pair is translated as
source -> English -> target.  Model installation is explicit/manual from
Lexicon 1 and is serialized to protect the server.
"""
from __future__ import annotations

import os
import subprocess
import sys
import threading
import time
from pathlib import Path
from typing import Any

from local_dictionary_service import log_unknown_terms, norm_lang, translate_with_local_db

REFERENCE = "en"
SUPPORTED = {
    "el","en","bg","hr","cs","da","nl","et","fi","fr","de","hu","ga","it","lv","lt","mt","pl","pt","ro","sk","sl","es","sv",
    "sq","bs","ca","eu","gl","is","lb","mk","no","sr","tr","uk","cy","be"
}
_INSTALL_LOCK = threading.Lock()
_LAST_INSTALL: dict[str, float] = {}


def _root() -> Path:
    configured = os.getenv("ITHAKI_ARGOS_PACKAGE_DIR", "").strip()
    if configured:
        return Path(configured).expanduser().resolve()
    if Path("/opt/ithaki").exists():
        return Path("/opt/ithaki/shared/argos_packages")
    return Path(__file__).resolve().parent / "data" / "argos_packages"


def _prepare_env() -> Path:
    root = _root()
    root.mkdir(parents=True, exist_ok=True)
    os.environ.setdefault("ARGOS_PACKAGES_DIR", str(root))
    os.environ.setdefault("ARGOS_PACKAGE_DIR", str(root))
    return root


def _imports():
    _prepare_env()
    import argostranslate.package as package  # type: ignore
    import argostranslate.translate as translate  # type: ignore
    return package, translate


def _runtime_available() -> bool:
    try:
        _imports()
        return True
    except Exception:
        return False


def install_runtime() -> dict[str, Any]:
    if _runtime_available():
        return {"ok": True, "installed": True, "note": "argos_runtime_already_installed"}
    cmd = [sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "argostranslate>=1.9,<2", "langdetect>=1.0.9,<2"]
    try:
        cp = subprocess.run(cmd, capture_output=True, text=True, timeout=900, check=False)
        ok = cp.returncode == 0 and _runtime_available()
        return {"ok": ok, "installed": ok, "returncode": cp.returncode, "note": (cp.stdout + "\n" + cp.stderr)[-3000:]}
    except Exception as exc:
        return {"ok": False, "installed": False, "note": str(exc)}


def _installed_routes() -> set[tuple[str, str]]:
    _, tr = _imports()
    routes: set[tuple[str, str]] = set()
    langs = tr.get_installed_languages()
    for src in langs:
        for dst in langs:
            if src.code == dst.code:
                continue
            try:
                src.get_translation(dst)
                routes.add((norm_lang(src.code), norm_lang(dst.code)))
            except Exception:
                pass
    return routes


def _catalog(refresh: bool = False):
    package, _ = _imports()
    if refresh:
        package.update_package_index()
    return package.get_available_packages()


def argos_status(language: str = "") -> dict[str, Any]:
    root = _prepare_env()
    if not _runtime_available():
        return {"ok": True, "installed": False, "ready": False, "reference_language": REFERENCE, "package_dir": str(root), "routes": [], "language": norm_lang(language), "pair_ready": False, "note": "Argos runtime is not installed. Install it manually from Lexicon 1."}
    try:
        routes = sorted([list(x) for x in _installed_routes()])
        code = norm_lang(language)
        pair_ready = code in ("", REFERENCE) or ((REFERENCE, code) in {tuple(x) for x in routes} and (code, REFERENCE) in {tuple(x) for x in routes})
        return {"ok": True, "installed": True, "ready": bool(routes), "reference_language": REFERENCE, "package_dir": str(root), "routes": routes, "language": code, "pair_ready": pair_ready}
    except Exception as exc:
        return {"ok": False, "installed": True, "ready": False, "reference_language": REFERENCE, "package_dir": str(root), "note": str(exc)}


def _find_package(packages, src: str, dst: str):
    for p in packages:
        if norm_lang(getattr(p, "from_code", "")) == src and norm_lang(getattr(p, "to_code", "")) == dst:
            return p
    return None


def install_reference_pair(language: str, install_runtime_if_missing: bool = True) -> dict[str, Any]:
    code = norm_lang(language)
    if code not in SUPPORTED:
        return {"ok": False, "note": "unsupported_language", "language": code}
    if code == REFERENCE:
        return {"ok": True, "language": code, "pair_ready": True, "installed_routes": []}
    if not _runtime_available():
        if not install_runtime_if_missing:
            return {"ok": False, "note": "argos_runtime_missing", "language": code}
        runtime = install_runtime()
        if not runtime.get("ok"):
            return {"ok": False, "note": "argos_runtime_install_failed", "runtime": runtime, "language": code}
    now = time.time()
    if now - _LAST_INSTALL.get(code, 0) < 60:
        return {"ok": False, "note": "install_cooldown", "language": code, "status": argos_status(code)}
    if not _INSTALL_LOCK.acquire(blocking=False):
        return {"ok": False, "note": "another_model_install_is_running", "language": code}
    try:
        _LAST_INSTALL[code] = now
        routes = _installed_routes()
        needed = [(REFERENCE, code), (code, REFERENCE)]
        missing = [pair for pair in needed if pair not in routes]
        if not missing:
            return {"ok": True, "language": code, "pair_ready": True, "installed_routes": [], "status": argos_status(code)}
        packages = _catalog(refresh=True)
        installed: list[list[str]] = []
        unavailable: list[list[str]] = []
        package, _ = _imports()
        for src, dst in missing:
            candidate = _find_package(packages, src, dst)
            if candidate is None:
                unavailable.append([src, dst])
                continue
            path = candidate.download()
            package.install_from_path(path)
            installed.append([src, dst])
        status = argos_status(code)
        ok = bool(status.get("pair_ready"))
        return {"ok": ok, "language": code, "pair_ready": ok, "installed_routes": installed, "unavailable_routes": unavailable, "status": status, "note": "ok" if ok else "One or both Argos reference routes are unavailable."}
    except Exception as exc:
        return {"ok": False, "language": code, "note": str(exc), "status": argos_status(code)}
    finally:
        _INSTALL_LOCK.release()


def _language_obj(code: str):
    _, tr = _imports()
    for lang in tr.get_installed_languages():
        if norm_lang(lang.code) == code:
            return lang
    return None


def _direct_argos(text: str, src: str, dst: str) -> str | None:
    if src == dst:
        return text
    src_obj, dst_obj = _language_obj(src), _language_obj(dst)
    if not src_obj or not dst_obj:
        return None
    try:
        return str(src_obj.get_translation(dst_obj).translate(text) or "").strip() or None
    except Exception:
        return None


def _leg(text: str, src: str, dst: str) -> tuple[str, str]:
    local = translate_with_local_db(text, src, dst)
    local_out = str(local.get("translated", text))
    if local_out.strip() and local_out != text:
        return local_out, str(local.get("provider", "ithaki-lexicon"))
    argos = _direct_argos(text, src, dst)
    if argos and argos != text:
        # Learned words can correct/complete Argos output too.
        corrected = translate_with_local_db(argos, src, dst)
        return str(corrected.get("translated", argos)), "argos-offline-reference"
    return text, "original"


def translate_reference(text: str, src_lang: str, dst_lang: str, log_unknown: bool = True) -> dict[str, Any]:
    text = str(text or "")
    src, dst = norm_lang(src_lang), norm_lang(dst_lang)
    if src == "auto":
        # The UI normally knows the language, but messages may arrive without metadata.
        try:
            from langdetect import detect  # type: ignore
            src = norm_lang(detect(text))
        except Exception:
            if any("\u0370" <= c <= "\u03ff" for c in text):
                src = "el"
            elif any("\u0400" <= c <= "\u04ff" for c in text):
                src = "uk"
            else:
                src = "en"
    if src not in SUPPORTED or dst not in SUPPORTED:
        return {"ok": False, "translated": text, "original": text, "source_lang": src, "target_lang": dst, "provider": "unsupported-language"}
    if not text.strip() or src == dst:
        return {"ok": True, "translated": text, "original": text, "source_lang": src, "target_lang": dst, "provider": "same-or-empty", "reference_language": REFERENCE, "route": [src]}
    route = [src, dst] if REFERENCE in (src, dst) else [src, REFERENCE, dst]
    current, providers = text, []
    for a, b in zip(route, route[1:]):
        current, provider = _leg(current, a, b)
        providers.append(provider)
        if current == text and provider == "original":
            break
    unknown = {"added": 0}
    if log_unknown:
        unknown = log_unknown_terms(text, src, dst, current, notes="english-reference-r10f8")
    ready = current.strip() != text.strip() or all(x != "original" for x in providers)
    return {"ok": True, "translated": current, "original": text, "source_lang": src, "target_lang": dst, "provider": "+".join(providers), "reference_language": REFERENCE, "route": route, "translation_ready": ready, "unknown_terms_added": int(unknown.get("added", 0) or 0), "argos_status": argos_status(dst if dst != REFERENCE else src)}


def translate_offline_argos(text: str, src_lang: str = "auto", dst_lang: str = "en") -> dict[str, Any]:
    return translate_reference(text, src_lang, dst_lang, log_unknown=True)
