# tractor: structured concurrent "actors".
# Copyright 2018-eternity Tyler Goodlet.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

"""
The "bootloader" for sub-actors spawned via the native `trio`
backend (the default `python -m tractor._child` CLI entry) and
the in-process `subint` backend (`tractor.spawn._subint`).

"""
from __future__ import annotations
import argparse
from ast import literal_eval
from typing import TYPE_CHECKING

from .runtime._runtime import Actor
from .spawn._entry import _trio_main

if TYPE_CHECKING:
    from .discovery._addr import UnwrappedAddress
    from .spawn._spawn import SpawnMethodKey


def parse_uid(arg):
    name, uuid = literal_eval(arg)  # ensure 2 elements
    return str(name), str(uuid)  # ensures str encoding

def parse_ipaddr(arg):
    try:
        return literal_eval(arg)

    except (ValueError, SyntaxError):
        # UDS: try to interpret as a straight up str
        return arg


def _actor_child_main(
    uid: tuple[str, str],
    loglevel: str | None,
    parent_addr: UnwrappedAddress | None,
    infect_asyncio: bool,
    spawn_method: SpawnMethodKey = 'trio',

) -> None:
    '''
    Construct the child `Actor` and dispatch to `_trio_main()`.

    Shared entry shape used by both the `python -m tractor._child`
    CLI (trio/mp subproc backends) and the `subint` backend, which
    invokes this from inside a fresh `concurrent.interpreters`
    sub-interpreter via `Interpreter.call()`.

    '''
    # Apply defensive monkey-patches for upstream `trio`
    # bugs we've encountered while running tractor — see
    # `tractor.trionics.patches` for the catalog +
    # per-patch upstream-fix tracking. Must run BEFORE
    # any trio runtime init.
    from .trionics.patches import apply_all
    apply_all()

    subactor = Actor(
        name=uid[0],
        uuid=uid[1],
        loglevel=loglevel,
        spawn_method=spawn_method,
    )

    # XXX, set a stable OS-level proc-title BEFORE entering
    # the trio runtime so `ps`/`top`/`acli.pytree` and
    # orphan-reapers can identify this actor for its full
    # lifetime — e.g.
    #   `tractor[doggy@1027301b]`
    # vs. the default uninformative
    #   `python -m tractor._child --uid (...)`
    #
    # `setproctitle` mutates `argv[0]` (visible in
    # `/proc/<pid>/cmdline`) AND the kernel `comm`
    # (visible in `/proc/<pid>/comm`, kernel-truncated to
    # ~15 bytes, but preserved through zombie state). Both
    # surfaces are enough for `_testing._reap` /
    # `acli.reap` orphan- and zombie-detection to identify
    # tractor sub-actors via intrinsic signals — no cwd,
    # venv path, or env-var coincidence-of-implementation
    # matching needed.
    #
    # NB: an earlier draft also wrote `TRACTOR_AID` to
    # `os.environ` here for `pgrep --env`-style discovery,
    # but Linux snapshots `/proc/<pid>/environ` at exec/fork
    # time, so post-fork runtime mutations don't propagate
    # to the kernel-visible env. The proc-title path
    # provides equivalent ergonomics
    # (`pgrep -f 'tractor\['`) without that gotcha.
    from .devx._proctitle import set_actor_proctitle
    set_actor_proctitle(subactor)

    _trio_main(
        subactor,
        parent_addr=parent_addr,
        infect_asyncio=infect_asyncio,
    )


if __name__ == "__main__":
    __tracebackhide__: bool = True

    parser = argparse.ArgumentParser()
    parser.add_argument("--uid", type=parse_uid)
    parser.add_argument("--loglevel", type=str)
    parser.add_argument("--parent_addr", type=parse_ipaddr)
    parser.add_argument("--asyncio", action='store_true')
    args = parser.parse_args()

    _actor_child_main(
        uid=args.uid,
        loglevel=args.loglevel,
        parent_addr=args.parent_addr,
        infect_asyncio=args.asyncio,
        spawn_method='trio',
    )
