import pytest
import subprocess
import time
import threading
import os
import sys
from typing import Optional

# Add the src directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))

from mcp_windbg.cdb_session import CDBSession, CDBError, DEFAULT_CDB_PATHS
from mcp_windbg.server import get_or_create_session, unload_session


class CDBServerProcess:
    """Helper class to manage a CDB server process for testing."""

    def __init__(self, port: int = 5005):
        self.port = port
        self.process: Optional[subprocess.Popen] = None
        self.output_lines = []
        self.reader_thread: Optional[threading.Thread] = None
        self.running = False

    def start(self, timeout: int = 10) -> bool:
        """Start the CDB server process."""
        try:
            # Find cdb.exe
            cdb_path = self._find_cdb_executable()
            if not cdb_path:
                raise Exception("Could not find cdb.exe")

            # Use CDB to launch and debug a new instance of CDB
            self.process = subprocess.Popen(
                [cdb_path, "-o", cdb_path],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1
            )

            # Start output reader thread
            self.running = True
            self.reader_thread = threading.Thread(target=self._read_output)
            self.reader_thread.daemon = True
            self.reader_thread.start()

            # Wait for CDB to initialize
            if not self._wait_for_prompt(timeout):
                return False

            # Start the remote server
            server_command = f".server tcp:port={self.port}\n"
            self.process.stdin.write(server_command)
            self.process.stdin.flush()

            # Wait for server to start and check for success message
            start_time = time.time()
            while time.time() - start_time < 5:
                recent_lines = self.output_lines[-10:]
                if any("Server started" in line for line in recent_lines):
                    return True
                time.sleep(0.1)

            return True  # Assume success if we got this far

        except Exception as e:
            print(f"Failed to start CDB server: {e}")
            self.cleanup()
            return False

    def cleanup(self):
        """Clean up the CDB server process."""
        self.running = False

        if self.process and self.process.poll() is None:
            try:
                # Send quit command
                self.process.stdin.write("q\n")
                self.process.stdin.flush()
                self.process.wait(timeout=3)
            except Exception:
                pass

            if self.process.poll() is None:
                self.process.terminate()
                try:
                    self.process.wait(timeout=3)
                except subprocess.TimeoutExpired:
                    self.process.kill()

        if self.reader_thread and self.reader_thread.is_alive():
            self.reader_thread.join(timeout=1)

        self.process = None

    def _find_cdb_executable(self) -> Optional[str]:
        """Find the cdb.exe executable."""
        for path in DEFAULT_CDB_PATHS:
            if os.path.isfile(path):
                return path
        return None

    def _read_output(self):
        """Thread function to read CDB output."""
        if not self.process or not self.process.stdout:
            return

        try:
            for line in self.process.stdout:
                line = line.rstrip()
                self.output_lines.append(line)
                print(f"CDB Server: {line}")  # Debug output
        except Exception as e:
            print(f"CDB server output reader error: {e}")

    def _wait_for_prompt(self, timeout: int) -> bool:
        """Wait for CDB to be ready."""
        start_time = time.time()
        while time.time() - start_time < timeout:
            # Look for the CDB prompt pattern (e.g., "0:000>")
            recent_lines = self.output_lines[-10:]  # Check last 10 lines
            for line in recent_lines:
                if ":000>" in line or "Break instruction exception" in line:
                    return True
            time.sleep(0.1)
        return False


@pytest.mark.skipif(not os.name == 'nt', reason="Windows-only test")
class TestRemoteDebugging:
    """Test cases for remote debugging functionality."""

    def test_remote_debugging_workflow(self):
        """Test the complete remote debugging workflow."""
        server = CDBServerProcess(port=5005)
        connection_string = "tcp:Port=5005,Server=127.0.0.1"

        try:
            # Start the CDB server process
            assert server.start(timeout=15), "Failed to start CDB server process"

            # Test opening remote connection
            session = get_or_create_session(connection_string=connection_string, timeout=10, verbose=True)
            assert session is not None, "Failed to create remote session"

            # Test sending a command
            try:
                output = session.send_command("r")  # Show registers
                assert len(output) > 0, "No output from remote command"
                print(f"Remote command output: {output[:3]}")  # Show first 3 lines
            except CDBError as e:
                # Sometimes the first command might timeout during connection establishment
                print(f"First command failed (this might be expected): {e}")

            # Test that session exists in active sessions
            from mcp_windbg.server import active_sessions
            session_id = f"remote:{connection_string}"
            assert session_id in active_sessions, "Session not found in active sessions"

            # Test closing the remote connection
            success = unload_session(connection_string=connection_string)
            assert success, "Failed to unload remote session"

            # Verify session was removed
            assert session_id not in active_sessions, "Session still exists after unloading"

        finally:
            # Clean up the server process
            server.cleanup()

    def test_remote_connection_validation(self):
        """Test validation of remote connection parameters."""
        # Test that CDBSession validates parameters correctly
        with pytest.raises(ValueError, match="Either dump_path or remote_connection must be provided"):
            CDBSession()

        with pytest.raises(ValueError, match="dump_path and remote_connection are mutually exclusive"):
            CDBSession(dump_path="test.dmp", remote_connection="tcp:Port=5005,Server=127.0.0.1")

    def test_invalid_remote_connection(self):
        """Test handling of invalid remote connections."""
        invalid_connection = "tcp:Port=99999,Server=192.168.255.255"  # Invalid server

        with pytest.raises(CDBError):
            session = CDBSession(remote_connection=invalid_connection, timeout=2)
            # The session creation might succeed but commands should fail
            session.send_command("r")


if __name__ == "__main__":
    # Run a simple test manually
    print("Running remote debugging test...")

    server = CDBServerProcess(port=5005)
    connection_string = "tcp:Port=5005,Server=127.0.0.1"

    try:
        print("Starting CDB server...")
        if server.start(timeout=15):
            print("CDB server started successfully")

            print("Creating remote session...")
            session = get_or_create_session(connection_string=connection_string, timeout=10, verbose=True)

            print("Sending test command...")
            try:
                output = session.send_command("r")
                print(f"Command successful, got {len(output)} lines of output")
            except Exception as e:
                print(f"Command failed: {e}")

            print("Closing remote session...")
            unload_session(connection_string=connection_string)
            print("Test completed successfully!")

        else:
            print("Failed to start CDB server")

    except Exception as e:
        print(f"Test failed: {e}")

    finally:
        print("Cleaning up...")
        server.cleanup()
