"""
USB Solo-/Lotto-Miner einrichten (GekkoScience, Compac, ähnliche Sticks).

Ehrlich: USB-Miner = sehr geringe Hashrate, Solo = Lotterie-Chance, kein sicheres Einkommen.
"""

from __future__ import annotations

import json
import re
import subprocess
import webbrowser
from datetime import datetime
from pathlib import Path

from jam.brain import JamBrain
from jam.owner import owner_name

ROOT = Path(__file__).resolve().parent.parent
CONFIG_FILE = ROOT / "data" / "usb-miner.json"
MINING_PID_FILE = ROOT / "data" / "jam-mining.pid"
MINING_LOG = ROOT / "data" / "jam-mining.log"
MINER_ROOT = Path.home() / "Documents" / "Jam-USB-Mining"
CGMINER_EXE = MINER_ROOT / "cgminer" / "cgminer.exe"
CGMINER_DIR = MINER_ROOT / "cgminer"

# Bekannte Gerätenamen im Windows-Geräte-Manager (nach Zadig/Original-Treiber)
KNOWN_MINER_NAMES = (
    "compac",
    "gekko",
    "newpac",
    "bm1387",
    "bm1382",
    "bitforce",
    "antminer",
    "bitcoin miner",
    "asic",
    "lottery",
    "block erupter",
    "rockminer",
)

_SKIP_NAMES = ("microsoft basic", "render driver", "display driver")

POOL_SOLO_CKPOOL = "stratum+tcp://solo.ckpool.org:3333"
POOL_KANO = "stratum+tcp://stratum.kano.is:3333"

DOWNLOAD_LINKS = (
    ("Gekko CGMiner (Windows)", "https://kano.is/gekko.php"),
    ("Zadig WinUSB-Treiber", "https://zadig.akeo.ie/"),
    ("Compac A1 Anleitung (DE)", "https://www.bitshopper.de/en/support/instructions/compac-a1/compac-a1-setup-windows/"),
    ("Solo-Pool ckpool", "https://solo.ckpool.org/"),
)


def ensure_jam_bitcoin(brain: JamBrain) -> str:
    """Jam erstellt eigene BTC-Adresse und trägt sie in die Miner-Config ein."""
    from jam.jam_btc_wallet import ensure_jam_wallet, wallet_summary

    owner = owner_name(brain.state)
    wallet = ensure_jam_wallet(owner=owner)
    addr = wallet["address"]
    cfg = _load_config()
    cfg["btc_address"] = addr
    cfg["jam_wallet"] = True
    save_config(cfg)
    write_miner_pack(brain)
    brain.state.jam_btc_address = addr
    brain.state.remember(f"Jam BTC-Adresse: {addr[:14]}…")
    brain.save()
    return f"{owner}, Jam hat **seine Bitcoin-Adresse** erstellt: {addr}. {wallet_summary()}"[:520]


def _load_config() -> dict:
    if CONFIG_FILE.is_file():
        try:
            return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            pass
    return {
        "btc_address": "",
        "pool_url": POOL_SOLO_CKPOOL,
        "worker": "",
        "password": "x",
        "device_profile": "auto",
        "newpac_freq": 100,
        "compacf_freq": 400,
        "fan_required": True,
    }


def get_miner_config() -> dict:
    return _load_config()


def save_config(data: dict) -> None:
    CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
    CONFIG_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def detect_usb_miners() -> list[dict]:
    """Windows: PnP-Geräte nach Miner-Namen durchsuchen."""
    found: list[dict] = []
    try:
        ps = subprocess.run(
            [
                "powershell",
                "-NoProfile",
                "-Command",
                "Get-PnpDevice -PresentOnly | Select-Object FriendlyName, Class, Status | ConvertTo-Json -Compress",
            ],
            capture_output=True,
            text=True,
            timeout=45,
            encoding="utf-8",
            errors="replace",
        )
        raw = ps.stdout.strip()
        if not raw:
            return found
        items = json.loads(raw)
        if isinstance(items, dict):
            items = [items]
        for it in items:
            if not isinstance(it, dict):
                continue
            name = str(it.get("FriendlyName", ""))
            low = name.lower()
            if any(s in low for s in _SKIP_NAMES):
                continue
            if any(k in low for k in KNOWN_MINER_NAMES) or (
                "jtag" in low and "serial" in low
            ):
                found.append(
                    {
                        "name": name,
                        "class": it.get("Class", ""),
                        "status": it.get("Status", ""),
                        "likely_miner": "bitcoin" in low or "miner" in low or "compac" in low or "gekko" in low,
                    }
                )
    except (subprocess.TimeoutExpired, json.JSONDecodeError, OSError):
        pass
    return found


def _gekko_conf(cfg: dict) -> str:
    pool = cfg.get("pool_url", POOL_SOLO_CKPOOL)
    user = cfg.get("btc_address") or cfg.get("worker") or "DEINE_BITCOIN_ADRESSE"
    pw = cfg.get("password", "x")
    return f"""# Jam USB-Miner – cgminer Konfiguration
# Pool: solo.ckpool.org = Lotterie-Solo (BTC-Adresse als User)

{{
  "pools": [
    {{
      "url": "{pool}",
      "user": "{user}",
      "pass": "{pw}"
    }}
  ],
  "suggest-diff": 442,
  "gekko-newpac-detect": true,
  "gekko-newpac-freq": {int(cfg.get('newpac_freq', 100))},
  "gekko-compacf-detect": true,
  "gekko-compacf-freq": {int(cfg.get('compacf_freq', 400))},
  "gekko-compaca1-detect": true,
  "gekko-compaca1-freq": 300
}}
"""


def _start_bat(cfg: dict, cgminer_dir: str) -> str:
    pool = cfg.get("pool_url", POOL_SOLO_CKPOOL)
    user = cfg.get("btc_address") or "DEINE_BITCOIN_ADRESSE"
    pw = cfg.get("password", "x")
    freq = int(cfg.get("newpac_freq", 100))
    return f"""@echo off
cd /d "{cgminer_dir}"
echo Jam USB-Miner starten...
echo Pool: {pool}
echo User (BTC-Adresse): {user}
echo.
if not exist cgminer.exe (
  echo FEHLER: cgminer.exe fehlt in diesem Ordner.
  echo Lade Gekko-CGMiner von https://kano.is/gekko.php
  pause
  exit /b 1
)
cgminer.exe -n
echo.
echo Wenn Miner oben erkannt: Enter zum Start, Strg+C zum Abbrechen
pause
cgminer.exe -o {pool} -u {user} -p {pw} --suggest-diff 442 ^
  --gekko-newpac-detect --gekko-newpac-freq {freq} ^
  --gekko-compacf-detect --gekko-compacf-freq {int(cfg.get('compacf_freq', 400))}
pause
"""


def write_miner_pack(brain: JamBrain | None = None) -> Path:
    cfg = _load_config()
    MINER_ROOT.mkdir(parents=True, exist_ok=True)
    devices = detect_usb_miners()
    cgminer_dir = str(MINER_ROOT / "cgminer")

    (MINER_ROOT / "ANLEITUNG.md").write_text(
        _anleitung_md(devices, cfg, brain),
        encoding="utf-8",
    )
    (MINER_ROOT / "gekko.conf").write_text(_gekko_conf(cfg), encoding="utf-8")
    (MINER_ROOT / "start-mining.bat").write_text(_start_bat(cfg, cgminer_dir), encoding="utf-8")
    (MINER_ROOT / "1-zadig-treiber.txt").write_text(
        "1. Miner an **USB-Hub mit Netzteil** + **Lüfter** stecken\n"
        "2. Zadig als Admin: https://zadig.akeo.ie/\n"
        "3. Options -> List All Devices\n"
        "4. Gerät wählen (z.B. CompacF / NewPac BM1387 Bitcoin Miner)\n"
        "5. WinUSB installieren\n"
        "6. cgminer.exe -n im cgminer-Ordner testen\n",
        encoding="utf-8",
    )
    (MINER_ROOT / "DOWNLOADS.txt").write_text(
        "\n".join(f"{t}: {u}" for t, u in DOWNLOAD_LINKS) + "\n\n"
        f"cgminer.exe nach: {cgminer_dir}\n",
        encoding="utf-8",
    )
    (MINER_ROOT / "detected-devices.json").write_text(
        json.dumps(devices, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    (MINER_ROOT / "cgminer").mkdir(parents=True, exist_ok=True)
    (MINER_ROOT / "cgminer" / "LEGE_CGMINER_HIER.txt").write_text(
        "Entpacke den Gekko-cgminer von https://kano.is/gekko.php hier.\n"
        "cgminer.exe muss in diesem Ordner liegen.\n",
        encoding="utf-8",
    )
    return MINER_ROOT


def _anleitung_md(devices: list[dict], cfg: dict, brain: JamBrain | None) -> str:
    owner = owner_name(brain.state) if brain else "David"
    dev_lines = "\n".join(f"- {d['name']} ({d['status']})" for d in devices) or "- *(kein Miner im Geräte-Manager – Zadig/Treiber oder USB-Hub prüfen)*"
    btc = cfg.get("btc_address") or "*(trage deine BTC-Adresse in data/usb-miner.json ein)*"
    return f"""# Jam USB Solo-/Lotto-Miner – {owner}

Stand: {datetime.now().strftime("%d.%m.%Y %H:%M")}

## Ehrlich
- USB-Miner verdienen **sehr wenig** – Solo = **Lotto** (ein Block = extrem unwahrscheinlich).
- Strom + Lüfter + **powered USB-Hub** sind Pflicht.
- **Verkaufen** der BTC später nur über Börse mit KYC – nicht automatisch wie PayPal.

## Erkannte Geräte (Windows)
{dev_lines}

## Schritte
1. **Bitcoin-Adresse** in `jam-buddy/data/usb-miner.json` → `"btc_address": "bc1..."`
2. **CGMiner** von https://kano.is/gekko.php nach `cgminer/cgminer.exe`
3. **Zadig** WinUSB: siehe `1-zadig-treiber.txt`
4. Test: `cgminer\\cgminer.exe -n` (Miner muss erscheinen)
5. Start: `start-mining.bat` (als Admin falls nötig)

## Pool (Lotto-Solo)
- Standard: **solo.ckpool.org** – Username = deine **BTC-Adresse**
- Aktuell in Config: `{cfg.get('pool_url', POOL_SOLO_CKPOOL)}`
- Adresse: `{btc}`

## Jam steuert Mining auf diesem PC
- „**Jam, starte Mining**“ / Menü **USB-Mining: auf PC starten**
- „**Jam, Mining stoppen**“
- Skript: `start-pc-mining.bat`

## Hilfe
- Jam: „Jam, USB Miner Status“
- Ordner: {MINER_ROOT}
"""


def set_btc_address(brain: JamBrain, address: str) -> str:
    addr = address.strip()
    if not re.match(r"^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,62}$", addr):
        return "Ungültige BTC-Adresse – bitte bc1... oder 1... / 3..."
    cfg = _load_config()
    cfg["btc_address"] = addr
    save_config(cfg)
    write_miner_pack(brain)
    brain.state.remember(f"USB-Miner BTC: {addr[:12]}...")
    brain.save()
    return f"{owner_name(brain.state)}, BTC-Adresse für Solo-Mining gespeichert. Start: start-usb-miner.bat"


def run_usb_miner_setup(brain: JamBrain, *, open_links: bool = True) -> str:
    owner = owner_name(brain.state)
    folder = write_miner_pack(brain)
    devices = detect_usb_miners()
    cfg = _load_config()

    if open_links:
        for _t, url in DOWNLOAD_LINKS[:3]:
            try:
                webbrowser.open(url, new=2)
            except OSError:
                break
        try:
            import os

            os.startfile(str(folder))
        except OSError:
            pass

    dev_txt = ", ".join(d["name"] for d in devices[:3]) if devices else "noch kein Miner erkannt (Zadig + Hub)"
    btc_ok = "ja" if cfg.get("btc_address") else "fehlt – in data/usb-miner.json"
    cgminer = (folder / "cgminer" / "cgminer.exe").is_file()

    return (
        f"{owner}, **USB-Miner Setup** liegt in {folder.name}. "
        f"Geräte: {dev_txt}. BTC-Adresse: {btc_ok}. "
        f"cgminer.exe: {'da' if cgminer else 'fehlt – von kano.is/gekko.php laden'}. "
        f"Dann Zadig WinUSB → start-mining.bat. Solo = Lotterie, kein festes Geld!"
    )[:620]


def cgminer_path() -> Path:
    return CGMINER_EXE


def _mining_pid() -> int | None:
    try:
        if MINING_PID_FILE.is_file():
            pid = int(MINING_PID_FILE.read_text(encoding="utf-8").strip())
            import os

            os.kill(pid, 0)
            return pid
    except (OSError, ValueError):
        MINING_PID_FILE.unlink(missing_ok=True)
    return None


def is_pc_mining_running() -> bool:
    return _mining_pid() is not None


def _cgminer_command(cfg: dict) -> list[str]:
    pool = cfg.get("pool_url", POOL_SOLO_CKPOOL)
    user = cfg.get("btc_address", "")
    pw = cfg.get("password", "x")
    freq = int(cfg.get("newpac_freq", 100))
    cfreq = int(cfg.get("compacf_freq", 400))
    return [
        str(CGMINER_EXE),
        "-o",
        pool,
        "-u",
        user,
        "-p",
        pw,
        "--suggest-diff",
        "442",
        "--gekko-newpac-detect",
        "--gekko-newpac-freq",
        str(freq),
        "--gekko-compacf-detect",
        "--gekko-compacf-freq",
        str(cfreq),
        "--gekko-compaca1-detect",
        "--gekko-compaca1-freq",
        "300",
    ]


def _log_mining(msg: str) -> None:
    line = f"[{datetime.now().strftime('%H:%M:%S')}] {msg}\n"
    try:
        MINING_LOG.parent.mkdir(parents=True, exist_ok=True)
        prev = MINING_LOG.read_text(encoding="utf-8") if MINING_LOG.is_file() else ""
        MINING_LOG.write_text(prev[-4000:] + line, encoding="utf-8")
    except OSError:
        pass


def setup_full_pc_mining(brain: JamBrain, *, use_gpu: bool = True) -> str:
    """Wallet + USB-Pack + optional GPU (NiceHash → BTC)."""
    owner = owner_name(brain.state)
    parts = [ensure_jam_bitcoin(brain)]
    parts.append(run_usb_miner_setup(brain, open_links=False))
    cfg = _load_config()
    if use_gpu and cfg.get("use_gpu", True):
        from jam.gpu_mining import start_gpu_mining_now

        parts.append(
            start_gpu_mining_now(btc_address=cfg["btc_address"], power_percent=90, open_browser=True)
        )
    return " ".join(parts)[:680]


def start_pc_mining(brain: JamBrain) -> str:
    """Jam startet cgminer auf diesem PC (USB-ASIC, sichtbares Fenster)."""
    owner = owner_name(brain.state)
    if is_pc_mining_running():
        return f"{owner}, Mining läuft schon auf dem PC (cgminer-Fenster)."

    cfg = _load_config()
    if cfg.get("jam_wallet_auto", True) and not cfg.get("btc_address"):
        ensure_jam_bitcoin(brain)
        cfg = _load_config()

    write_miner_pack(brain)
    if not cfg.get("btc_address"):
        return ensure_jam_bitcoin(brain)
    if not CGMINER_EXE.is_file():
        return (
            f"{owner}, cgminer.exe fehlt in {CGMINER_DIR}. "
            f"Lade Gekko-CGMiner von kano.is/gekko.php – dann nochmal starten."
        )[:520]

    devices = detect_usb_miners()
    if not devices:
        return (
            f"{owner}, kein USB-Miner sichtbar – Hub + Zadig WinUSB, dann "
            f"cgminer.exe -n testen. Einrichtung: start-usb-miner.bat"
        )[:520]

    cmd = _cgminer_command(cfg)
    try:
        proc = subprocess.Popen(
            cmd,
            cwd=str(CGMINER_DIR),
            creationflags=getattr(subprocess, "CREATE_NEW_CONSOLE", 0),
        )
    except OSError as e:
        _log_mining(f"start fail: {e}")
        return f"Mining-Start fehlgeschlagen: {e}"[:350]

    MINING_PID_FILE.parent.mkdir(parents=True, exist_ok=True)
    MINING_PID_FILE.write_text(str(proc.pid), encoding="utf-8")
    state = brain.state
    state.jam_pc_mining_on = True
    state.jam_mining_started_at = __import__("time").time()
    state.jam_mining_device = devices[0].get("name", "USB-Miner")[:80]
    state.remember("Jam: USB-Mining auf PC gestartet")
    brain.save()
    _log_mining(f"started pid={proc.pid} pool={cfg.get('pool_url')}")

    extra = ""
    if cfg.get("use_gpu", True):
        from jam.gpu_mining import start_gpu_mining_now

        extra = " " + start_gpu_mining_now(
            btc_address=cfg["btc_address"], power_percent=90, open_browser=False
        )[:200]

    return (
        f"{owner}, **Jam betreibt Mining auf dem PC**! "
        f"USB cgminer offen ({devices[0]['name']}). "
        f"BTC an **Jams Adresse** {cfg['btc_address'][:18]}… Solo=Lotto. "
        f"GPU: NiceHash für Grafikkarte.{extra} Stoppen: „Jam, Mining stoppen“."
    )[:720]


def stop_pc_mining(brain: JamBrain) -> str:
    import os

    owner = owner_name(brain.state)
    pid = _mining_pid()
    if not pid:
        brain.state.jam_pc_mining_on = False
        brain.save()
        return f"{owner}, Mining läuft gerade nicht auf dem PC."

    try:
        subprocess.run(
            ["taskkill", "/PID", str(pid), "/F", "/T"],
            capture_output=True,
            timeout=15,
        )
    except (subprocess.TimeoutExpired, OSError):
        try:
            os.kill(pid, 9)
        except OSError:
            pass
    MINING_PID_FILE.unlink(missing_ok=True)
    brain.state.jam_pc_mining_on = False
    brain.save()
    _log_mining(f"stopped pid={pid}")
    return f"{owner}, Mining auf dem PC gestoppt (cgminer beendet)."


def miner_status(brain: JamBrain) -> str:
    devices = detect_usb_miners()
    cfg = _load_config()
    cg = CGMINER_EXE.is_file()
    running = is_pc_mining_running()
    lines = [f"{owner_name(brain.state)}, **Mining auf diesem PC**:"]
    lines.append(f"Läuft: {'JA (cgminer)' if running else 'nein'}")
    lines.append(f"Geräte: {len(devices)} – " + (devices[0]["name"] if devices else "nicht sichtbar"))
    btc = cfg.get("btc_address", "")
    lines.append(
        f"Jam-BTC: {btc[:22] + '…' if len(btc) > 22 else btc or 'fehlt'} · cgminer: {'OK' if cg else 'fehlt'}"
    )
    if cfg.get("use_gpu"):
        lines.append("GPU: NiceHash → BTC (nicht direktes BTC-GPU-Mining)")
    lines.append(f"Ordner: {MINER_ROOT}")
    if not running and cg and cfg.get("btc_address"):
        lines.append("Bereit – sag „Jam, starte Mining“.")
    return "\n".join(lines)[:520]


def hears_usb_miner(text: str) -> bool:
    h = text.lower().replace("jam", " ")
    if any(w in h for w in ("usb miner", "usb-miner", "lotto mining", "solo mining", "stick miner")):
        return True
    if "mining" in h and any(
        w in h
        for w in (
            "usb",
            "stick",
            "lotto",
            "solo",
            "einrichten",
            "pc",
            "start",
            "stop",
            "betreib",
            "laufen",
        )
    ):
        return True
    if "auf pc" in h or "auf dem pc" in h:
        if "mining" in h or "miner" in h:
            return True
    if any(w in h for w in ("grafikkarte", "gpu", "nicehash", "radeon", "6900")):
        if "mining" in h or "miner" in h or "bitcoin" in h:
            return True
    if any(w in h for w in ("bitcoin erstellen", "btc erstellen", "wallet", "adresse erstellen")):
        return True
    return False


def handle_speech(brain: JamBrain, heard: str) -> str | None:
    if not hears_usb_miner(heard):
        return None
    h = heard.lower()
    m = re.search(r"\b(bc1[a-z0-9]{25,62}|[13][a-km-zA-HJ-NP-Z1-9]{25,34})\b", heard)
    if m:
        return set_btc_address(brain, m.group(1))
    if any(w in h for w in ("wallet", "adresse erstellen", "bitcoin erstellen", "btc erstellen")):
        return ensure_jam_bitcoin(brain)
    if any(w in h for w in ("grafikkarte", "gpu", "nicehash")) and "stop" not in h:
        from jam.gpu_mining import handle_speech as gpu_speech

        r = gpu_speech(brain, heard)
        if r:
            return r
        cfg = _load_config()
        if not cfg.get("btc_address"):
            ensure_jam_bitcoin(brain)
        from jam.gpu_mining import start_gpu_mining_for_brain

        pct = 90
        m = re.search(r"(\d{2,3})\s*(?:%|prozent)", heard.lower())
        if m:
            pct = max(50, min(100, int(m.group(1))))
        return start_gpu_mining_for_brain(brain, power_percent=pct)
    if any(w in h for w in ("stop", "stopp", "aus", "beenden")):
        return stop_pc_mining(brain)
    if any(w in h for w in ("alles", "komplett", "einrichten", "setup")):
        return setup_full_pc_mining(brain, use_gpu=True)
    if any(w in h for w in ("start", "los", "betreib", "anmachen", "laufen lass")):
        return start_pc_mining(brain)
    if any(w in h for w in ("status", "läuft", "erkannt")):
        return miner_status(brain)
    if any(w in h for w in ("einricht", "setup", "hilfe")):
        return run_usb_miner_setup(brain, open_links=True)
    return start_pc_mining(brain)
