import json
from warnings import warn
from dynamix_sdk import config
from dynamix_sdk.exceptions import IncompatibleAPIError
from dynamix_sdk.utils import F_DECORATOR
from .api import API


class Dynamix:
    _config: config.ConfigWithAuth
    _COMPATIBLE_DX_MINOR_VERSION: str = '4.6'
    _COMPATIBLE_DX_BUILD: int = 1524
    _COMPATIBILITY_WITH_NEWER_DX_BUILDS: bool = True
    _dx_version: str
    _dx_build: int

    def __init__(
        self,
        *,
        url: str,
        auth: str | config.AuthenticatorProtocol,
        verify_ssl: bool = True,
        http503_attempts: int = 10,
        http503_attempts_interval: int = 5,
        result_extra_allow: bool = False,
        ignore_api_compatibility: bool = False,
        wrap_request_exceptions: bool = False,
        f_decorators: list[F_DECORATOR] | None = None,
    ):
        self._config = config.ConfigWithAuth(
            base_api_path='/restmachine',
            url=url,
            auth=auth,
            verify_ssl=verify_ssl,
            http503_attempts=http503_attempts,
            http503_attempts_interval=http503_attempts_interval,
            result_extra_allow=result_extra_allow,
            wrap_request_exceptions=wrap_request_exceptions,
            f_decorators=f_decorators,
        )

        self._dx_version, self._dx_build = self._get_dx_version_and_build()

        self._check_dx_compatibility(ignore_api_compatibility)

    @property
    def api(self):
        return API(config=self._config)

    @property
    def compatible_dx_minor_version(self) -> str:
        return self._COMPATIBLE_DX_MINOR_VERSION

    @property
    def compatible_dx_build(self) -> int:
        return self._COMPATIBLE_DX_BUILD

    @property
    def compatibility_with_newer_dx_builds(self) -> bool:
        return self._COMPATIBILITY_WITH_NEWER_DX_BUILDS

    @property
    def dx_version(self) -> str:
        return self._dx_version

    @property
    def dx_build(self) -> int:
        return self._dx_build

    def _get_dx_version_and_build(self) -> tuple[str, int]:
        result = self.api.system.info.version()

        decoded_data = json.loads(result.replace("'", '"'))

        dx_version = decoded_data['version']
        if not isinstance(dx_version, str):
            raise TypeError(f'DX version must be an str, got {type(dx_version)}')  # noqa: E501

        dx_build = decoded_data['build']
        if not isinstance(dx_build, int):
            raise TypeError(f'DX build must be an int, got {type(dx_build)}')

        return dx_version, dx_build

    def _check_dx_compatibility(self, ignore_api_compatibility: bool):
        if (
            self.dx_version.rsplit('.', 1)[0]
            != self.compatible_dx_minor_version
        ):
            message = (
                f'Incompatible minor Dynamix version. '
                f'Dynamix version: {self.dx_version}. '
                f'Compatible minor version: '
                f'{self.compatible_dx_minor_version}.'
            )

            if ignore_api_compatibility:
                warn(message=message)
                return
            else:
                raise IncompatibleAPIError(message=message)

        build_is_compatible = (
            (
                self.compatibility_with_newer_dx_builds
                and self.dx_build >= self.compatible_dx_build
            )
            or (
                not self.compatibility_with_newer_dx_builds
                and self.dx_build == self.compatible_dx_build
            )
        )

        if not build_is_compatible:
            compatible_build_str = f'{self.compatible_dx_build}'
            if self.compatibility_with_newer_dx_builds:
                compatible_build_str = f'>={compatible_build_str}'

            message = (
                f'Incompatible Dynamix build. '
                f'Dynamix build: {self.dx_build}. '
                f'Compatible build: {compatible_build_str}.'
            )

            if ignore_api_compatibility:
                warn(message=message)
            else:
                raise IncompatibleAPIError(message=message)
