"""
Hand crafted classes which should undoubtedly be autogenerated from the schema.
"""
from __future__ import annotations

import attrs

from bowtie import exceptions


@attrs.define
class Test:

    description: str
    instance: object
    valid: bool | None = None


@attrs.define
class TestCase:

    description: str
    schema: object
    tests: list[Test]
    comment: str | None = None

    @classmethod
    def from_dict(cls, data):
        data["tests"] = [Test(**test) for test in data["tests"]]
        return cls(**data)

    def without_expected_results(self):
        as_dict = {
            "tests": [
                attrs.asdict(test, filter=lambda k, _: k.name != "valid")
                for test in self.tests
            ],
        }
        as_dict.update(
            attrs.asdict(
                self,
                filter=lambda k, v: k.name != "tests" and (
                    k.name != "comment" or v is not None
                ),
            ),
        )
        return as_dict


@attrs.define
class Started:

    succeeded = True

    implementation: dict
    ready: bool = False
    version: int = None


@attrs.define
class Start:

    cmd = "start"
    Response = Started

    version: int

    def succeed(self, response):
        if not response.succeeded:
            raise exceptions.StartupFailure(self, response.stderr)
        elif not response.ready:
            raise exceptions.ImplementationNotReady(self)
        else:
            version = response.version
            if version != 1:
                raise exceptions.VersionMismatch(self, expected=1, got=version)

        return response.implementation


START_V1 = Start(version=1)


@attrs.define
class StartedDialect:

    succeeded = True

    ok: bool


StartedDialect.OK = StartedDialect(ok=True)


@attrs.define
class Dialect:

    cmd = "dialect"
    Response = StartedDialect

    dialect: str

    def succeed(self, response):
        return response


def _case_result(errored=False, **response):
    if errored:
        return lambda implementation, expected: CaseErrored(
            implementation=implementation,
            **response,
        )
    return lambda implementation, expected: CaseResult(
        implementation=implementation,
        expected=expected,
        **response,
    )


@attrs.define
class CaseResult:
    """
    The result of running a test case.
    """

    succeeded = True

    implementation: str
    seq: int
    results: dict
    expected: list[bool | None]

    def report(self, reporter):
        reporter.got_results(self)


@attrs.define
class CaseErrored:

    succeeded = False

    implementation: str
    seq: int
    context: dict

    def report(self, reporter):
        return reporter.errored(self.implementation, self.context)


@attrs.define
class Run:

    cmd = "run"
    Response = staticmethod(_case_result)

    seq: int
    case: dict

    def succeed(self, response):
        return response


@attrs.define
class Stop:

    cmd = "stop"

    def Response(self, **kwargs):
        # FIXME: Ugh, the last response... we're off by one.
        if kwargs:
            return _case_result(**kwargs)

    def succeed(self, response):
        return response


STOP = Stop()


def to_request(command):
    as_dict = {"cmd": command.cmd}
    as_dict.update(attrs.asdict(command))
    return as_dict
