import json
import os
import signal
import sys
import time
from datetime import datetime, timezone

import wcferry.client as wcf_client
from wcferry import Wcf


PORT = int(os.environ.get("WCF_COMMAND_PORT", "10086"))
STATE_ROOT = os.environ.get("WCF_STATE_ROOT", r"C:\UniDesk\personal-wechat\wcf-state")
DEBUG = os.environ.get("WCF_DEBUG", "false").lower() == "true"

STOP = False


class WcfProcessExit(Exception):
    def __init__(self, code):
        super().__init__(f"wcferry requested process exit: {code}")
        self.code = code


def now_iso():
    return datetime.now(timezone.utc).isoformat()


def write_json(path, payload):
    tmp = f"{path}.tmp"
    with open(tmp, "w", encoding="utf-8") as fp:
        json.dump(payload, fp, ensure_ascii=False, indent=2, sort_keys=True)
    os.replace(tmp, path)


def log(event, **fields):
    os.makedirs(STATE_ROOT, exist_ok=True)
    payload = {"ts": now_iso(), "event": event, **fields}
    line = json.dumps(payload, ensure_ascii=False, sort_keys=True)
    print(line, flush=True)
    with open(os.path.join(STATE_ROOT, "wcf-host.log"), "a", encoding="utf-8") as fp:
        fp.write(line + "\n")
        fp.flush()


def write_state(**fields):
    os.makedirs(STATE_ROOT, exist_ok=True)
    payload = {
        "ts": now_iso(),
        "isLogin": None,
        "qrcodePresent": False,
        "commandPort": PORT,
        "messagePort": PORT + 1,
        **fields,
    }
    write_json(os.path.join(STATE_ROOT, "status.json"), payload)


def handle_signal(_signum, _frame):
    global STOP
    STOP = True


def patch_wcferry_exit():
    def raise_exit(code=0):
        raise WcfProcessExit(code)

    wcf_client.os._exit = raise_exit


signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)


def connect_wcf():
    patch_wcferry_exit()
    attempt = 0
    while not STOP:
        attempt += 1
        local_init = attempt == 1 or attempt % 15 == 0
        mode = "local-init" if local_init else "remote-dial"
        try:
            write_state(phase="connecting", connectAttempt=attempt, connectMode=mode)
            log("wcf-host-connect-attempt", attempt=attempt, mode=mode, port=PORT)
            if local_init:
                wcf = Wcf(port=PORT, debug=DEBUG, block=False)
            else:
                wcf = Wcf(host="127.0.0.1", port=PORT, debug=DEBUG, block=False)
            log("wcf-host-connected", attempt=attempt, mode=mode, port=PORT)
            return wcf
        except WcfProcessExit as exc:
            log("wcf-host-connect-retry", attempt=attempt, mode=mode, requestedExit=exc.code)
        except Exception as exc:
            log("wcf-host-connect-error", attempt=attempt, mode=mode, error=f"{type(exc).__name__}: {exc}"[:1000])
        time.sleep(min(2 + attempt, 10))
    return None


def main():
    os.makedirs(STATE_ROOT, exist_ok=True)
    with open(os.path.join(STATE_ROOT, "wcf-host.pid"), "w", encoding="ascii") as fp:
        fp.write(str(os.getpid()))
    log(
        "wcf-host-start",
        port=PORT,
        debug=DEBUG,
        pid=os.getpid(),
        appData=os.environ.get("APPDATA", ""),
        userProfile=os.environ.get("USERPROFILE", ""),
    )
    wcf = connect_wcf()
    if wcf is None:
        log("wcf-host-stop-before-connect")
        return

    while not STOP:
        try:
            logged_in = bool(wcf.is_login())
            qrcode = ""
            if not logged_in:
                try:
                    qrcode = wcf.get_qrcode()
                except Exception as exc:
                    log("wcf-host-qrcode-error", error=f"{type(exc).__name__}: {exc}"[:1000])
                    qrcode = ""
            state = {
                "phase": "ready",
                "isLogin": logged_in,
                "qrcodePresent": bool(qrcode),
                "commandPort": PORT,
                "messagePort": PORT + 1,
            }
            if qrcode:
                state["qrcode"] = qrcode
            write_state(**state)
            log("wcf-host-status", isLogin=logged_in, qrcodePresent=bool(qrcode))
            time.sleep(5)
        except Exception as exc:
            log("wcf-host-error", error=f"{type(exc).__name__}: {exc}"[:1000])
            time.sleep(3)
    try:
        wcf.cleanup()
    except Exception:
        pass
    log("wcf-host-stop")


if __name__ == "__main__":
    main()
