"""sshj.identity — our own LAN/tailnet identity (leaf).

B2 step 2: moved verbatim from sshj_cli.py (single-file era) — no
behavior change. Owns IDENT_REFRESH (only its consumer, Identity,
lives here).
"""

import json
import os
import subprocess
import threading
import time

from .jsonio import IDENTITY_FILE, load_json, save_json

IDENT_REFRESH = 60         # seconds between identity refreshes


def _lan_ip_socket():
    """Best-effort LAN IP without the `ip` command (e.g. Termux/Android):
    open a UDP socket 'to' an address and read the local endpoint — no
    packet is actually sent, so it works on any interface."""
    import socket
    for probe in (("10.255.255.1", 1), ("8.8.8.8", 80)):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.settimeout(0.3)
            s.connect(probe)
            ip = s.getsockname()[0]
            s.close()
            return ip
        except Exception:
            continue
    return ""


def our_identity():
    """(lan_ip, tailscale_ip, tailscale_name) — live, best-effort."""
    lan = ts = ""
    try:
        r = subprocess.run(["ip", "-4", "-o", "addr", "show"],
                           capture_output=True, text=True, timeout=2)
        for ln in r.stdout.splitlines():
            p = ln.split()
            if len(p) >= 4:
                dev, addr = p[1], p[3].split("/")[0]
                if dev == "tailscale0":
                    ts = addr
                elif dev != "lo" and not lan:
                    lan = addr
    except Exception:
        pass
    if not lan:
        lan = _lan_ip_socket()       # `ip` missing (e.g. Termux/Android)
    name = ""
    try:
        r = subprocess.run(["tailscale", "status", "--json"],
                           capture_output=True, text=True, timeout=3)
        d = json.loads(r.stdout)
        self_ = d.get("Self") or {}
        dns = (self_.get("DNSName") or "").rstrip(".")
        if dns.endswith(".ts.net"):
            name = dns.split(".ts.net")[0]
        if "." in name:  # drop the .tailXXXXX magicdns suffix
            name = name.split(".")[0]
        ips = self_.get("TailscaleIPs") or []
        if ips:
            ts = ips[0]
    except Exception:
        pass
    if not name:
        name = os.uname().nodename
    return lan, ts, name


class Identity:
    """Live identity: cached value shown muted until a fresh fetch lands."""

    def __init__(self):
        self.name = self.lan = self.ts = ""
        self.stale = False
        self.lock = threading.Lock()

    def load_cache(self):
        d = load_json(IDENTITY_FILE, {})
        if d:
            with self.lock:
                self.name = d.get("name", "")
                self.lan = d.get("lan", "")
                self.ts = d.get("ts", "")
                self.stale = True

    def refresh(self):
        lan, ts, name = our_identity()
        with self.lock:
            if name:
                self.name = name
            if ts:
                self.ts = ts
            if lan:
                self.lan = lan
            self.stale = False
            snap = (self.lan, self.ts, self.name)
        save_json(IDENTITY_FILE, {"lan": snap[0], "ts": snap[1],
                                  "name": snap[2], "ts_at": time.time()})

    def start(self):
        t = threading.Thread(daemon=True, target=self._loop)
        t.start()
        return t

    def _loop(self):
        self.refresh()
        while True:
            time.sleep(IDENT_REFRESH)
            try:
                self.refresh()
            except Exception:
                pass

    def display(self, max_w):
        """Pick the longest tier that fits: lan · ts name → ts name → name."""
        with self.lock:
            parts = [p for p in (self.ts, self.name) if p]
            short = " ".join(parts)
            full = " · ".join(p for p in (self.lan, short) if p)
            stale = self.stale
            name = self.name
        for s in (full, short, name):
            if s and len(s) <= max_w:
                return s, stale
        return "", stale


