from __future__ import annotations

import copy
from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
from typing import Any

from .list import CruUniqueKeyList
from ._type import CruTypeSet
from ._const import CruNotFound, CruUseDefault, CruDontChange
from ._iter import CruIterator


@dataclass
class CruAttr:

    name: str
    value: Any
    description: str | None

    @staticmethod
    def make(
        name: str, value: Any = CruUseDefault.VALUE, description: str | None = None
    ) -> CruAttr:
        return CruAttr(name, value, description)


CruAttrDefaultFactory = Callable[["CruAttrDef"], Any]
CruAttrTransformer = Callable[[Any, "CruAttrDef"], Any]
CruAttrValidator = Callable[[Any, "CruAttrDef"], None]


@dataclass
class CruAttrDef:
    name: str
    description: str
    default_factory: CruAttrDefaultFactory
    transformer: CruAttrTransformer
    validator: CruAttrValidator

    def __init__(
        self,
        name: str,
        description: str,
        default_factory: CruAttrDefaultFactory,
        transformer: CruAttrTransformer,
        validator: CruAttrValidator,
    ) -> None:
        self.name = name
        self.description = description
        self.default_factory = default_factory
        self.transformer = transformer
        self.validator = validator

    def transform(self, value: Any) -> Any:
        if self.transformer is not None:
            return self.transformer(value, self)
        return value

    def validate(self, value: Any, /, force_allow_none: bool = False) -> None:
        if force_allow_none is value is None:
            return
        if self.validator is not None:
            self.validator(value, self)

    def transform_and_validate(
        self, value: Any, /, force_allow_none: bool = False
    ) -> Any:
        value = self.transform(value)
        self.validate(value, force_allow_none)
        return value

    def make_default_value(self) -> Any:
        return self.transform_and_validate(self.default_factory(self))

    def adopt(self, attr: CruAttr) -> CruAttr:
        attr = copy.deepcopy(attr)

        if attr.name is None:
            attr.name = self.name
        elif attr.name != self.name:
            raise ValueError(f"Attr name is not match: {attr.name} != {self.name}")

        if attr.value is CruUseDefault.VALUE:
            attr.value = self.make_default_value()
        else:
            attr.value = self.transform_and_validate(attr.value)

        if attr.description is None:
            attr.description = self.description

        return attr

    def make(
        self, value: Any = CruUseDefault.VALUE, description: None | str = None
    ) -> CruAttr:
        value = self.make_default_value() if value is CruUseDefault.VALUE else value
        value = self.transform_and_validate(value)
        return CruAttr(
            self.name,
            value,
            description if description is not None else self.description,
        )


@dataclass
class CruAttrDefBuilder:

    name: str
    description: str
    types: list[type] | None = field(default=None)
    allow_none: bool = field(default=False)
    default: Any = field(default=CruUseDefault.VALUE)
    default_factory: CruAttrDefaultFactory | None = field(default=None)
    auto_list: bool = field(default=False)
    transformers: list[CruAttrTransformer] = field(default_factory=list)
    validators: list[CruAttrValidator] = field(default_factory=list)
    override_transformer: CruAttrTransformer | None = field(default=None)
    override_validator: CruAttrValidator | None = field(default=None)

    build_hook: Callable[[CruAttrDef], None] | None = field(default=None)

    def __init__(self, name: str, description: str) -> None:
        super().__init__()
        self.name = name
        self.description = description

    def auto_adjust_default(self) -> None:
        if self.default is not CruUseDefault.VALUE and self.default is not None:
            return
        if self.allow_none and self.default is CruUseDefault.VALUE:
            self.default = None
        if not self.allow_none and self.default is None:
            self.default = CruUseDefault.VALUE
        if self.auto_list and not self.allow_none:
            self.default = []

    def with_name(self, name: str | CruDontChange) -> CruAttrDefBuilder:
        if name is not CruDontChange.VALUE:
            self.name = name
        return self

    def with_description(
        self, default_description: str | CruDontChange
    ) -> CruAttrDefBuilder:
        if default_description is not CruDontChange.VALUE:
            self.description = default_description
        return self

    def with_default(self, default: Any) -> CruAttrDefBuilder:
        if default is not CruDontChange.VALUE:
            self.default = default
        return self

    def with_default_factory(
        self,
        default_factory: CruAttrDefaultFactory | CruDontChange,
    ) -> CruAttrDefBuilder:
        if default_factory is not CruDontChange.VALUE:
            self.default_factory = default_factory
        return self

    def with_types(
        self,
        types: Iterable[type] | None | CruDontChange,
    ) -> CruAttrDefBuilder:
        if types is not CruDontChange.VALUE:
            self.types = None if types is None else list(types)
        return self

    def with_allow_none(self, allow_none: bool | CruDontChange) -> CruAttrDefBuilder:
        if allow_none is not CruDontChange.VALUE:
            self.allow_none = allow_none
        return self

    def with_auto_list(
        self, auto_list: bool | CruDontChange = True
    ) -> CruAttrDefBuilder:
        if auto_list is not CruDontChange.VALUE:
            self.auto_list = auto_list
        return self

    def with_constraint(
        self,
        /,
        allow_none: bool | CruDontChange = CruDontChange.VALUE,
        types: Iterable[type] | None | CruDontChange = CruDontChange.VALUE,
        default: Any = CruDontChange.VALUE,
        default_factory: CruAttrDefaultFactory | CruDontChange = CruDontChange.VALUE,
        auto_list: bool | CruDontChange = CruDontChange.VALUE,
    ) -> CruAttrDefBuilder:
        return (
            self.with_allow_none(allow_none)
            .with_types(types)
            .with_default(default)
            .with_default_factory(default_factory)
            .with_auto_list(auto_list)
        )

    def add_transformer(self, transformer: CruAttrTransformer) -> CruAttrDefBuilder:
        self.transformers.append(transformer)
        return self

    def clear_transformers(self) -> CruAttrDefBuilder:
        self.transformers.clear()
        return self

    def add_validator(self, validator: CruAttrValidator) -> CruAttrDefBuilder:
        self.validators.append(validator)
        return self

    def clear_validators(self) -> CruAttrDefBuilder:
        self.validators.clear()
        return self

    def with_override_transformer(
        self, override_transformer: CruAttrTransformer | None | CruDontChange
    ) -> CruAttrDefBuilder:
        if override_transformer is not CruDontChange.VALUE:
            self.override_transformer = override_transformer
        return self

    def with_override_validator(
        self, override_validator: CruAttrValidator | None | CruDontChange
    ) -> CruAttrDefBuilder:
        if override_validator is not CruDontChange.VALUE:
            self.override_validator = override_validator
        return self

    def is_valid(self) -> tuple[bool, str]:
        if not isinstance(self.name, str):
            return False, "Name must be a string!"
        if not isinstance(self.description, str):
            return False, "Default description must be a string!"
        if (
            not self.allow_none
            and self.default is None
            and self.default_factory is None
        ):
            return False, "Default must be set if allow_none is False!"
        return True, ""

    @staticmethod
    def _build(
        builder: CruAttrDefBuilder, auto_adjust_default: bool = True
    ) -> CruAttrDef:
        if auto_adjust_default:
            builder.auto_adjust_default()

        valid, err = builder.is_valid()
        if not valid:
            raise ValueError(err)

        def composed_transformer(value: Any, attr_def: CruAttrDef) -> Any:
            def transform_value(single_value: Any) -> Any:
                for transformer in builder.transformers:
                    single_value = transformer(single_value, attr_def)
                return single_value

            if builder.auto_list:
                if not isinstance(value, list):
                    value = [value]
                value = CruIterator(value).transform(transform_value).to_list()

            else:
                value = transform_value(value)
            return value

        type_set = None if builder.types is None else CruTypeSet(*builder.types)

        def composed_validator(value: Any, attr_def: CruAttrDef):
            def validate_value(single_value: Any) -> None:
                if type_set is not None:
                    type_set.check_value(single_value, allow_none=builder.allow_none)
                for validator in builder.validators:
                    validator(single_value, attr_def)

            if builder.auto_list:
                CruIterator(value).foreach(validate_value)
            else:
                validate_value(value)

        real_transformer = builder.override_transformer or composed_transformer
        real_validator = builder.override_validator or composed_validator

        default_factory = builder.default_factory
        if default_factory is None:

            def default_factory(_d):
                return copy.deepcopy(builder.default)

        d = CruAttrDef(
            builder.name,
            builder.description,
            default_factory,
            real_transformer,
            real_validator,
        )
        if builder.build_hook:
            builder.build_hook(d)
        return d

    def build(self, auto_adjust_default=True) -> CruAttrDef:
        c = copy.deepcopy(self)
        self.build_hook = None
        return CruAttrDefBuilder._build(c, auto_adjust_default)


class CruAttrDefRegistry(CruUniqueKeyList[CruAttrDef, str]):

    def __init__(self) -> None:
        super().__init__(lambda d: d.name)

    def make_builder(self, name: str, default_description: str) -> CruAttrDefBuilder:
        b = CruAttrDefBuilder(name, default_description)
        b.build_hook = lambda a: self.add(a)
        return b

    def adopt(self, attr: CruAttr) -> CruAttr:
        d = self.get(attr.name)
        return d.adopt(attr)


class CruAttrTable(CruUniqueKeyList[CruAttr, str]):
    def __init__(self, registry: CruAttrDefRegistry) -> None:
        self._registry: CruAttrDefRegistry = registry
        super().__init__(lambda a: a.name, before_add=registry.adopt)

    @property
    def registry(self) -> CruAttrDefRegistry:
        return self._registry

    def get_value_or(self, name: str, fallback: Any = CruNotFound.VALUE) -> Any:
        a = self.get_or(name, CruNotFound.VALUE)
        if a is CruNotFound.VALUE:
            return fallback
        return a.value

    def get_value(self, name: str) -> Any:
        a = self.get(name)
        return a.value

    def make_attr(
        self,
        name: str,
        value: Any = CruUseDefault.VALUE,
        /,
        description: str | None = None,
    ) -> CruAttr:
        d = self._registry.get(name)
        return d.make(value, description or d.description)

    def add_value(
        self,
        name: str,
        value: Any = CruUseDefault.VALUE,
        /,
        description: str | None = None,
        *,
        replace: bool = False,
    ) -> CruAttr:
        attr = self.make_attr(name, value, description)
        self.add(attr, replace)
        return attr