"""CH-EXEC-001 — create script → execute → poll completed."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock

import pytest

from server.api.dependencies import get_script_service
from server.api.schemas import ScriptCreate, ScriptExecute
from server.api.scripts import create_script, execute_command, list_executions

pytestmark = pytest.mark.chain

_BG_TASKS: list = []


class _MemRedis:
    def __init__(self) -> None:
        self._data: dict[str, str] = {}

    async def set(self, key: str, value: str, ex: int | None = None) -> None:
        self._data[key] = value

    async def get(self, key: str) -> str | None:
        return self._data.get(key)

    async def delete(self, key: str) -> None:
        self._data.pop(key, None)

    async def rpush(self, key: str, value: str) -> None:
        self._data.setdefault(key, "[]")

    async def lrange(self, key: str, start: int, end: int) -> list:
        return []

    async def sadd(self, key: str, member: str) -> None:
        self._data.setdefault(f"set:{key}", set()).add(member)

    async def srem(self, key: str, member: str) -> None:
        self._data.get(f"set:{key}", set()).discard(member)


class _SessionCtx:
    def __init__(self, session):
        self._session = session

    async def __aenter__(self):
        return self._session

    async def __aexit__(self, exc_type, exc, tb) -> None:
        if exc:
            await self._session.rollback()


def _chain_exec_env(db_session, monkeypatch) -> None:
    mem = _MemRedis()
    monkeypatch.setattr(
        "server.infrastructure.redis.script_execution_store.get_redis",
        lambda: mem,
    )
    monkeypatch.setattr(
        "server.infrastructure.database.session.AsyncSessionLocal",
        lambda: _SessionCtx(db_session),
    )
    monkeypatch.setattr(
        "server.application.services.script_execution_notify.notify_script_progress",
        AsyncMock(),
    )
    monkeypatch.setattr(
        "server.application.services.script_execution_notify.notify_script_complete",
        AsyncMock(),
    )

    async def fake_dispatch(self, server_ids, command, timeout=30, **kwargs):
        return {
            str(sid): {
                "exit_code": 0,
                "stdout": "ok\n",
                "stderr": "",
                "status": "completed",
                "phase": "done",
            }
            for sid in server_ids
        }

    monkeypatch.setattr(
        "server.application.services.script_service.ScriptService._dispatch_to_servers",
        fake_dispatch,
    )

    _BG_TASKS.clear()

    def capture_create_task(coro):
        _BG_TASKS.append(coro)
        return MagicMock()

    monkeypatch.setattr(
        "server.application.services.script_service.asyncio.create_task",
        capture_create_task,
    )


@pytest.mark.asyncio
async def test_create_script_then_list_executions(db_session, test_admin, mock_request):
    mock_request.state.db = db_session
    service = await get_script_service(mock_request)

    created = await create_script(
        ScriptCreate(name="chain-exec-script", content="echo chain", category="ops"),
        mock_request,
        test_admin["admin"],
        service,
        db_session,
    )
    assert created["id"] > 0

    board = await list_executions(
        limit=20,
        offset=0,
        status=None,
        script_id=created["id"],
        admin=test_admin["admin"],
        service=service,
    )
    assert board["total"] >= 0
    assert isinstance(board["items"], list)


@pytest.mark.asyncio
async def test_execute_script_reaches_completed(
    db_session,
    test_admin,
    test_server,
    mock_request,
    monkeypatch,
):
    _chain_exec_env(db_session, monkeypatch)
    mock_request.state.db = db_session
    service = await get_script_service(mock_request)

    created = await create_script(
        ScriptCreate(name="chain-exec-run", content="echo done", category="ops"),
        mock_request,
        test_admin["admin"],
        service,
        db_session,
    )

    started = await execute_command(
        ScriptExecute(script_id=created["id"], server_ids=[test_server.id], timeout=30),
        test_admin["admin"],
        service,
    )
    assert started["status"] == "running"
    assert started["id"] > 0

    assert len(_BG_TASKS) == 1
    await _BG_TASKS[0]

    detail = await service.get_execution_detail(started["id"])
    assert detail is not None
    assert detail["status"] == "completed"
    assert detail["results"][str(test_server.id)]["exit_code"] == 0

    from server.infrastructure.redis import script_execution_store as ses

    live = await ses.load_live(started["id"])
    assert live is not None
    await ses.flush_to_mysql(service.execution_repo, live)
    row = await service.get_execution(started["id"])
    assert row is not None
    assert row.status == "completed"
