from __future__ import annotations

from dataclasses import InitVar, dataclass, fields  # NOQA
from io import BytesIO
from textwrap import dedent
from typing import ClassVar, Union


class TestDataClasses:
    def test_1(self) -> None:
        from ruyaml import YAML

        yaml = YAML()

        @yaml.register_class
        @dataclass
        class DC:
            abc: int
            klm: int
            xyz: int = 0

            def __post_init__(self) -> None:
                self.xyz = self.abc + self.klm

        dc = DC(abc=5, klm=42)
        assert dc.xyz == 47

        yaml_str = dedent(
            """\
        !DC
        abc: 13
        klm: 37
        """
        )
        dc2 = yaml.load(yaml_str)
        assert dc2.xyz == 50

    def test_yamltag(self) -> None:
        from ruyaml import YAML

        yaml = YAML()

        @yaml.register_class
        @dataclass
        class DC:
            yaml_tag: ClassVar = '!dc_example'
            abc: int
            klm: int

        dc = DC(abc=5, klm=42)
        buf = BytesIO()
        yaml.dump(dc, buf)
        assert (
            buf.getvalue()
            == dedent(
                """\
        !dc_example
        abc: 5
        klm: 42
        """
            ).encode('utf-8')
        )
        dc2 = yaml.load(buf.getvalue())
        assert len(fields(dc2)) == 2  # class var is not a field
        assert dc2.abc == dc.abc
        assert dc2.klm == dc.klm

    def test_initvar(self) -> None:
        from ruyaml import YAML

        yaml = YAML()

        @yaml.register_class
        @dataclass
        class DC:
            abc: int
            klm: int
            xyz: InitVar[Union[str, None]] = None

            def __post_init__(self, xyz: Union[str, None]) -> None:
                # assert xyz == self.xyz  # self.xyz is always None
                if xyz is not None:
                    self.klm += len(xyz)

        dc = DC(abc=5, klm=42, xyz='provided')
        # this actually doesn't raise an attribute error, I would have expected it not to work
        # at all, but it has the default value
        assert dc.xyz is None  # type: ignore
        buf = BytesIO()
        yaml.dump(dc, buf)
        assert (
            buf.getvalue()
            == dedent(
                """\
        !DC
        abc: 5
        klm: 50
        """
            ).encode('utf-8')
        )

        yaml_str = dedent(
            """\
        !DC
        abc: 18
        klm: 55
        xyz: some string
        """
        )
        dc2 = yaml.load(yaml_str)
        assert dc2.xyz is None
        assert dc2.klm == 55 + len('some string')

    def test_initvar_not_in_yaml(self) -> None:
        from ruyaml import YAML

        yaml = YAML()

        @yaml.register_class
        @dataclass
        class DC:
            abc: int
            klm: int
            xyz: InitVar[Union[str, None]] = 'hello'

            def __post_init__(self, xyz: Union[str, None]) -> None:
                # assert xyz == self.xyz  # self.xyz is always None
                if xyz is not None:
                    self.klm += len(xyz)

        dc = DC(abc=5, klm=42, xyz='provided')
        assert dc.abc == 5
        assert dc.xyz == 'hello'  # type: ignore
        buf = BytesIO()
        yaml.dump(dc, buf)
        assert (
            buf.getvalue()
            == dedent(
                """\
        !DC
        abc: 5
        klm: 50
        """
            ).encode('utf-8')
        )

        yaml_str = dedent(
            """\
        !DC
        abc: 18
        klm: 55
        """
        )
        dc2 = yaml.load(yaml_str)
        assert dc2.xyz == 'hello'
        assert dc2.klm == 55 + len('hello')

    def test_collection_field(self) -> None:
        # https://stackoverflow.com/a/77485786/1307905
        from dataclasses import dataclass

        import ruyaml

        @dataclass
        class Msg:
            id: int
            desc: str
            fields: list[Field]

            def __post_init__(self) -> None:
                idx: int = 0
                for field in self.fields:  # why is this empty??
                    field.index = idx
                    idx += field.size

        @dataclass
        class Field:
            id: int
            name: str
            units: str
            size: int
            index: int = -1

        yaml = ruyaml.YAML()
        yaml.register_class(Msg)
        yaml.register_class(Field)

        msg: Msg = yaml.load(
            """\
        !Msg
        id: 1
        desc: status
        fields:
        - !Field
            id: 1
            name: Temp
            units: degC
            size: 2
        """
        )

        assert msg.fields[0].index != -1
