import json
import re
import threading
import typing as t
import warnings
from hashlib import sha256
from pathlib import Path

from trezorlib import cosi, device, models
from trezorlib._internal import translations
from trezorlib.debuglink import TrezorClientDebugLink as Client

from . import common

HERE = Path(__file__).resolve().parent
ROOT = HERE.parent

TRANSLATIONS_DIR = ROOT / "core" / "translations"
FONTS_DIR = TRANSLATIONS_DIR / "fonts"
ORDER_FILE = TRANSLATIONS_DIR / "order.json"

LANGUAGES = [file.stem for file in TRANSLATIONS_DIR.glob("??.json")]

_CURRENT_TRANSLATION = threading.local()


def prepare_blob(
    lang_or_def: translations.JsonDef | Path | str,
    model: models.TrezorModel,
    version: translations.VersionTuple | tuple[int, int, int] | None = None,
) -> translations.TranslationsBlob:
    order = translations.order_from_json(json.loads(ORDER_FILE.read_text()))
    if isinstance(lang_or_def, str):
        lang_or_def = get_lang_json(lang_or_def)
    if isinstance(lang_or_def, Path):
        lang_or_def = t.cast(translations.JsonDef, json.loads(lang_or_def.read_text()))

    # generate raw blob
    if version is None:
        version = translations.version_from_json(lang_or_def["header"]["version"])
    elif len(version) == 3:
        # version coming from client object does not have build item
        version = *version, 0
    return translations.blob_from_defs(lang_or_def, order, model, version, FONTS_DIR)


def sign_blob(blob: translations.TranslationsBlob) -> bytes:
    # build 0-item Merkle proof
    digest = sha256(b"\x00" + blob.header_bytes).digest()
    signature = cosi.sign_with_privkeys(digest, common.PRIVATE_KEYS_DEV)
    blob.proof = translations.Proof(
        merkle_proof=[],
        sigmask=0b111,
        signature=signature,
    )
    return blob.build()


def build_and_sign_blob(
    lang_or_def: translations.JsonDef | Path | str,
    client: Client,
) -> bytes:
    blob = prepare_blob(lang_or_def, client.model, client.version)
    return sign_blob(blob)


def set_language(client: Client, lang: str):
    if lang.startswith("en"):
        language_data = b""
    else:
        language_data = build_and_sign_blob(lang, client)
    with client:
        device.change_language(client, language_data)  # type: ignore
    _CURRENT_TRANSLATION.TR = TRANSLATIONS[lang]


def get_lang_json(lang: str) -> translations.JsonDef:
    assert lang in LANGUAGES
    lang_json = json.loads((TRANSLATIONS_DIR / f"{lang}.json").read_text())
    if (fonts_safe3 := lang_json.get("fonts", {}).get("##Safe3")) is not None:
        lang_json["fonts"]["T2B1"] = fonts_safe3
        lang_json["fonts"]["T3B1"] = fonts_safe3
    return lang_json


class Translation:
    FORMAT_STR_RE = re.compile(r"\\{\d+\\}")

    def __init__(self, lang: str) -> None:
        self.lang = lang
        self.lang_json = get_lang_json(lang)

    @property
    def translations(self) -> dict[str, str]:
        return self.lang_json["translations"]

    def _translate_raw(self, key: str, _stacklevel: int = 0) -> str:
        tr = self.translations.get(key)
        if tr is not None:
            return tr
        if self.lang != "en":
            warnings.warn(
                f"Translation key '{key}' not found in '{self.lang}' translation file",
                stacklevel=_stacklevel + 2,
            )
            return TRANSLATIONS["en"]._translate_raw(key)
        raise KeyError(key)

    def translate(self, key: str, _stacklevel: int = 0) -> str:
        tr = self._translate_raw(key, _stacklevel=_stacklevel + 1)
        return tr.replace("\xa0", " ").strip()

    def as_regexp(self, key: str, _stacklevel: int = 0) -> re.Pattern:
        tr = self.translate(key, _stacklevel=_stacklevel + 1)
        re_safe = re.escape(tr)
        return re.compile(self.FORMAT_STR_RE.sub(r".*?", re_safe))


TRANSLATIONS = {lang: Translation(lang) for lang in LANGUAGES}
_CURRENT_TRANSLATION.TR = TRANSLATIONS["en"]


def translate(key: str, _stacklevel: int = 0) -> str:
    return _CURRENT_TRANSLATION.TR.translate(key, _stacklevel=_stacklevel + 1)


def regexp(key: str) -> re.Pattern:
    return _CURRENT_TRANSLATION.TR.as_regexp(key, _stacklevel=1)


def __getattr__(key: str) -> str:
    try:
        return translate(key, _stacklevel=1)
    except KeyError as e:
        raise AttributeError(f"Translation key '{key}' not found") from e