#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Chip ID (TID / identidade do chip) — FastChecker II.
Usado pelo FastSurance (embutido no .exe) e pelo script ``Chip_ID.py`` na raiz do projeto.
"""

import argparse
import ctypes
import os
import sys
import threading
import time
import tkinter as tk
from tkinter import messagebox, ttk
from typing import Optional

from .i18n import get_translator, t

MODEL_LIBRARY = {
    0x414: ("Alien", "Higgs-4"),
    0x412: ("Alien", "Higgs-3"),
    0x893: ("NXP", "UCODE 12C"),
    0x080: ("EM Microelectronic", "EM4124"),
    0x811: ("Alien", "Higgs-EC"),
    0x004: ("NXP", "UCODE G2XL"),
    0x803: ("NXP", "UCODE G2XL/G2XM"),
    0x003: ("NXP", "UCODE G2XL/G2XM"),
    0x170: ("Impinj", "Monza R6-P"),
    0x160: ("Impinj", "Monza R6"),
    0x1C0: ("Impinj", "Monza M780"),
    0x093: ("Impinj", "Monza 3"),
    0x894: ("NXP", "UCODE 8"),
    0x810: ("NXP", "UCODE 7"),
    0x806: ("NXP", "G2iL"),
    0x1FE: ("CEITEC", "CTC13002"),
    0x10C: ("Impinj", "Monza 4E"),
    0x100: ("Impinj", "Monza 4D"),
    0x105: ("Impinj", "Monza 4QT"),
    0x130: ("Impinj", "Monza 5"),
}

MFG_DATABASE = {
    0x001: "Alien Microelectronics",
    0x003: "Impinj",
    0x801: "Impinj",
    0x806: "NXP Semiconductors",
    0x00B: "EM Microelectronic",
    0x006: "NXP (Série G2)",
    0x018: "CEITEC S.A.",
}

RFID_CMD_SET_TXPOWER = 0x10
RFID_CMD_SET_FREQ_TABLE = 0x14
RFID_CMD_STOP_INVENTORY = 0x8C
RFID_CMD_INV_TAG = 0x80
RFID_CMD_READ_DATA = 0x84
MEM_TID = 0x02
BAUD_RATE = 115200


def resolve_default_com_port(fallback: int = 4) -> int:
    """Detecta COM do reader (cache do port_manager) para Chip_ID standalone."""
    try:
        from .port_manager import get_com_port_number

        detected = get_com_port_number(refresh=False)
        if detected is not None:
            return int(detected)
    except Exception:
        pass
    return int(fallback)


def get_dll():
    dll_name = "UHFRFID.dll"
    meipass = getattr(sys, "_MEIPASS", None)
    if meipass:
        bundled = os.path.join(meipass, dll_name)
        if os.path.isfile(bundled):
            return ctypes.CDLL(bundled)
    here = os.path.dirname(os.path.abspath(__file__))
    dev = os.path.join(here, dll_name)
    if os.path.isfile(dev):
        return ctypes.CDLL(dev)
    return ctypes.CDLL(dll_name)


class ChipIdFinal:
    """UI Chip ID: ``Tk`` isolado ou ``Toplevel`` quando ``master`` é a app principal."""

    def __init__(
        self,
        default_freq_mhz: float = 915.0,
        master: Optional[tk.Misc] = None,
        default_com_port: int = 4,
    ):
        self._default_freq_mhz = float(default_freq_mhz)
        self._default_com_port = int(default_com_port)
        self._standalone = master is None
        self.translator = get_translator()
        self._widget_refs = {}
        self._field_labels = {}
        self._last_decode_raw = None
        self._status_done = False

        if self._standalone:
            self.root = tk.Tk()
        else:
            self.root = tk.Toplevel(master)
        self.root.geometry("640x580")

        try:
            self.sdk = get_dll()
            self.sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
            self.sdk.UHF_RFID_Set.argtypes = [
                ctypes.c_int,
                ctypes.c_char_p,
                ctypes.c_uint,
                ctypes.c_char_p,
                ctypes.POINTER(ctypes.c_uint),
            ]
        except Exception:
            messagebox.showerror(
                t("chip_id.dll_error_title"),
                t("chip_id.dll_error_msg"),
                parent=self.root,
            )

        frm = ttk.Frame(self.root, padding=10)
        frm.pack(fill=tk.BOTH, expand=True)

        cfg = ttk.LabelFrame(frm, padding=5)
        cfg.pack(fill=tk.X, pady=5)
        self._widget_refs["hw_frame"] = cfg

        lbl_freq = ttk.Label(cfg)
        lbl_freq.grid(row=0, column=0, padx=5, sticky=tk.W)
        self._widget_refs["lbl_freq"] = lbl_freq
        self.ent_freq = ttk.Entry(cfg, width=10)
        self.ent_freq.insert(0, f"{self._default_freq_mhz:g}")
        self.ent_freq.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)

        lbl_pwr = ttk.Label(cfg)
        lbl_pwr.grid(row=0, column=2, padx=5, sticky=tk.W)
        self._widget_refs["lbl_pwr"] = lbl_pwr
        self.ent_pwr = ttk.Entry(cfg, width=10)
        self.ent_pwr.insert(0, "20.0")
        self.ent_pwr.grid(row=0, column=3, padx=5, pady=5)

        self.btn_run = ttk.Button(frm, command=self.run_read)
        self.btn_run.pack(pady=10)
        self._widget_refs["btn_run"] = self.btn_run

        self.txt_tid = self.create_field(frm, "tid_raw", 1)
        self.txt_res = self.create_field(frm, "tid_tech", 2)
        self.txt_full = self.create_field(frm, "tid_clean", 1)

        self.lbl_st = ttk.Label(frm)
        self.lbl_st.pack(side=tk.BOTTOM, anchor=tk.W)
        self._widget_refs["lbl_st"] = self.lbl_st

        self._on_language_changed = self._make_language_handler()
        self.translator.add_language_change_listener(self._on_language_changed)
        self.root.protocol("WM_DELETE_WINDOW", self._on_close)

        self.refresh_language()

    def _make_language_handler(self):
        def _handler(old_language=None, new_language=None):
            try:
                if self.root.winfo_exists():
                    self.root.after(0, self.refresh_language)
            except tk.TclError:
                pass

        return _handler

    def _on_close(self):
        try:
            self.translator.remove_language_change_listener(self._on_language_changed)
        except Exception:
            pass
        self.root.destroy()

    def refresh_language(self):
        """Atualiza textos conforme PT/EN (mesmo tradutor que o FastChecker II)."""
        self.root.title(t("chip_id.window_title"))
        text_keys = {
            "hw_frame": "chip_id.hw_frame",
            "lbl_pwr": "chip_id.power_dbm",
            "lbl_freq": "chip_id.freq_mhz",
            "btn_run": "chip_id.run_button",
        }
        for key, i18n_key in text_keys.items():
            w = self._widget_refs.get(key)
            if w is not None:
                w.config(text=t(i18n_key))
        st = self._widget_refs.get("lbl_st")
        if st is not None:
            st.config(
                text=t("chip_id.status_done") if self._status_done else t("chip_id.status_ready")
            )

        for field_key, label_widget in self._field_labels.items():
            label_widget.config(text=t(f"chip_id.{field_key}"))

        if self._last_decode_raw is not None:
            info, clean = self.decode_engine(self._last_decode_raw)
            self.update_text(self.txt_res, info)
            self.update_text(self.txt_full, clean)

    def create_field(self, parent, field_key: str, h: int):
        lbl = ttk.Label(parent)
        lbl.pack(anchor=tk.W)
        self._field_labels[field_key] = lbl
        text_widget = tk.Text(parent, height=h, state=tk.DISABLED, bg="#f0f0f0")
        text_widget.pack(fill=tk.X, pady=2)
        return text_widget

    def update_text(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert(tk.END, text)
        widget.config(state=tk.DISABLED)

    def decode_engine(self, raw_hex: str):
        """Decodifica TID — lógica do Chip_ID Universal (laboratório Campinas)."""
        raw_hex = (raw_hex or "").strip().upper()
        mfg_lbl = t("chip_id.manufacturer")
        model_lbl = t("chip_id.model")
        model_new_lbl = t("chip_id.model_new")

        if not raw_hex or raw_hex == "00010000":
            return t("chip_id.no_tag_detected"), ""

        idx = raw_hex.find("E2")
        if idx == -1:
            if len(raw_hex) >= 8 and raw_hex.startswith("0001"):
                return (
                    f"{mfg_lbl}: Alien\n{model_lbl}: Alien Higgs-2 (Leitura Direta)",
                    raw_hex,
                )
            return t("chip_id.invalid_tid_response"), raw_hex

        clean = raw_hex[idx:]
        if len(clean) < 8:
            return t("chip_id.insufficient_tid_data"), clean

        try:
            mdid_code = int(clean[2:5], 16)
            model_code = int(clean[5:8], 16)

            if (model_code & 0xFF0) == 0x070:
                if mdid_code in (0x003, 0x801):
                    return f"{mfg_lbl}: Impinj\n{model_lbl}: Impinj Monza 2", clean
                return f"{mfg_lbl}: Alien\n{model_lbl}: Alien Higgs-2 (Var)", clean

            if model_code in MODEL_LIBRARY:
                mfg, model = MODEL_LIBRARY[model_code]
                if model_code == 0x105 and mdid_code not in (0x003, 0x801):
                    mfg, model = "Alien", "Higgs-4 (Var)"
                return f"{mfg_lbl}: {mfg}\n{model_lbl}: {model}", clean

            if (model_code & 0xFF0) == 0x410:
                return (
                    f"{mfg_lbl}: CPA Wernher von Braun\n"
                    f"{model_lbl}: CPA Wernher von Braun 0x415",
                    clean,
                )

            if (model_code & 0xFF0) == 0x880:
                return f"{mfg_lbl}: NXP\n{model_lbl}: NXP UCODE 12C", clean

            if (model_code & 0xFF0) == 0x190:
                return f"{mfg_lbl}: Impinj\n{model_lbl}: Impinj 0x190", clean

            mfg = MFG_DATABASE.get(mdid_code, f"MDID: {hex(mdid_code)}")
            return f"{mfg_lbl}: {mfg}\n{model_new_lbl}: {hex(model_code)}", clean
        except (ValueError, IndexError):
            return t("chip_id.decode_error"), clean

    def run_read(self):
        for f in [self.txt_tid, self.txt_res, self.txt_full]:
            self.update_text(f, "")
        self._last_decode_raw = None
        self._status_done = False
        if "lbl_st" in self._widget_refs:
            self._widget_refs["lbl_st"].config(text=t("chip_id.status_ready"))
        com = self._default_com_port
        try:
            pwr = float(self.ent_pwr.get())
            freq_mhz = float(self.ent_freq.get().replace(",", "."))
        except ValueError:
            messagebox.showwarning(
                t("chip_id.warning_title"),
                t("chip_id.invalid_values"),
                parent=self.root,
            )
            return

        def worker():
            if self.sdk.UHF_RFID_Open(com, BAUD_RATE) == 0:
                out = ctypes.create_string_buffer(256)
                ln = ctypes.c_uint(0)

                self.sdk.UHF_RFID_Set(RFID_CMD_STOP_INVENTORY, None, 0, out, ctypes.byref(ln))

                p_val = int(pwr * 100)
                p_data = bytes([0x00, 0x00]) + p_val.to_bytes(2, "big") * 2
                self.sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, p_data, 6, out, ctypes.byref(ln))

                f_khz = int(round(freq_mhz * 1000))
                f_khz = max(800_000, min(1_000_000, f_khz))
                freq_data = b"\x01" + f_khz.to_bytes(3, "big")
                self.sdk.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, freq_data, 4, out, ctypes.byref(ln))

                self.sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, b"\x00\x64", 2, out, ctypes.byref(ln))
                time.sleep(0.2)

                payload = (
                    b"\x00\x00\x00\x00"
                    + b"\x00\x00\x00\x00\x00"
                    + bytes([MEM_TID])
                    + b"\x00\x00\x00\x02"
                )
                self.sdk.UHF_RFID_Set(RFID_CMD_READ_DATA, payload, len(payload), out, ctypes.byref(ln))

                raw = out.raw[: ln.value].hex().upper() if ln.value >= 4 else ""
                if (
                    not raw
                    or raw == "00010000"
                    or raw.startswith("0003")
                    or all(b == 0 for b in out.raw[: ln.value])
                ):
                    if raw.startswith("0003"):
                        self.root.after(
                            0,
                            lambda: self.update_text(
                                self.txt_res,
                                t("chip_id.read_error_0003"),
                            ),
                        )
                    else:
                        self.root.after(
                            0,
                            lambda: self.update_text(
                                self.txt_res,
                                t("chip_id.no_tag_detected"),
                            ),
                        )
                else:
                    self._last_decode_raw = raw
                    self.root.after(0, lambda r=raw: self.update_text(self.txt_tid, r))
                    info, clean = self.decode_engine(raw)
                    self.root.after(0, lambda i=info: self.update_text(self.txt_res, i))
                    self.root.after(0, lambda c=clean: self.update_text(self.txt_full, c))

                self.sdk.UHF_RFID_Close(com)
            else:
                if self.root.winfo_exists():
                    self.root.after(
                        0,
                        lambda: self.update_text(
                            self.txt_res,
                            t("chip_id.com_open_error").format(com=com),
                        ),
                    )
            if self.root.winfo_exists():
                def _set_done():
                    self._status_done = True
                    self.lbl_st.config(text=t("chip_id.status_done"))

                self.root.after(0, _set_done)

        threading.Thread(target=worker, daemon=True).start()


def main_cli() -> None:
    parser = argparse.ArgumentParser(description="Chip_ID — identidade do chip (TID) via UHFRFID.dll")
    parser.add_argument(
        "--freq-mhz",
        type=float,
        default=915.0,
        help="Frequência do teste em MHz (ex.: 915 FCC/Anatel/broadband; 866 ETSI).",
    )
    parser.add_argument(
        "--com-port",
        type=int,
        default=None,
        help="Porta COM do reader (omitir = deteção automática).",
    )
    args, _unknown = parser.parse_known_args()
    com_port = args.com_port if args.com_port is not None else resolve_default_com_port(4)
    app = ChipIdFinal(default_freq_mhz=args.freq_mhz, default_com_port=com_port)
    app.root.mainloop()


if __name__ == "__main__":
    main_cli()
