import logging
import json
from typing import Any, Dict, Optional, Union
from .communicationService import LambdaCommunicationService

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

class BaseCommunicationService:
    def __init__(self):
        self.LAMBDA = LambdaCommunicationService()
        if not self.LAMBDA:
            logger.warning("BaseCommunicationService initialized without LAMBDA mapping.")

    async def get_token(self, id_: int, parent_id: Optional[int] = None) -> str:
        try:
            token = await self.LAMBDA.generate_jwt_token("User", id_, str(parent_id) if parent_id is not None else None)
            return token
        except Exception as error:
            logger.exception("BaseCommunicationService : get_token : %s", repr(error))
            raise

    async def get_refresh_token(self, id_: int, parent_id: Optional[int] = None) -> str:
        try:
            token = await self.LAMBDA.generate_refresh_jwt_token("User", id_, str(parent_id) if parent_id is not None else None)
            return token
        except Exception as error:
            logger.exception("BaseCommunicationService : get_refresh_token : %s", repr(error))
            raise

    async def verify_refresh_token(self, token: str) -> Any:
        try:
            verified = await self.LAMBDA.verify_refresh_jwt_token(token)
            return verified
        except Exception as error:
            logger.exception("BaseCommunicationService : verify_refresh_token : %s", repr(error))
            raise

    async def invoke_lambda_api_with_token(
        self,
        module: str,
        path: str,
        method: Union[str, Any],
        user_type: str,
        json_data: Any,
        id_: str,
        token: str,
    ) -> Dict[str, Any]:
        logger.info("BaseCommunicationService : invoke_lambda_api_with_token : Start : ")

        communication_request = {
            "moduleName": module,
            "method": method,
            "path": path,
            "jsonData": json_data,
            "userType": user_type,
            "id": id_,
            "rawToken": token,
        }

        try:
            result = await self.LAMBDA.invoke_api(communication_request)
            result_json = self._normalize_invoke_result(result)

            logger.info("response - %s", result_json)

            response: Dict[str, Any] = {}
            status_code = result_json.get("statusCode", None)
            body = result_json.get("body", "")

            if status_code is None:
                response["statusCode"] = 200
            else:
                response["statusCode"] = status_code

            if status_code != 404 and body not in (None, ""):
                response["body"] = json.loads(body) if self._is_json_string(body) else body

            logger.info("BaseCommunicationService : invoke_lambda_api_with_token : End : ")
            return response

        except Exception as error:
            logger.exception("BaseCommunicationService : invoke_lambda_api_with_token : Error : %s", error)
            raise

    async def invoke_lambda_api(
        self,
        module: str,
        path: str,
        method: Union[str, Any],
        user_type: str,
        json_data: Any,
        id_: str,
    ) -> Dict[str, Any]:
        communication_request = {
            "moduleName": module,
            "method": method,
            "path": path,
            "jsonData": json_data,
            "userType": user_type,
            "id": id_,
        }

        try:
            result = await self.LAMBDA.invoke_lambda_api(communication_request)
            result_json = self._normalize_invoke_result(result)

            logger.info("response - %s", result_json)
            response: Dict[str, Any] = {}
            body = result_json.get("body", "")
            if body not in (None, ""):
                response["body"] = json.loads(body) if self._is_json_string(body) else body
            response["statusCode"] = result_json.get("statusCode", None)
            return response
        except Exception as error:
            logger.exception("CommonService : invoke_lambda_api : Error : %s", getattr(error, "name", error))
            logger.error("error occured while calling lambda api for path - %s", path)
            raise

    async def invoke_lambda(self, module: str, invocation_type: str, events: Union[list, dict]) -> Dict[str, Any]:
        try:
            result = await self.LAMBDA.invoke_lambda(module, invocation_type, events)
            if not result:
                return {"statusCode": 200}

            result_json = self._normalize_invoke_result(result)
            logger.info("response - %s", result_json)
            response: Dict[str, Any] = {}
            body = result_json.get("body", "")
            if body not in (None, ""):
                response["body"] = json.loads(body) if self._is_json_string(body) else body
            response["statusCode"] = result_json.get("statusCode", None)
            return response
        except Exception as error:
            logger.exception("CommonService : invoke_lambda : Error : %s", getattr(error, "name", error))
            logger.error("error occured while calling lambda for path - %s", module)
            raise

    # --------------------------
    # Helpers
    # --------------------------
    def _normalize_invoke_result(self, result: Any) -> Dict[str, Any]:
        """
        Normalize possible lambda invoke return values to a dict with statusCode/body keys.
        Accepts dict, bytes, string, or StreamingBody-like objects.
        """
        if isinstance(result, dict):
            return result

        try:
            # bytes/bytearray
            if isinstance(result, (bytes, bytearray)):
                text = result.decode("utf-8")
                try:
                    return json.loads(text)
                except Exception:
                    return {"body": text}
            # string
            if isinstance(result, str):
                try:
                    return json.loads(result)
                except Exception:
                    return {"body": result}
            # stream-like
            if hasattr(result, "read"):
                raw = result.read()
                if isinstance(raw, (bytes, bytearray)):
                    raw = raw.decode("utf-8")
                try:
                    return json.loads(raw)
                except Exception:
                    return {"body": raw}
        except Exception:
            logger.exception("Failed to normalize invoke result")
            return {"body": None}
        return {"body": None}

    def _is_json_string(self, s: Any) -> bool:
        try:
            if isinstance(s, (bytes, bytearray)):
                s = s.decode("utf-8")
            if not isinstance(s, str):
                return False
            json.loads(s)
            return True
        except Exception:
            return False
