"""
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
    registry: dict | None = None

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

    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 not in {"comment", "registry"} or v is not None),
            ),
        )
        return as_dict


@attrs.define
class Started:

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

    def __attrs_post_init__(self):
        if not self.ready:
            raise exceptions.ImplementationNotReady(self)
        if self.version != 1:
            raise exceptions.VersionMismatch(expected=1, got=self.version)


def command(name, Response):

    request_schema = {"$ref": f"#/$defs/command/$defs/{name}"}
    response_schema = {"$ref": f"#/$defs/command/$defs/{name}/$defs/response"}

    def _command(cls):
        def to_request(self, validate):
            request = dict(cmd=name, **attrs.asdict(self))
            validate(instance=request, schema=request_schema)
            return request

        def from_response(self, response, validate):
            validate(instance=response, schema=response_schema)
            return Response(**response)

        cls.to_request = to_request
        cls.from_response = from_response
        return attrs.define(cls)

    return _command


@command(name="start", Response=Started)
class Start:

    version: int


START_V1 = Start(version=1)


@attrs.define
class StartedDialect:

    ok: bool


StartedDialect.OK = StartedDialect(ok=True)


@command(name="dialect", Response=StartedDialect)
class Dialect:

    dialect: str


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:

    errored = False

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

    @property
    def failed(self):
        return any(
            got.get("valid") != expected and expected is not None
            for got, expected in zip(self.results, self.expected)
        )

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


@attrs.define
class CaseErrored:

    errored = True

    implementation: str
    seq: int
    context: dict

    caught: bool = True

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

    @classmethod
    def uncaught(cls, implementation, seq, **context):
        return cls(
            implementation=implementation,
            seq=seq,
            caught=False,
            context=context,
        )


@attrs.define
class Empty:
    """
    An implementation didn't send a response.
    """

    errored = True

    implementation: str

    def report(self, reporter):
        reporter.no_response(implementation=self.implementation)


@command(name="run", Response=_case_result)
class Run:

    seq: int
    case: dict


@command(name="stop", Response=lambda: None)
class Stop:
    pass


STOP = Stop()
