import json
import os

import grpc
from google.protobuf.json_format import MessageToDict

from dmmc.exceptions import *
from dmmc.proto import *


class ServiceFactory:
    name = None

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


class BucketServiceFactory(ServiceFactory):
    name = "bucket"

    def create_service_stub(self, channel):
        return {
            "stub": bucket_pb2_grpc.BucketsStub(channel),
            "create": bucket_pb2.CreateRequest,
            "get": bucket_pb2.RetrieveRequest,
            "validate": bucket_pb2.ValidateRequest,
            "message": bucket_pb2.Bucket,
        }


class MediaServiceFactory(ServiceFactory):
    name = "media"

    def create_service_stub(self, channel):
        return {
            "stub": media_pb2_grpc.MediasStub(channel),
            "create": media_pb2.CreateRequest,
            "get": media_pb2.RetrieveRequest,
            "delete": media_pb2.DeleteRequest,
            "validate": media_pb2.ValidateRequest,
            "message": media_pb2.Media,
        }


class StoreTransactionServiceFactory(ServiceFactory):
    name = "store_transaction"

    def create_service_stub(self, channel):
        return {
            "stub": store_transaction_pb2_grpc.StoreTransactionsStub(channel),
            "list": store_transaction_pb2.ListRequest,
            "message": store_transaction_pb2.StoreTransaction,
        }


class DarsmanMedia:
    _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:50055"):
        if not hasattr(self, "_initialized"):
            self.url = url
            self._initialized = True

    @property
    def _services(self):
        return {
            "bucket": BucketServiceFactory(),
            "media": MediaServiceFactory(),
            "store_transaction": StoreTransactionServiceFactory(),
        }

    def create_service_factory(self, service):
        if self._services.get(service):
            return self._services.get(service)
        else:
            raise ValidationError("Invalid service")


class ServiceClient:
    def __init__(self, darsman: DarsmanMedia, service_factory: ServiceFactory):
        self.stub = None
        self.url = darsman.url
        self.channel = grpc.insecure_channel(self.url)
        self.service_factory = service_factory

    def create_request(self, service, *args, **kwargs):
        service_details = self.service_factory.create_service_stub(self.channel)
        stub = service_details["stub"]
        create_request_cls = service_details["create"]
        self.stub = stub

        if create_request_cls is None:
            raise ValidationError(
                f"Create operation not supported for {service} service"
            )

        api_key = kwargs.pop("api_key", None)
        if self.service_factory.name == "media":
            media_file = kwargs.pop("file", None)
            if media_file:
                request = create_request_cls(
                    data=service_details["message"](**kwargs), buffer=media_file
                )
            else:
                raise ValidationError("file not found")
        else:
            request = create_request_cls(data=service_details["message"](**kwargs))

        try:
            response = self.stub.create(request, metadata=[("api-key", api_key)])
            return response
        except grpc.RpcError as e:
            print(e)
            raise ServiceIntegrationsError

    def retrieve_request(self, service, *args, **kwargs):
        service_details = self.service_factory.create_service_stub(self.channel)
        stub = service_details["stub"]
        get_request_cls = service_details["get"]
        self.stub = stub

        if get_request_cls is None:
            raise ValidationError(f"Get operation not supported for {service} service")

        api_key = kwargs.pop("api_key", None)
        request = get_request_cls(uuid_id=str(kwargs.get("uuid_id", None)))

        try:
            response = self.stub.retrieve(request, metadata=[("api-key", api_key)])
            return response

        except grpc.RpcError as e:
            print(e)
            raise ServiceIntegrationsError

    def delete_request(self, service, *args, **kwargs):
        service_details = self.service_factory.create_service_stub(self.channel)
        stub = service_details["stub"]
        delete_request_cls = service_details["delete"]
        self.stub = stub

        if delete_request_cls is None:
            raise ValidationError(
                f"Delete operation not supported for {service} service"
            )

        api_key = kwargs.pop("api_key", None)
        request = delete_request_cls(uuid_id=str(kwargs.get("uuid_id", None)))

        try:
            response = self.stub.delete_instance(
                request, metadata=[("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise ServiceIntegrationsError

    def list_request(self, service, *args, **kwargs):
        service_details = self.service_factory.create_service_stub(self.channel)
        stub = service_details["stub"]
        list_request_cls = service_details["list"]
        self.stub = stub

        if list_request_cls is None:
            raise ValidationError(f"List operation not supported for {service} service")

        api_key = kwargs.pop("api_key", None)
        request = list_request_cls()

        try:
            response = self.stub.list_instance(request, metadata=[("api-key", api_key)])
            return response

        except grpc.RpcError as e:
            print(e)
            raise ServiceIntegrationsError

    def validate_request(self, service, *args, **kwargs):
        service_details = self.service_factory.create_service_stub(self.channel)
        stub = service_details["stub"]
        validate_request_cls = service_details["validate"]

        self.stub = stub

        if validate_request_cls is None:
            raise ValidationError(
                f"validate operation not supported for {service} service"
            )

        api_key = kwargs.pop("api_key", None)
        request = validate_request_cls(uuid_id=str(kwargs.get("uuid_id", None)))

        try:
            response = self.stub.validate_instance(
                request, metadata=[("api-key", api_key)]
            )
            return response

        except grpc.RpcError as e:
            print(e)
            raise ServiceIntegrationsError
