import shlex
from collections import deque
from os import setsid
from pathlib import Path
from subprocess import PIPE, Popen
from time import sleep
from typing import Generator

from mcrcon import MCRcon
from mcstatus import JavaServer
from psutil import NoSuchProcess, Process

from .classes import ServerStatus
from .config import Config


class ProcessController:
    def __init__(self):
        self.pid_file: Path = Path(Config.PID_FILE)
        self.start_command: list[str] = shlex.split(Config.START_COMMAND)
        self.process: Process | None = None

        if self.pid_file.is_file():
            pid = int(self.pid_file.read_text())
            if self._is_pid_alive(pid):
                self.process = Process(pid)
            else:
                self.pid_file.unlink()

    def start(self) -> None:
        "Starts the process."
        if self.is_started():
            return

        process = Popen(
            self.start_command,
            stdout=PIPE,
            stderr=PIPE,
            preexec_fn=setsid,
        )
        self.process = Process(process.pid)
        self.pid_file.write_text(str(self.process.pid))

    def is_started(self) -> bool:
        "Check if the process is running."
        if self.process:
            return self.process.is_running()
        return False

    def is_crashed(self) -> bool:
        "Check if the process has crashed."
        return False  # TODO

    def kill(self) -> None:
        "Kill the process."
        if self.process:
            self.process.terminate()
            self.process = None
            self.pid_file.unlink()

    def _is_pid_alive(self, pid: int) -> bool:
        "Check if a process with the given PID is alive."
        try:
            p = Process(pid)
            return p.is_running()
        except NoSuchProcess:
            return False


class ServerController:
    def __init__(self):
        self.server: JavaServer = JavaServer(
            Config.SERVER_HOST,
            Config.SERVER_PORT,
        )
        self.rcon = MCRcon(
            Config.SERVER_HOST,
            Config.SERVER_RCON_PASSWORD,
            Config.SERVER_RCON_PORT,
        )

    def status(self) -> ServerStatus:
        try:
            status = self.server.status()
        except Exception:
            return {"online": False}

        players = []
        if status.players.sample:
            for player in status.players.sample:
                players.append(player.name)

        return {
            "online": True,
            "icon": status.icon,
            "motd": status.motd.to_html(),
            "players": {
                "online": status.players.online,
                "max": status.players.max,
                "list": players,
            },
        }

    def command(self, command: str) -> str:
        self.rcon.connect()
        output = self.rcon.command(command)
        self.rcon.disconnect()
        return output


class MaintainanceController:
    def __init__(self):
        self.mnt_file = Path(Config.MAINTAINANCE_FILE)

    def set(self, reason: str):
        self.mnt_file.write_text(reason)

    def is_set(self) -> bool:
        return self.mnt_file.is_file()

    def get(self) -> str:
        if self.is_set():
            return self.mnt_file.read_text()
        return ""

    def unset(self):
        if self.is_set():
            self.mnt_file.unlink()


class LogsController:
    def __init__(self):
        self.log_file = Path(Config.LOG_FILE)

    def stream(self):
        with self.log_file.open() as f:
            f.seek(0, 2)
            while True:
                line = f.readline()
                if line:
                    yield line
                else:
                    sleep(0.1)

    def tail(self, back: int = 10):
        with self.log_file.open() as f:
            for line in deque(f, maxlen=back):
                yield line


class Controllers:
    process = ProcessController()
    server = ServerController()
    maintainance = MaintainanceController()
    logs = LogsController()
