import dataclasses
from typing import Callable, List, Optional

import pytest

from taskweaver.ces.common import ExecutionResult, Manager


@dataclasses.dataclass
class RoundSpec:
    id: str
    code: str
    expect_success: bool = True
    output: Optional[str] = None
    stdout: Optional[List[str]] = None
    assessment: Optional[Callable[[ExecutionResult], bool]] = None


@dataclasses.dataclass
class SessionSpec:
    id: str
    round_list: List[RoundSpec] = dataclasses.field(default_factory=list)


spec_def = [
    SessionSpec(
        id="simple_session",
        round_list=[
            RoundSpec(
                id="return_str",
                code="'Hello World!'",
                output="Hello World!",
            ),
            RoundSpec(
                id="return_str_as_var",
                code="result = 'Hello World!'",
                output="Hello World!",
            ),
            RoundSpec(
                id="print_str",
                code="print('Hello World!')",
                output="",
                stdout=["Hello World!\n"],
            ),
        ],
    ),
    SessionSpec(
        id="failed_cases",
        round_list=[
            RoundSpec(
                id="syntax_error",
                code="Hello World!",
                expect_success=False,
                assessment=lambda r: r.error is not None and "SyntaxError" in r.error,
            ),
            RoundSpec(
                id="syntax_error_2",
                code="[1, 2, {",
                expect_success=False,
                assessment=lambda r: r.error is not None and "SyntaxError" in r.error,
            ),
            RoundSpec(
                id="not_defined",
                code="Hello_World",
                output="",
                expect_success=False,
                assessment=lambda r: r.error is not None and "NameError" in r.error,
            ),
        ],
    ),
]


@pytest.mark.parametrize(
    "session_spec",
    spec_def,
)
def test_ces_session(ces_manager: Manager, session_spec: SessionSpec):
    session = ces_manager.get_session_client(session_spec.id)

    session.start()
    for round in session_spec.round_list:
        result: ExecutionResult = session.execute_code(round.id, round.code)

        if not result.is_success:
            assert result.error is not None, "Expecting error message to be present"

        assert (
            result.is_success if round.expect_success else not result.is_success
        ), f"Expecting execution to be {'successful' if round.expect_success else 'unsuccessful'}"

        if round.output is not None:
            assert result.output == round.output, "Expecting output to match"

        if round.stdout is not None:
            for line, comp in enumerate(zip(result.stdout, round.stdout)):
                assert comp[0] == comp[1], f"Expecting stdout line {line + 1} to match: {comp[0]} != {comp[1]}"

        if round.assessment is not None:
            assert round.assessment(result), "Expecting assessment to pass"

    session.stop()
