import re
import dataclasses  # type: ignore
from dataclasses import InitVar
from typing import Any, Dict, Optional, Union
import inspect
import requests
from requests.adapters import HTTPAdapter
# from requests.packages.urllib3.util.retry import Retry
from urllib3.util.retry import Retry
from logging import getLogger

from anon.exceptions import InvalidAPIResponse
# import anon.projects as projects
# import anon.label.dl_models as dl_models
logger = getLogger(__name__)

@dataclasses.dataclass
class _MixinLogin:
    username: InitVar[str]
    password: InitVar[str]


@dataclasses.dataclass
class _MixinToken:
    token: str


@dataclasses.dataclass
class BaseClient:
    infer_url: str = 'https://anon.streamingo.ai/blur'
    # session: Any = Field()
    owner_id: Optional[str] = None
    restriction: Union[bool, str] = True

    def __post_init__(self):
        session = requests.Session()
        retry = Retry(connect=5, backoff_factor=0.5)
        session.mount("http://", HTTPAdapter(max_retries=retry))
        session.mount("https://", HTTPAdapter(max_retries=retry))
        self.session = session
        # self.label_url = self.label_url.rstrip('/')
        self.infer_url = self.infer_url.rstrip('/')

    @property
    def headers(self) -> Dict[str, str]:
        return {"Authorization": f"Bearer {self.token}"}  # type: ignore

    def post(self, url: str, json: Dict[str, Any], headers:Optional[Dict[str, str]]=None):
        if not url.startswith("http"):
            url = self._get_url_by_caller(url)
        if self.restriction in ["false",False]:
            json["restriction"] = "false"
        json["sessionRestriction"] = "false"
        response = self.session.post(
            url, json=json, headers={**self.headers, **(headers if isinstance(headers, dict) else {})})
        if response.status_code > 300:
            logger.error(f"{response.status_code}  {url} failed. {response.text} ; data:{json}")
            raise InvalidAPIResponse(f"{response.status_code}  {url} {json} {response.text}")
        return response

    def _get_url_by_caller(self, suffix: str):
        """When client's get/post/put url is called, the passed suffix must me
        added to either label_url or infer_url. In order to determine which of
        these two should be the base url, use inspect.stack() to get the caller
        function's path and determine it.

        This func is generally called inside client's get/put/post methods which
        are called by the caller method in question. Hence callstack would be
        this_func -> get/post/put -> caller
        """
        caller_filename = inspect.stack()[2][1]  # 1 is for filename
        # if re.search(r'[/\\]infer[/\\]', caller_filename):
        #     return self.infer_url + suffix
        # if re.search(r"[/\\]feedback[/\\]", caller_filename):
        #     return self.infer_url + suffix
        # elif re.search(r"[/\\]label[/\\]", caller_filename):
        #     return self.label_url + suffix
        # elif re.search(r"[/\\]auth[/\\]", caller_filename):
        #     return self.label_url + suffix
        # print(self.infer_url + suffix)
        return self.infer_url + suffix
        # else:
        #     called_func = inspect.stack()[1][3]  #  BaseClient.{get/put/post}
        #     logger.error(
        #         f"Called BaseClient.{called_func} without base url outside library"
        #     )
        #     raise InvalidAPIResponse(
        #         f"Suffix based url is only supported inside infer and label objects. "
        #         f"Please use full url to call BaseClient.{called_func}"
        #     )

    def get(
        self,
        url: str,
        params: Dict[str, Any],
        # add_project_id: bool = False,
        headers: Optional[Dict[str,str]] = None
    ):
        """Get a deeplabel get api

        Args:
            url (str): url to get. can be a suffix like /graphs/nodes or full url
            params (Dict[str,Any]): query parameters
            add_project_id (bool, optional): Weather to add projectId to query
                params if available. Defaults to False.

        Raises:
            InvalidAPIResponse: If the response code is > 200

        Returns:
            Response: requests.Response object
        """
        # if add_project_id and getattr(self, "project_id", None):
        #     params["projectId"] = self.project_id  # type: ignore
        if self.restriction in ["false",False]:
            params["restriction"] = "false"
        # params["sessionRestriction"] = "false"
        if not url.startswith("http"):
            # print('url not starting with http', url)
            url = self._get_url_by_caller(url)
        logger.debug(f"Fetching {url} params: {params}")
        response = self.session.get(
            url, params=params, headers={**self.headers, **(headers if isinstance(headers, dict) else {})})
        if response.status_code > 202:
            logger.error(f"{response.status_code}  {url} {params} failed. {response.text}")
            raise InvalidAPIResponse(f"{response.status_code} {url} {params} {response.text}")
        logger.debug(f"Received Response {response.text}")
        return response

    def put(self, url: str, json: Dict[str, Any], headers:Optional[Dict[str,str]]=None):
        if not url.startswith("http"):
            url = self._get_url_by_caller(url)
        if self.restriction in ["false",False]:
            json["restriction"] = "false"
        # json["sessionRestriction"] = "false"
        response = self.session.put(url, json=json, headers={**self.headers, **(headers if isinstance(headers, dict) else {})})
        if response.status_code > 202:
            logger.error(f"{response.status_code}  {url} {json} failed. {response.text}")
            raise InvalidAPIResponse(f"{response.status_code}  {url} {json} {response.text}")
        return response
    
    def delete(self, url: str, json: Dict[str, Any], headers:Optional[Dict[str,str]]=None):
        if not url.startswith("http"):
            url = self._get_url_by_caller(url)
        if self.restriction in ["false",False]:
            json["restriction"] = "false"
        # json["sessionRestriction"] = "false"
        response = self.session.delete(url, json=json, headers={**self.headers, **(headers if isinstance(headers, dict) else {})})
        if response.status_code > 202:
            logger.error(f"{response.status_code}  {url} {json} failed. {response.text}")
            raise InvalidAPIResponse(f"{response.status_code}  {url} {json} {response.text}")
        return response

    # def project(self, project_id: str) -> "projects.Project":
    #     return projects.Project.from_project_id(project_id, client=self)

    # def dl_model(self, dl_model_id: str) -> "dl_models.DlModel":
    #     return dl_models.DlModel.from_dl_model_id(dl_model_id, client=self)



@dataclasses.dataclass
class AnonClient(BaseClient, _MixinToken):
    ...


@dataclasses.dataclass
class AnonLoginClient(BaseClient, _MixinLogin):
    def __post_init__(self, email: str, password: str):  # type: ignore
        import anon.auth as auth
        # Get Session
        super().__post_init__()
        # Get user and token
        user: auth.users.User = auth.users.User.from_login(email, password, self)
        assert user.root_secret_key, f"No root secret key found in user"
        token = auth.tokens.UserToken.from_root_secret_key(user.root_secret_key, self)
        self.token = token.hash
