"""
MineConnect Server — Main server orchestration class
"""

import subprocess
import sys
import os
import time
import signal
import threading
import logging
from typing import Optional, List, Tuple

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)


class MineConnectServer:
    """
    Main server class that orchestrates TCP/UDP bridges and cloudflared tunnels.
    
    Usage:
        server = MineConnectServer(tcp_port=25565, udp_port=19132)
        server.start()  # Blocks until stopped with Ctrl+C
    """
    
    def __init__(self, tcp_port: int = 25565, udp_port: int = 19132, 
                 single_tunnel: bool = False, ws_tcp_port: int = 8764, 
                 ws_udp_port: int = 8765):
        """
        Initialize MineConnect server configuration.
        
        Args:
            tcp_port: Minecraft Java Edition server port (default: 25565)
            udp_port: Geyser/Bedrock server port (default: 19132)
            single_tunnel: Use one tunnel with two ingress rules (default: False)
            ws_tcp_port: WebSocket port for TCP bridge (default: 8764)
            ws_udp_port: WebSocket port for UDP bridge (default: 8765)
        """
        self.tcp_port = tcp_port
        self.udp_port = udp_port
        self.single_tunnel = single_tunnel
        self.ws_tcp_port = ws_tcp_port
        self.ws_udp_port = ws_udp_port
        
        self.logger = logging.getLogger("mineconnect-server")
        self._children: List[Tuple[str, subprocess.Popen]] = []
        self._running = False
        
        # Get package directory for config files
        self.package_dir = os.path.dirname(os.path.abspath(__file__))
        
    def _launch(self, name: str, cmd: List[str], cwd: Optional[str] = None) -> Optional[subprocess.Popen]:
        """Launch a subprocess and attach logging."""
        self.logger.info("Starting %s: %s", name, " ".join(cmd))
        try:
            proc = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                cwd=cwd,
            )
        except FileNotFoundError as exc:
            self.logger.error("Cannot start %s: %s", name, exc)
            return None

        self._children.append((name, proc))

        def _reader():
            for line in proc.stdout:
                self.logger.info("[%s] %s", name, line.rstrip())

        threading.Thread(target=_reader, daemon=True).start()
        return proc

    def _shutdown(self, *_):
        """Gracefully shutdown all services."""
        if not self._running:
            return
            
        self.logger.info("Shutting down all services…")
        self._running = False
        
        for name, proc in self._children:
            self.logger.info("  Stopping %s", name)
            proc.terminate()
            
        for name, proc in self._children:
            try:
                proc.wait(timeout=5)
            except subprocess.TimeoutExpired:
                proc.kill()
                
        self._children.clear()

    def start(self):
        """
        Start the MineConnect server with all bridges and tunnels.
        
        This method blocks until interrupted with Ctrl+C or terminated.
        Logs are displayed to the console in real-time.
        """
        self.logger.info("=" * 60)
        self.logger.info("  MineConnect Server Starting")
        self.logger.info("  TCP Port: %d | UDP Port: %d", self.tcp_port, self.udp_port)
        self.logger.info("  Single Tunnel Mode: %s", self.single_tunnel)
        self.logger.info("=" * 60)
        
        # Register signal handlers
        signal.signal(signal.SIGINT, self._shutdown)
        signal.signal(signal.SIGTERM, self._shutdown)
        
        self._running = True
        py = sys.executable

        try:
            # 1) TCP<->WS bridge for Java Edition
            tcp_bridge_path = os.path.join(self.package_dir, "tcp_bridge.py")
            self._launch("TCP-WS-Bridge", [
                py, tcp_bridge_path,
                "--ws-port", str(self.ws_tcp_port),
                "--mc-port", str(self.tcp_port),
            ])

            # 2) UDP<->WS bridge for Bedrock Edition
            udp_bridge_path = os.path.join(self.package_dir, "udp_bridge.py")
            self._launch("UDP-WS-Bridge", [
                py, udp_bridge_path,
                "--ws-port", str(self.ws_udp_port),
                "--geyser-port", str(self.udp_port),
            ])

            time.sleep(1)  # Let bridges initialize

            # 3) Cloudflared tunnel(s)
            # Run from package directory where configs and creds are located
            if self.single_tunnel:
                cfg = os.path.join(self.package_dir, "tunnel_combined.yml")
                self._launch("Cloudflared", ["cloudflared", "tunnel", "--config", cfg, "run"], cwd=self.package_dir)
            else:
                java_cfg = os.path.join(self.package_dir, "tunnel_java.yml")
                bedrock_cfg = os.path.join(self.package_dir, "tunnel_bedrock.yml")
                
                self._launch("Cloudflared-Java", [
                    "cloudflared", "tunnel",
                    "--config", java_cfg,
                    "run",
                ], cwd=self.package_dir)
                self._launch("Cloudflared-Bedrock", [
                    "cloudflared", "tunnel",
                    "--config", bedrock_cfg,
                    "run",
                ], cwd=self.package_dir)

            self.logger.info("=" * 60)
            self.logger.info("  All services running. Press Ctrl+C to stop.")
            self.logger.info("=" * 60)

            # Monitor processes
            while self._running:
                for name, proc in self._children:
                    if proc.poll() is not None:
                        self.logger.warning("%s exited (code %s)", name, proc.returncode)
                        if self._running:
                            self.logger.error("Service crashed! Initiating shutdown...")
                            self._shutdown()
                            break
                time.sleep(2)
                
        except Exception as exc:
            self.logger.error("Fatal error: %s", exc)
            self._shutdown()
            raise
        finally:
            if self._running:
                self._shutdown()
            self.logger.info("MineConnect server stopped.")

    def stop(self):
        """Manually stop the server."""
        self._shutdown()
