"""Synthetic Mystic BBS directory tree for running the vendored
mrc_client.py multiplexer (see vendor/PROVENANCE.md) as a real
subprocess without a real Mystic BBS installation anywhere on the box.

mrc_client.py expects to live in a Mystic install's root ("bbspath")
with a `data/users.dat` file (its own sanity check that it's really
being run from a BBS directory) and does all its file-based IPC
relative to that root: outbound packets are `.mrc` files dropped into
`data/mrc/`, inbound packets get written into `temp<room>/*.mrc`
(the literal string "temp" concatenated with the room name -- NOT a
`temp/<room>/` subdirectory, see room_dir()'s own docstring for how
this was confirmed) for any room with an active `tchat.inuse` marker
in that directory. None of that has anything to do with a real Mystic
install once these files/markers exist -- it's just a directory shape
the script happens to expect.

`mrc_config.py` is imported by `from mrc_config import *` BEFORE the
script's own os.chdir(bbspath) runs, so Python resolves it via
sys.path[0] (the directory containing the script being executed) --
meaning mrc_client.py and its mrc_config.py must be co-located. Rather
than fight that, this puts both files directly in `bbspath` itself:
the synthetic root doubles as the script's own home directory.
"""
import os
import shutil
from pathlib import Path

_VENDOR_DIR = Path(__file__).resolve().parent / 'vendor'
_VENDOR_CLIENT = _VENDOR_DIR / 'mrc_client.py'

_MRC_CONFIG_TEMPLATE = '''\
# Auto-generated by mrc/mystic_client/fake_bbs.py -- DO NOT EDIT BY HAND.
# Regenerated on every bridge startup from mrc/bridge/config.json.

pidfile     = {pidfile!r}
bbspath     = {bbspath!r}
bbsname     = {bbsname!r}
host        = {host!r}
plainport   = {plainport!r}
sslport     = {sslport!r}
usessl      = {usessl!r}
nocertcheck = {nocertcheck!r}
info_web    = {info_web!r}
info_telnet = {info_telnet!r}
info_ssh    = {info_ssh!r}
info_sysop  = {info_sysop!r}
info_desc   = {info_desc!r}
'''


def _stripmci_len(s: str) -> int:
    """mrc_client.py's own check_startup() rejects a bbsname shorter
    than 5 chars after stripping |NN pipe-color codes -- mirror that
    here so a bad config fails loudly at generation time instead of
    silently inside the subprocess's own sys.exit()."""
    import re
    return len(re.sub(r'\|[0-9]{2}', '', s or ''))


def bbs_config_to_mystic_fields(bridge_config: dict) -> dict:
    """Map mrc/bridge/config.json's existing field names (already used
    by MRCConnection/BridgeApp) onto mrc_config.py's expected names --
    no new sysop-facing config surface, same values either backend uses."""
    use_ssl = bool(bridge_config.get('use_ssl', False))
    port = int(bridge_config.get('mrc_port', 5000))
    return {
        'bbsname':     bridge_config.get('bridge_bbs', ''),
        'host':        bridge_config.get('mrc_host', ''),
        'plainport':   port if not use_ssl else 5000,
        'sslport':     port if use_ssl else 5001,
        'usessl':      1 if use_ssl else 0,
        # Matches MRCConnection.connect()'s own unconditional
        # check_hostname=False/verify_mode=CERT_NONE -- same lenient
        # cert policy regardless of which backend is active.
        'nocertcheck': 1,
        'info_web':    bridge_config.get('bbs_website', ''),
        'info_telnet': bridge_config.get('bbs_telnet', ''),
        'info_ssh':    bridge_config.get('bbs_ssh', ''),
        'info_sysop':  bridge_config.get('bbs_sysop', ''),
        'info_desc':   bridge_config.get('bbs_description', ''),
    }


def ensure_fake_bbs_tree(bbspath: Path, bridge_config: dict) -> Path:
    """Create/refresh the synthetic Mystic tree at `bbspath` and return
    the path to the (freshly copied) mrc_client.py script to execute.
    Safe to call every bridge startup -- idempotent, never touches an
    already-populated data/mrc or temp directory (live packet queues)."""
    bbspath = Path(bbspath)
    data_dir = bbspath / 'data'
    mrc_dir  = data_dir / 'mrc'
    temp_dir = bbspath / 'temp'

    for d in (bbspath, data_dir, mrc_dir, temp_dir):
        d.mkdir(parents=True, exist_ok=True)

    users_dat = data_dir / 'users.dat'
    if not users_dat.exists():
        # Only needs to exist -- mrc_client.py's check_startup() just
        # does os.path.exists() on it, never reads the content.
        users_dat.write_text('')

    fields = bbs_config_to_mystic_fields(bridge_config)
    bbsname_len = _stripmci_len(fields['bbsname'])
    if bbsname_len < 5:
        raise ValueError(
            f"mrc_backend='mystic' needs bridge_bbs in config.json to be "
            f"at least 5 characters after stripping |NN color codes "
            f"(got {fields['bbsname']!r}, {bbsname_len} chars) -- the "
            f"vendored mrc_client.py refuses to start otherwise.")

    client_script = bbspath / 'mrc_client.py'
    shutil.copyfile(_VENDOR_CLIENT, client_script)

    config_script = bbspath / 'mrc_config.py'
    config_script.write_text(_MRC_CONFIG_TEMPLATE.format(
        pidfile=str(bbspath / 'mrc_client.pid'),
        bbspath=str(bbspath),
        **fields,
    ))

    return client_script


def room_dir(bbspath: Path, room: str) -> Path:
    """Real bug found live (first genuine round-trip test, /whoon and
    /identify replies never arriving despite outbound packets and a
    real upstream connection both confirmed working): this used to
    return bbspath/temp/<room>/ -- a subdirectory of temp/. The actual
    vendored script's own deliver_mrc() builds this path by STRING
    REPLACEMENT, not path joining -- `f.replace(chatfile, tempdir)`
    where chatfile ends in ".../data/chat" and tempdir is
    ".../temp" (no trailing separator) -- so "chatlobby.dat" becomes
    ".../templobby.dat" and, after stripping ".dat", the directory it
    actually checks/writes to is ".../temp<room>/" (temp and the room
    name concatenated with NO separator, a sibling of temp/, not a
    child of it). Confirmed by literally running the vendored script's
    own substitution logic. Every inbound reply was being written to a
    directory nothing was ever watching."""
    return Path(bbspath) / f'temp{room}'


def chat_dat_path(bbspath: Path, room: str) -> Path:
    return Path(bbspath) / 'data' / f'chat{room}.dat'


def inuse_marker_path(bbspath: Path, room: str) -> Path:
    return room_dir(bbspath, room) / 'tchat.inuse'


def mrc_outbound_dir(bbspath: Path) -> Path:
    return Path(bbspath) / 'data' / 'mrc'
