"""
Kamus alias nama pabrik farmasi Indonesia.

Fitur:
- Kamus bawaan (built-in) dengan singkatan umum, tidak bisa dihapus via bot.
- Kamus kustom: user dapat menambah alias baru lewat perintah bot.
  Data kustom disimpan di data/manufacturer_aliases.json supaya persisten.
- Metode expand() memperluas setiap token dalam query yang cocok dengan alias.

Contoh:
    dict = ManufacturerDict()
    dict.expand("asam mefenamat hj")
    # -> "asam mefenamat hexpharm jaya"
"""
from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Iterator

logger = logging.getLogger(__name__)

# Lokasi penyimpanan alias kustom (di luar package agar mudah di-mount/backup)
_CUSTOM_PATH: Path = Path(__file__).parent.parent / "data" / "manufacturer_aliases.json"

# ---------------------------------------------------------------------------
# Kamus bawaan — daftar singkatan umum pabrik farmasi Indonesia
# ---------------------------------------------------------------------------
_BUILTIN: dict[str, str] = {
    # Hexpharm Jaya
    "hj": "hexpharm jaya",
    "hexpharm": "hexpharm jaya",
    # Dexa Medica
    "dexa": "dexa medica",
    # Kimia Farma
    "kimfa": "kimia farma",
    "kf": "kimia farma",
    # Kalbe Farma group
    "kalbe": "kalbe farma",
    "dankos": "dankos farma",
    # Sanbe Farma
    "sanbe": "sanbe farma",
    # Tempo Scan Pacific
    "tempo": "tempo scan pacific",
    "tspc": "tempo scan pacific",
    # Darya Varia
    "darya": "darya varia",
    "dv": "darya varia",
    # Soho Global Health
    "soho": "soho global health",
    # Pratapa Raya (Prafa)
    "prafa": "pratapa raya",
    "pratapa": "pratapa raya",
    # Indofarma
    "indofarma": "indofarma",
    # Phapros
    "phapros": "phapros",
    # Pyridam Farma
    "pyridam": "pyridam farma",
    "pyra": "pyridam farma",
    # Bernofarm
    "berno": "bernofarm",
    "bernofarm": "bernofarm",
    # Novell Pharmaceutical
    "novell": "novell pharmaceutical",
    # Otto Pharmaceutical
    "otto": "otto pharmaceutical",
    # Meprofarm
    "meprof": "meprofarm",
    "meprofarm": "meprofarm",
    # Ferron Par
    "ferron": "ferron par",
    # Interbat
    "interbat": "interbat",
    # Pharos
    "pharos": "pharos",
    # Combiphar
    "combiphar": "combiphar",
    # Guardian Farma
    "guardian": "guardian farma",
    # Lapi Laboratories
    "lapi": "lapi laboratories",
    # Mahakam Beta Farma
    "mahakam": "mahakam beta farma",
    # Yarindo Farmatama
    "yarindo": "yarindo farmatama",
    # Mutiara Mukti Farma
    "mutiara": "mutiara mukti farma",
    # Galenium Pharmasia
    "galenium": "galenium pharmasia",
    # Nufarindo
    "nufarindo": "nufarindo",
    # Ethica Industri Farmasi
    "ethica": "ethica industri farmasi",
    # Caprifarmindo Laboratories
    "capri": "caprifarmindo laboratories",
    "caprifarmindo": "caprifarmindo laboratories",
    # Actavis
    "actavis": "actavis",
    # Widatra Bhakti
    "widatra": "widatra bhakti",
    # Gracia Pharmindo
    "gracia": "gracia pharmindo",
    # Merck Indonesia
    "merck": "merck",
    # Pfizer Indonesia
    "pfizer": "pfizer",
    # Novartis Indonesia
    "novartis": "novartis",
    # Sanofi
    "sanofi": "sanofi",
    # Bayer Indonesia
    "bayer": "bayer",
    # Abbott Indonesia
    "abbott": "abbott",
    # AstraZeneca
    "az": "astrazeneca",
    "astrazeneca": "astrazeneca",
    # MSD (Merck Sharp & Dohme)
    "msd": "merck sharp dohme",
    # Fahrenheit
    "fahrenheit": "fahrenheit",
    # Alpharma
    "alpharma": "alpharma",
}


class ManufacturerDict:
    """
    Kamus alias nama pabrik farmasi.
    Thread-safe untuk read; write memakai lock sederhana.
    """

    def __init__(self) -> None:
        self._aliases: dict[str, str] = {**_BUILTIN}
        self._load_custom()

    # ------------------------------------------------------------------
    # Lookup & expand
    # ------------------------------------------------------------------

    def expand(self, text: str) -> str:
        """
        Perluas setiap token dalam teks yang cocok dengan alias pabrik.
        Pencocokan case-insensitive.

        Contoh:
            "asam mefenamat hj 500"  →  "asam mefenamat hexpharm jaya 500"
        """
        tokens = text.split()
        result = []
        for token in tokens:
            expanded = self._aliases.get(token.lower())
            result.append(expanded if expanded is not None else token)
        return " ".join(result)

    def lookup(self, abbr: str) -> str | None:
        """Kembalikan nama lengkap dari singkatan, atau None jika tidak ada."""
        return self._aliases.get(abbr.lower().strip())

    def all_aliases(self) -> dict[str, str]:
        """Kembalikan seluruh alias (built-in + kustom) yang aktif."""
        return dict(self._aliases)

    def custom_aliases(self) -> dict[str, str]:
        """Kembalikan hanya alias kustom yang ditambahkan via bot."""
        return {k: v for k, v in self._aliases.items() if k not in _BUILTIN}

    def iter_by_full_name(self, partial: str) -> Iterator[tuple[str, str]]:
        """Iterasi alias yang nilai (nama lengkap) mengandung partial string."""
        partial_lower = partial.lower()
        for abbr, full in self._aliases.items():
            if partial_lower in full.lower():
                yield abbr, full

    def find_abbreviation(self, full_name: str) -> str | None:
        """
        Cari singkatan terpendek untuk nama lengkap pabrik.
        Contoh: 'hexpharm jaya' -> 'hj', 'dexa medica' -> 'dexa'.
        Mengembalikan singkatan terpendek (bukan token tunggal) jika ada.
        """
        full_lower = full_name.lower().strip()
        candidates = [(k, len(k)) for k, v in self._aliases.items() if v.lower() == full_lower]
        if not candidates:
            # Coba partial match: jika full_name mengandung nama lengkap
            for abbr, full in self._aliases.items():
                if full_lower in full.lower() or full.lower() in full_lower:
                    candidates.append((abbr, len(abbr)))
        if not candidates:
            return None
        # Kembalikan singkatan terpendek (upper case)
        candidates.sort(key=lambda x: x[1])
        return candidates[0][0].upper()

    # ------------------------------------------------------------------
    # Mutasi
    # ------------------------------------------------------------------

    def add(self, abbr: str, full_name: str) -> bool:
        """
        Tambah atau timpa alias kustom.
        Return True jika berhasil, False jika abbr adalah built-in
        yang tidak boleh ditimpa lewat bot.
        """
        abbr = abbr.lower().strip()
        full_name = full_name.strip()
        if not abbr or not full_name:
            return False
        # Izinkan override built-in melalui kustom — apoteker lebih tahu
        self._aliases[abbr] = full_name
        self._save_custom()
        logger.info("ManufacturerDict: alias ditambah '%s' -> '%s'", abbr, full_name)
        return True

    # ------------------------------------------------------------------
    # Persistence
    # ------------------------------------------------------------------

    def _load_custom(self) -> None:
        if not _CUSTOM_PATH.exists():
            return
        try:
            data: dict[str, str] = json.loads(_CUSTOM_PATH.read_text(encoding="utf-8"))
            self._aliases.update(data)
            logger.debug("ManufacturerDict: %d alias kustom dimuat", len(data))
        except Exception:
            logger.exception("ManufacturerDict: gagal load alias kustom dari %s", _CUSTOM_PATH)

    def _save_custom(self) -> None:
        try:
            _CUSTOM_PATH.parent.mkdir(parents=True, exist_ok=True)
            custom = self.custom_aliases()
            _CUSTOM_PATH.write_text(
                json.dumps(custom, ensure_ascii=False, indent=2, sort_keys=True),
                encoding="utf-8",
            )
        except Exception:
            logger.exception("ManufacturerDict: gagal simpan alias kustom ke %s", _CUSTOM_PATH)
