import os
import random
import subprocess
import sys
import time
from threading import Thread
from typing import (
    Optional,
    List,
)

import msgbus
from msgbus import (
    MBContext,
    MBService,
    MBException,
)


class LocalServiceLocator(object):
    def __init__(
        self,
        address: str = "127.0.0.1",
        port: int = 16000,
        config: Optional[str] = None,
        log_config: Optional[str] = None,
        comp_dir: str = None,
        python_path: Optional[List[str]] = None,
        main_path: Optional[str] = None,
        debug_streams: bool = False,
    ):

        self.address = address
        self.port = port
        self.config = config
        self.log_config = log_config
        self.debug_streams = debug_streams
        self.comp_dir = comp_dir
        if self.comp_dir is None:
            self.comp_dir = os.path.dirname(os.path.abspath(__file__))
        self.main_path = main_path or "main.py"
        self.python_path = python_path or []

        self.process: Optional[subprocess.Popen] = None
        self.stdout_thread = None
        self.stderr_thread = None
        self.stdout_content = bytearray()
        self.stderr_content = bytearray()

    def start(self):
        env = {
            "LOADERCFG": "{}",
            "LOG_PATH": "{}",
            "HVIP": self.address,
            "HVPORT": str(self.port),
        }
        if len(self.python_path) > 0:
            env_python_path = []
            for src_folder in self.python_path:
                env_python_path.append(os.path.join(self.comp_dir, src_folder))

            env_python_path = os.pathsep.join(env_python_path)
            env["PYTHONPATH"] = env_python_path

        if sys.platform == "win32":
            env["SystemRoot"] = os.environ["SystemRoot"]

        stdout = stderr = subprocess.DEVNULL
        if self.debug_streams:
            stdout = stderr = subprocess.PIPE

        self.process = subprocess.Popen(
            args=[sys.executable, os.path.join(self.comp_dir, self.main_path)],
            stdout=stdout,
            stderr=stderr,
            env=env,
        )

        if self.debug_streams:
            self.stdout_thread = Thread(target=read_stream, args=(self.process.stdout, self.stdout_content))
            self.stderr_thread = Thread(target=read_stream, args=(self.process.stderr, self.stderr_content))

            self.stdout_thread.start()
            self.stderr_thread.start()

        try:
            wait_process_running(
                comp_address=self.address,
                comp_port=self.port,
            )
        except (Exception,):
            self.stop()
            raise

    def stop(self) -> Optional[int]:
        try:
            stop_component(comp_port=self.port)

            return self.process.wait(timeout=10)
        except (Exception,):
            self.process.terminate()
            raise
        finally:
            if self.debug_streams:
                print("\nService Locator stdout:")
                stdout = self.stdout_content.decode()
                for line in stdout.splitlines():
                    print(line)

                print("\nService Locator stderr:")
                stdout = self.stderr_content.decode()
                for line in stdout.splitlines():
                    print(line)


def read_stream(
    stream,
    buffer: bytearray,
):
    read = stream.read(100)
    try:
        while read != b"":
            buffer += read
            read = stream.read(100)
        stream.close()
    except (Exception,):
        pass


def wait_process_running(comp_address: str, comp_port: int):
    random_port = random.randint(30000, 65535)
    service = MBService("127.0.0.1", random_port)
    mb_context = MBContext(
        my_service=service,
        name="CompWaiter"
    )

    other_service = MBService(
        address=comp_address,
        port=comp_port,
    )

    for _ in range(10):
        try:
            reply = mb_context.MB_SendMessage(
                destination=other_service,
                token=msgbus.TK_SYS_HEALTH_CHECK,
            )
            break
        except MBException:
            time.sleep(0.05)
    else:
        raise RuntimeError("Timeout waiting for component to start.")

    if reply.token != msgbus.TK_SYS_ACK:
        raise RuntimeError("Error on TK_SYS_HEALTH_CHECK")

    mb_context.MB_Finalize()

def stop_component(comp_port: int):
    random_port = random.randint(30000, 65535)
    service = MBService("127.0.0.1", random_port)
    mb_context = MBContext(
        my_service=service,
        name="CompStopper"
    )

    other_service = MBService("127.0.0.1", comp_port)

    reply = mb_context.MB_SendMessage(
        destination=other_service,
        token=msgbus.TK_CMP_TERM_NOW,
        format=msgbus.FM_PARAM,
    )
    if reply.token != msgbus.TK_CMP_TERM_NOW:
        raise RuntimeError("Error sending TK_CMP_TERM_NOW")

    mb_context.MB_Finalize()
