import grpc

from dmac import exceptions
from dmac import proto
from dmac.utils import assign_dict_to_message


class ServiceFactory:
    def create_service_stub(self, channel):
        raise NotImplementedError("Subclasses must implement this method")


class LoginServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.login_pb2_grpc.LoginsStub(channel),
            "create": proto.login_pb2.CreateRequest,
            "structure": proto.login_pb2.Login,
            "create_otp": proto.login_pb2.SendOTP,
            "verify_otp": proto.login_pb2.VerifyOTP,
            "verify_password": proto.login_pb2.VerifyPassword,
            "refresh_token": proto.login_pb2.RefreshJWT,
        }


class UserServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.user_pb2_grpc.UsersStub(channel),
            "create": proto.user_pb2.CreateRequest,
            "get": proto.user_pb2.RetrieveRequest,
            "update": proto.user_pb2.UpdateRequest,
            "delete": proto.user_pb2.DeleteRequest,
            "validate": proto.user_pb2.ValidateRequest,
            "validate_jwt": proto.user_pb2.ValidateJWTRequest,
            "structure": proto.user_pb2.User,
            "authenticate": proto.user_pb2.AuthenticateRequest,
        }


class WalletServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.wallet_pb2_grpc.WalletsStub(channel),
            "create": proto.wallet_pb2.CreateRequest,
            "get": proto.wallet_pb2.RetrieveRequest,
            "update": proto.wallet_pb2.UpdateRequest,
            "delete": proto.wallet_pb2.DeleteRequest,
            "validate": proto.wallet_pb2.ValidateRequest,
            "structure": proto.wallet_pb2.Wallet,
        }


class AssetServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.asset_pb2_grpc.AssetsStub(channel),
            "create": proto.asset_pb2.CreateRequest,
            "get": proto.asset_pb2.RetrieveRequest,
            "update": proto.asset_pb2.UpdateRequest,
            "delete": proto.asset_pb2.DeleteRequest,
            "list": proto.asset_pb2.ListRequest,
            "validate": proto.asset_pb2.ValidateRequest,
            "structure": proto.asset_pb2.Asset,
        }


class TransactionServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.transaction_pb2_grpc.TransactionsStub(channel),
            "list": proto.transaction_pb2.ListRequest,
            "structure": proto.transaction_pb2.Transaction,
        }


class CertificateServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.certificate_pb2_grpc.CertificatesStub(channel),
            "create": proto.certificate_pb2.CreateRequest,
            "get": proto.certificate_pb2.RetrieveRequest,
            "update": proto.certificate_pb2.UpdateRequest,
            "delete": proto.certificate_pb2.DeleteRequest,
            "list": proto.certificate_pb2.ListRequest,
            "validate": proto.certificate_pb2.ValidateRequest,
            "structure": proto.certificate_pb2.Certificate,
        }


class ActivityServiceFactory(ServiceFactory):
    def create_service_stub(self, channel):
        return {
            "stub": proto.activity_pb2_grpc.ActivitiesStub(channel),
            "create": proto.activity_pb2.CreateRequest,
            "structure": proto.activity_pb2.Activity,
        }


class DarsmanAccount:
    _instance = None  # Class-level attribute to hold the single instance

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, url: str = "127.0.0.1:50054"):
        if not hasattr(self, "_initialized"):
            self.url = url
            self._initialized = True

    @property
    def _services(self):
        return {
            "login": LoginServiceFactory(),
            "user": UserServiceFactory(),
            "wallet": WalletServiceFactory(),
            "asset": AssetServiceFactory(),
            "transaction": TransactionServiceFactory(),
            "certificate": CertificateServiceFactory(),
            "activity": ActivityServiceFactory(),
        }

    def create_service_factory(self, service):
        if self._services.get(service):
            return self._services.get(service)
        else:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )


class ServiceClient:
    def __init__(self, darsman: DarsmanAccount, service_factory: ServiceFactory):
        self.stub = None
        self.url = darsman.url
        self.channel = grpc.insecure_channel(self.url)
        self.service_factory = service_factory
        self.service_details = self.service_factory.create_service_stub(self.channel)
        self.service_struct = self.service_details.pop("structure", None)
        self.stub = self.service_details.pop("stub", None)

    def create_request(self, service, **kwargs):
        request_cls = self.service_details.get("create", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        user_id = kwargs.pop("user_id", None)
        api_key = kwargs.pop("api_key", None)

        kwargs["data"] = assign_dict_to_message(self.service_struct, kwargs["data"])
        try:
            request = request_cls(data=kwargs["data"])
            response = self.stub.create(
                request,
                metadata=[
                    ("authorization", f"{token}"),
                    ("api-key", api_key),
                    ("user_id", f"{user_id}"),
                ],
            )
            return response
        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def authenticate_request(self, service, **kwargs):
        request_cls = self.service_details.get("authenticate", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(**kwargs)
            response = self.stub.authenticate(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def retrieve_request(self, service, **kwargs):
        request_cls = self.service_details.get("get", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(**kwargs)
            response = self.stub.retrieve(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def update_request(self, service, **kwargs):
        request_cls = self.service_details.get("update", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)
        new_password = kwargs.pop("new_password", "")
        old_password = kwargs.pop("old_password", "")
        avatar = kwargs.pop("avatar", b"")
        data = assign_dict_to_message(self.service_struct, kwargs.get("data", {}))

        try:
            request = request_cls(data=data)
            if new_password:
                request.new_password = new_password
                request.old_password = old_password
            if avatar:
                request.avatar = avatar
            response = self.stub.update(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def delete_request(self, service, **kwargs):
        request_cls = self.service_details.get("delete", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(uuid_id=str(kwargs.get("uuid_id", None)))
            response = self.stub.delete(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def list_request(self, service, **kwargs):
        request_cls = self.service_details.get("list", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )
        token = kwargs.pop("token", None)
        user_id = kwargs.pop("user_id", None)
        api_key = kwargs.pop("api_key", None)
        page = kwargs.pop("page", 1)
        per_page = kwargs.pop("per_page", 25)

        try:
            request = request_cls(page=page, per_page=per_page)
            response = self.stub.list(
                request,
                metadata=[
                    ("authorization", f"{token}"),
                    ("api-key", api_key),
                    ("user_id", f"{user_id}"),
                ],
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def validate_request(self, service, **kwargs):
        request_cls = self.service_details.get("validate", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(
                uuid_id=kwargs.get("uuid_id"), slug=kwargs.get("slug")
            )
            response = self.stub.validate(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def validate_jwt_request(self, service, **kwargs):
        request_cls = self.service_details.get("validate_jwt", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls()
            response = self.stub.validate(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def increase_wallet(self, service, **kwargs):
        request_cls = self.service_details.get("update", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            instance = assign_dict_to_message(self.service_struct, kwargs)
            request = request_cls(data=instance)
            response = self.stub.increase(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def decrease_wallet(self, service, **kwargs):
        request_cls = self.service_details.get("update", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            instance = assign_dict_to_message(self.service_struct, kwargs)
            request = request_cls(data=instance)
            response = self.stub.decrease(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def send_otp(self, service, **kwargs):
        request_cls = self.service_details.get("create_otp", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            kwargs["data"] = assign_dict_to_message(
                self.service_struct, (kwargs.get("data", {}))
            )
            request = request_cls(**kwargs)
            response = self.stub.send_otp(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def verify_otp(self, service, **kwargs):
        request_cls = self.service_details.get("verify_otp", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(**kwargs)
            response = self.stub.verify_otp(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def verify_password(self, service, **kwargs):
        request_cls = self.service_details.get("verify_password", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(**kwargs)
            response = self.stub.verify_password(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )

    def refresh_token(self, service, **kwargs):
        request_cls = self.service_details.get("refresh_token", None)

        if not request_cls:
            raise exceptions.ValidationError(
                message_out=exceptions.ErrorMessage.operation_error.value,
            )

        token = kwargs.pop("token", None)
        api_key = kwargs.pop("api_key", None)

        try:
            request = request_cls(**kwargs)
            response = self.stub.refresh_token(
                request, metadata=[("authorization", f"{token}"), ("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise exceptions.ServiceIntegrationsError(
                message_out=exceptions.ErrorMessage.rpc_error.value,
                details={"service": service, "exception": str(e)},
            )
