import pytest
from click.testing import CliRunner

from skop_runner.config import RunnerConfig
from skop_runner.__main__ import cli


@pytest.fixture
def cli_runner():
    return CliRunner()


@pytest.fixture
def config_factory(monkeypatch, tmp_path_factory):
    def _create(device_token: str | None):
        cfg_dir = tmp_path_factory.mktemp("config")
        config = RunnerConfig(
            device_token=device_token,
            api_url="http://api.local",
            ws_url="ws://ws.local/runner",
            log_level="INFO",
            config_dir=cfg_dir,
        )

        save_called = {"value": False}

        def save_override():
            save_called["value"] = True

        config.save = save_override  # type: ignore[attr-defined]
        config._save_called = save_called  # type: ignore[attr-defined]

        monkeypatch.setattr(
            "skop_runner.__main__.RunnerConfig.load",
            classmethod(lambda cls, _cfg=config: _cfg),
        )
        return config

    return _create


def test_login_validates_and_saves_token(cli_runner: CliRunner, config_factory, monkeypatch):
    config_factory(device_token=None)
    events: dict[str, str] = {}

    async def fake_validate(token: str, config: RunnerConfig):
        events["validated"] = token
        return {"user": {"email": "user@example.com"}, "runnerId": "runner_123"}

    def fake_save(token: str, config: RunnerConfig):
        events["saved"] = token

    monkeypatch.setattr("skop_runner.__main__.validate_token_with_token", fake_validate)
    monkeypatch.setattr("skop_runner.__main__.save_token", fake_save)

    result = cli_runner.invoke(cli, ["login", "--token", "tok_abc"])

    assert result.exit_code == 0
    assert events["validated"] == "tok_abc"
    assert events["saved"] == "tok_abc"


def test_start_requires_device_token(cli_runner: CliRunner, config_factory):
    config_factory(device_token=None)

    result = cli_runner.invoke(cli, ["start"])

    assert result.exit_code != 0
    assert "Not authenticated" in result.output


def test_start_runs_runner_client(cli_runner: CliRunner, config_factory, monkeypatch):
    config_factory(device_token="tok-valid")

    backend_instances = []

    class StubJupyterBackend:
        def __init__(self, **kwargs):
            self.close_called = False
            backend_instances.append(self)

        async def initialize(self, **kwargs):
            pass

        async def close(self, **kwargs):
            self.close_called = True

    class StubFileManager:
        def __init__(self, **kwargs):
            pass

    class StubSessionManager:
        async def load_from_disk(self):
            return 0

    class StubMessageHandler:
        def __init__(self, kernel_backend, file_manager, **kwargs):
            self.kernel_backend = kernel_backend
            self.file_manager = file_manager
            self.session_manager = StubSessionManager()

        def set_notification_sender(self, sender):
            self.notification_sender = sender

    class StubLocalWebSocketServer:
        def __init__(self, message_handler):
            self.token = "stub-token-123"

        async def start(self):
            return 9999

        async def stop(self):
            pass

        async def _broadcast_notification(self, method, params):
            pass

    client_instances = []

    class StubRunnerClient:
        def __init__(self, config: RunnerConfig, handler, output_buffer=None):
            self.config = config
            self.handler = handler
            self.on_connect = None
            self.run_called = False
            self.disconnect_called = False
            self.running = True
            self._metadata_supplier = None
            client_instances.append(self)

        async def _send_notification(self, method, params):
            pass

        async def run_with_reconnect(self):
            self.run_called = True
            if self.on_connect:
                self.on_connect()

        async def disconnect(self):
            self.disconnect_called = True

    monkeypatch.setattr("skop_runner.__main__.JupyterBackend", StubJupyterBackend)
    monkeypatch.setattr("skop_runner.__main__.FileManager", StubFileManager)
    monkeypatch.setattr("skop_runner.__main__.MessageHandler", StubMessageHandler)
    monkeypatch.setattr("skop_runner.__main__.RunnerClient", StubRunnerClient)
    monkeypatch.setattr("skop_runner.__main__.LocalWebSocketServer", StubLocalWebSocketServer)

    result = cli_runner.invoke(cli, ["start"])

    assert result.exit_code == 0
    assert client_instances, "RunnerClient was not instantiated"
    client = client_instances[0]
    assert client.run_called, "run_with_reconnect was not awaited"
    assert client.disconnect_called, "disconnect was not awaited"
    assert backend_instances[0].close_called


def test_logout_clears_token_and_saves(cli_runner: CliRunner, config_factory):
    config = config_factory(device_token="tok-saved")

    result = cli_runner.invoke(cli, ["logout"])

    assert result.exit_code == 0
    assert config.device_token is None
    assert config._save_called["value"] is True  # type: ignore[attr-defined]
