from __future__ import annotations

from collections.abc import Iterable, Mapping
from typing import Any

from ..config import DEFAULT_SERVICES
from ..exceptions import BrightreeException
from ..soap import ZeepSoapClient


class BaseService:
    service_name: str | None = None
    methods: dict[str, bool] = {}
    special_methods: dict[str, tuple[str, ...]] = {}

    def __init__(self, info: dict[str, Any], client_factory=None) -> None:
        self.info = info
        self._client_factory = client_factory or ZeepSoapClient

        service_name = self.get_service_name()
        services = info.get("config", {}).get("service", {})
        location = services.get(service_name)
        if not location:
            raise BrightreeException.config_error(f"{service_name.capitalize()} service URL not configured")

        username = info.get("username", "")
        password = info.get("password", "")
        if not username or not password:
            raise BrightreeException.auth_error("Authentication credentials not provided")

        self.wsdl = f"{location}?singleWsdl"
        self.location = location
        self.username = username
        self.password = password

    def get_service_name(self) -> str:
        if self.service_name:
            return self.service_name
        return self.__class__.__name__.lower()

    def _create_client(self):
        return self._client_factory(
            wsdl=self.wsdl,
            username=self.username,
            password=self.password,
            location=self.location,
        )

    def _api_call(self, method: str, params: dict[str, Any]) -> Any:
        try:
            client = self._create_client()
            return client.call(method, params)
        except BrightreeException:
            raise
        except Exception as exc:
            raise BrightreeException.from_soap_fault(exc, {"method": method, "params": params}) from exc

    @staticmethod
    def _is_iterable(value: Any) -> bool:
        return isinstance(value, Mapping) or (
            isinstance(value, Iterable) and not isinstance(value, (str, bytes, bytearray))
        )

    @staticmethod
    def _ensure_iterable(method: str, query: Any) -> None:
        if not BaseService._is_iterable(query):
            raise BrightreeException(f"Method {method} requires an iterable parameter", 1002)

    def _call_with_query(self, method: str, query: Any) -> Any:
        self._ensure_iterable(method, query)
        return self._api_call(method, dict(query))

    def _call_with_special(self, method: str, param_names: tuple[str, ...], *args: Any) -> Any:
        params = {}
        for index, param_name in enumerate(param_names):
            if index >= len(args):
                raise BrightreeException.param_error(method, param_name)
            params[param_name] = args[index]
        return self._api_call(method, params)

    def __getattr__(self, name: str):
        if name in self.methods:
            if self.methods[name]:
                return lambda query=None, _name=name: self._call_with_query(_name, query)
            return lambda _name=name: self._api_call(_name, {})

        if name in self.special_methods:
            return lambda *args, _name=name: self._call_with_special(_name, self.special_methods[_name], *args)

        raise AttributeError(name)


def default_config() -> dict[str, dict[str, str]]:
    return {"service": dict(DEFAULT_SERVICES)}
