"""Team Fortress 2 Console Monitor."""

import curses
import re
import threading
from pathlib import Path

import libcurses
from loguru import logger

import tf2mon
from tf2mon.conlog import Conlog
from tf2mon.database import Database
from tf2mon.game.capture import GameCaptureEvent
from tf2mon.game.chat import GameChatEvent
from tf2mon.game.connected import GameConnectedEvent
from tf2mon.game.kill import GameKillEvent
from tf2mon.game.lobby import GameLobbyEvent
from tf2mon.game.perk import (
    GamePerkChangeEvent,
    GamePerkOff1Event,
    GamePerkOff2Event,
    GamePerkOnEvent,
)
from tf2mon.game.status import GameStatusEvent
from tf2mon.pkg import APPNAME
from tf2mon.player import Player
from tf2mon.racist import load_racist_data
from tf2mon.regex import Regex
from tf2mon.role import load_weapons_data
from tf2mon.steamplayer import SteamPlayer
from tf2mon.ui import UI
from tf2mon.users import Users


class _Monitor:
    """Team Fortress 2 Console Monitor."""

    regex_list: list
    admin_regex_list: list[Regex] = []
    gameplay_regex_list: list[Regex] = []

    leader = r"^(\d{2}/\d{2}/\d{4} - \d{2}:\d{2}:\d{2}: )?"  # anchor to head; optional timestamp

    def __init__(self):
        """Initialize Team Fortress 2 Console Monitor."""

        # Commands operator may type into monitor admin console:
        self.admin_regex_list = [
            # stop single-stepping
            Regex(
                "^(c|cont|continue)$", lambda m: tf2mon.SingleStepControl.stop_single_stepping()
            ),
            # stop single-stepping until eof, then single-step again
            Regex(
                "^(r|run|g|go)$", lambda m: tf2mon.SingleStepControl.set_single_step_lineno(0)
            ),
            # dump internals
            Regex("^dump$", lambda m: tf2mon.dump()),
            # pause when logfile reaches `lineno`.
            Regex(
                R"^(b|break|breakpoint)[= ](?P<lineno>\d+)$",
                lambda m: tf2mon.SingleStepControl.set_single_step_lineno(
                    int(m.group("lineno"))
                ),
            ),
            # pause when logfile reaches next line that matches `pattern`.
            Regex(
                "^/(?P<pattern>.*)$",
                lambda m: tf2mon.SingleStepControl.set_single_step_pattern(m.group("pattern")),
            ),
            # kick cheater
            Regex(
                R"^kick[= ](?P<userid>\d+)$",
                lambda m: Users.kick_userid(int(m.group("userid")), Player.CHEATER),
            ),
            # kick racist
            Regex(
                R"^kkk[= ](?P<userid>\d+)$",
                lambda m: Users.kick_userid(int(m.group("userid")), Player.RACIST),
            ),
            # mark suspect
            Regex(
                R"^suspect[= ](?P<userid>\d+)$",
                lambda m: Users.kick_userid(int(m.group("userid")), Player.SUSPECT),
            ),
            # drop to python debugger
            Regex("^PDB$", lambda m: tf2mon.debugger()),
            # deprecated, legacy, support old conlogs.
            Regex(
                "^(TOGGLE-SCOREBOARD|TOGGLE-QUEUES|CHATS-POP|CHATS-POPLEFT|CHATS-CLEAR)$",
                lambda m: logger.debug("ignore"),
            ),
            # The following aren't commands, but other items that may be in
            # the conlog. They don't belong in `gameplay` because they're
            # not generated by tf2.
            # qvalve
            Regex("^(QVALVE) (.*)", lambda m: logger.log(m.group(1), m.group(2))),
        ]

        self.gameplay_regex_list = [
            # new server
            # Regex(
            #     leader
            #     + "(Client reached server_spawn.$|Connected to [0-9]|Differing lobby received.)",
            #     lambda m: tf2mon.reset_game(),
            # ),
            # capture/defend
            GameCaptureEvent().regex,
            # must be before `chat`
            # account : not logged in  (No account specified)
            # version : 6173888/24 6173888 secure
            # map     : pl_barnblitz at: 0 x, 0 y, 0 z
            # udp/ip  : 208.78.164.167:27067  (public ip: 208.78.164.167)
            # tags    : hidden,increased_maxplayers,payload,valve
            # steamid : [A:1:3814649857:15826] (90139968514486273)
            # players : 20 humans, 0 bots (32 max)
            # edicts  : 1378 used of 2048 max
            Regex(
                self.leader
                + r"(account|version|map|udp\/ip|tags|steamid|players|edicts)\s+: (.*)",
                lambda m: ...,  # logger.log("server", m.group(0)),
            ),
            # "06/05/2022 - 13:54:19: Client ping times:"
            Regex(
                self.leader + r"Client ping times:",
                lambda m: ...,  # logger.log("server", m.group(0)),
            ),
            # "06/05/2022 - 13:54:19:   67 ms : luft"
            # "06/05/2022 - 13:54:19:xy 87 ms : BananaHatTaco"
            Regex(
                self.leader + r"\s*\d+ ms .*",
                lambda m: ...,  # logger.log("server", m.group(0)),
            ),
            GameChatEvent().regex,
            GameKillEvent().regex,
            GameConnectedEvent().regex,
            GameStatusEvent().regex,
            GameLobbyEvent().regex,
            GamePerkOnEvent().regex,
            GamePerkOff1Event().regex,
            GamePerkOff2Event().regex,
            GamePerkChangeEvent().regex,
            #
            Regex(
                self.leader + "Failed to find lobby shared object",
                lambda m: logger.trace("tf_lobby_debug failed: " + m.group(0)),
            ),
            # Regex(
            #    '^Teams have been switched',
            #    lambda m: tf2mon.users.switch_teams()),
            #
            Regex(
                self.leader + r"You have switched to team (?P<teamname>\w+) and will",
                lambda m: Users.me.assign_team(m.group("teamname")),
            ),
            # hostname: Valve Matchmaking Server (Virginia iad-1/srcds148 #53)
            Regex(
                "^hostname: (.*)",
                lambda m: Users.check_status(),
            ),
        ]

    def run(self):
        """Run the Monitor."""
        libcurses.wrapper(self._run)

    def _run(self, win: curses.window) -> None:
        """Complete initialization; post CLI, options now available."""

        # Wait for con_logfile to exist, then open it.
        tf2mon.conlog = Conlog(tf2mon.options)

        #
        load_weapons_data(Path(__file__).parent / "data" / "weapons.csv")
        load_racist_data(Path(__file__).parent / "data" / "racist.txt")

        # admin command handlers
        self.regex_list = self.admin_regex_list

        # gameplay handlers
        self.regex_list += self.gameplay_regex_list

        # function key handlers
        self.regex_list += tf2mon.controller.get_regex_list()

        #
        tf2mon.ui = UI(win)
        Users.me = Users.my = Users[tf2mon.config.get("player_name")]
        tf2mon.controller.start()
        tf2mon.reset_game()

        # no need for threads if exiting at end of conlog
        if not tf2mon.options.follow:
            self.conlog_repl()
            return

        # Read from conlog, write to display.
        thread = threading.Thread(name="GAME", target=self.conlog_repl, daemon=True)
        thread.start()

        # main thread reads from keyboard/mouse, and writes to display
        # threading.current_thread().name = "MAIN"
        self.admin_repl()

    def conlog_repl(self):
        """Read the console log file and play game."""

        Database(tf2mon.options.database, [Player, SteamPlayer])
        tf2mon.conlog.open()

        while (line := tf2mon.conlog.readline()) is not None:
            if not line:
                continue

            regex = Regex.search_list(line, self.regex_list)
            if not regex:
                logger.log("ignore", tf2mon.conlog.last_line)
                continue

            self.step(line)
            regex.handler(regex.re_match_obj)
            tf2mon.MsgQueuesControl.send()
            tf2mon.ui.update_display()

    def step(self, line):
        """Involve operator if `line` requires single-step attention.

        Called by the game thread (not the admin thread) for each line read
        from conlog, to wait for the operator (when necessary) before
        processing the line.

        If there's an active single-step pattern and last `line` read from
        con_logfile matches pattern, or if we are single-stepping, then
        wait for admin thread to release the lock.

        Args:
            line: last string read from con_logfile.

        Returns:
            immediately if gate is clear, else waits for it.
        """

        # start single stepping if pattern match
        if (
            not tf2mon.SingleStepControl.is_stepping
            and tf2mon.SingleStepControl.pattern
            and tf2mon.SingleStepControl.pattern.search(line)
        ):
            pattern = tf2mon.SingleStepControl.pattern.pattern
            flags = "i" if (tf2mon.SingleStepControl.pattern.flags & re.IGNORECASE) else ""
            logger.log("ADMIN", f"break search /{pattern}/{flags}")
            tf2mon.SingleStepControl.start_single_stepping()

        level = "nextline" if tf2mon.SingleStepControl.is_stepping else "logline"
        logger.log(level, "-" * 80)
        logger.log(level, tf2mon.conlog.last_line)

        # check gate
        tf2mon.SingleStepControl.wait()
        if tf2mon.SingleStepControl.is_stepping:
            tf2mon.SingleStepControl.clear()

    def admin_repl(self):
        """Admin console read-evaluate-process-loop."""

        while not tf2mon.conlog.is_eof or tf2mon.options.follow:

            tf2mon.ui.update_display()

            prompt = APPNAME
            if tf2mon.SingleStepControl.is_stepping:
                prompt += " (single-stepping)"
            prompt += ": "

            if (line := tf2mon.ui.getline(prompt)) is None:
                logger.log("console", "quit eof")
                return

            if line == "":  # enter
                if tf2mon.conlog.is_eof:
                    logger.log("console", f"lineno={tf2mon.conlog.lineno} <EOF>")
                # else:
                #     logger.trace("step...")
                tf2mon.SingleStepControl.set()
                continue

            logger.log("console", f"line={line!r}")

            cmd = line  # .upper()
            if "quit".find(cmd) == 0:
                logger.log("console", "quit")
                return

            if regex := Regex.search_list(cmd, self.admin_regex_list):
                regex.handler(regex.re_match_obj)
            else:
                logger.error(f"bad admin command {cmd!r}")


Monitor: _Monitor = None
