"""Transduck — AI-native translation tool."""

import hashlib
import logging
import threading

from transduck.config import TransduckConfig, load_config
from transduck.storage import TranslationStore
from transduck.validation import validate_translation, extract_placeholders

logger = logging.getLogger("transduck")


class _State:
    def __init__(self):
        self.config: TransduckConfig | None = None
        self.store: TranslationStore | None = None
        self.target_lang: str | None = None
        self._locks: dict[tuple, threading.Lock] = {}
        self._locks_lock = threading.Lock()


_state = _State()


def _hash(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()


def _interpolate(text: str, vars: dict | None) -> str:
    if not vars:
        return text
    result = text
    for key, value in vars.items():
        result = result.replace(f"{{{key}}}", str(value))
    return result


def initialize(config: TransduckConfig | None = None):
    if config is None:
        config = load_config()
    _state.config = config
    _state.store = TranslationStore(config.storage_path)
    _state.store.initialize()


def set_language(lang: str):
    _state.target_lang = lang.upper()


def ait(source_text: str, context: str | None = None, vars: dict | None = None) -> str:
    if _state.config is None or _state.store is None:
        raise RuntimeError("transduck not initialized. Call transduck.initialize() first.")
    if _state.target_lang is None:
        raise RuntimeError("Target language not set. Call transduck.set_language() first.")

    cfg = _state.config

    if _state.target_lang == cfg.source_lang:
        return _interpolate(source_text, vars)

    project_context_hash = _hash(cfg.project_context)
    string_context_hash = _hash(context or "")

    # Cache lookup
    cached = _state.store.lookup(
        source_text=source_text,
        source_lang=cfg.source_lang,
        target_lang=_state.target_lang,
        project_context_hash=project_context_hash,
        string_context_hash=string_context_hash,
    )
    if cached is not None:
        return _interpolate(cached, vars)

    # Read-only mode: skip backend, return source text
    if cfg.read_only:
        logger.debug("Read-only mode, cache miss for: %s", source_text)
        return _interpolate(source_text, vars)

    # In-process dedup lock
    lock_key = (source_text, cfg.source_lang, _state.target_lang, project_context_hash, string_context_hash)
    with _state._locks_lock:
        if lock_key not in _state._locks:
            _state._locks[lock_key] = threading.Lock()
        lock = _state._locks[lock_key]

    with lock:
        # Double-check after acquiring lock
        cached = _state.store.lookup(
            source_text=source_text,
            source_lang=cfg.source_lang,
            target_lang=_state.target_lang,
            project_context_hash=project_context_hash,
            string_context_hash=string_context_hash,
        )
        if cached is not None:
            return _interpolate(cached, vars)

        # Call backend (lazy import — openai is heavy)
        from transduck import backend
        try:
            translated = backend.translate(
                source_text=source_text,
                source_lang=cfg.source_lang,
                target_lang=_state.target_lang,
                project_context=cfg.project_context,
                string_context=context,
                config=cfg,
            )
        except Exception:
            logger.warning("Backend failed for: %s", source_text, exc_info=True)
            return _interpolate(source_text, vars)

        # Validate
        if not validate_translation(source_text, translated):
            logger.warning("Validation failed for: %s -> %s", source_text, translated)
            _state.store.insert(
                source_text=source_text,
                source_lang=cfg.source_lang,
                target_lang=_state.target_lang,
                project_context_hash=project_context_hash,
                string_context_hash=string_context_hash,
                translated_text=translated,
                model=cfg.backend_model,
                status="failed",
            )
            return _interpolate(source_text, vars)

        # Store and return
        _state.store.insert(
            source_text=source_text,
            source_lang=cfg.source_lang,
            target_lang=_state.target_lang,
            project_context_hash=project_context_hash,
            string_context_hash=string_context_hash,
            translated_text=translated,
            model=cfg.backend_model,
        )
        return _interpolate(translated, vars)


def ait_plural(
    one: str,
    other: str,
    count: int | float,
    context: str | None = None,
    vars: dict | None = None,
) -> str:
    # Lazy import babel — only when pluralization is actually used
    from transduck.plural import get_plural_category, get_plural_categories

    if _state.config is None or _state.store is None:
        raise RuntimeError("transduck not initialized. Call transduck.initialize() first.")
    if _state.target_lang is None:
        raise RuntimeError("Target language not set. Call transduck.set_language() first.")

    cfg = _state.config

    # Build vars with count
    if vars is None:
        vars = {"count": count}
    elif "count" not in vars:
        vars = {**vars, "count": count}

    # Same language, 2-form language: select directly from provided forms
    if _state.target_lang == cfg.source_lang:
        categories = get_plural_categories(cfg.source_lang)
        if categories <= {"one", "other"}:
            category = get_plural_category(cfg.source_lang, count)
            form = one if category == "one" else other
            return _interpolate(form, vars)

    # Build cache key
    source_key = one + "\x00" + other
    project_context_hash = _hash(cfg.project_context)
    string_context_hash = _hash(context or "")

    # Cache lookup
    cached = _state.store.lookup_plural(
        source_text=source_key,
        source_lang=cfg.source_lang,
        target_lang=_state.target_lang,
        project_context_hash=project_context_hash,
        string_context_hash=string_context_hash,
    )

    category = get_plural_category(_state.target_lang, count)
    if category in cached:
        return _interpolate(cached[category], vars)

    # Read-only mode: skip backend, fall back to source forms
    if cfg.read_only:
        logger.debug("Read-only mode, cache miss for plural: %s / %s", one, other)
        fallback_category = get_plural_category(cfg.source_lang, count)
        fallback = one if fallback_category == "one" else other
        return _interpolate(fallback, vars)

    # Cache miss — call backend (lazy import — openai is heavy)
    from transduck import backend
    try:
        forms = backend.translate_plural(
            one=one,
            other=other,
            source_lang=cfg.source_lang,
            target_lang=_state.target_lang,
            project_context=cfg.project_context,
            string_context=context,
            config=cfg,
        )
    except Exception:
        logger.warning("Backend failed for plural: %s / %s", one, other, exc_info=True)
        fallback_category = get_plural_category(cfg.source_lang, count)
        fallback = one if fallback_category == "one" else other
        return _interpolate(fallback, vars)

    # Validate and store each form
    valid_categories = {"zero", "one", "two", "few", "many", "other"}
    source_placeholders = extract_placeholders(one) | extract_placeholders(other)

    if not isinstance(forms, dict) or "other" not in forms:
        logger.warning("Invalid plural response for: %s / %s", one, other)
        fallback_category = get_plural_category(cfg.source_lang, count)
        fallback = one if fallback_category == "one" else other
        return _interpolate(fallback, vars)

    for cat, text in forms.items():
        if cat not in valid_categories or not text:
            continue
        # Validate placeholders
        translated_placeholders = extract_placeholders(text)
        status = "translated" if source_placeholders.issubset(translated_placeholders) else "failed"
        _state.store.insert_plural(
            source_text=source_key,
            source_lang=cfg.source_lang,
            target_lang=_state.target_lang,
            project_context_hash=project_context_hash,
            string_context_hash=string_context_hash,
            plural_category=cat,
            translated_text=text,
            model=cfg.backend_model,
            status=status,
        )

    # Select the right form
    if category in forms:
        text = forms[category]
        tp = extract_placeholders(text)
        if source_placeholders.issubset(tp):
            return _interpolate(text, vars)

    # Fallback
    fallback_category = get_plural_category(cfg.source_lang, count)
    fallback = one if fallback_category == "one" else other
    return _interpolate(fallback, vars)
