from typing import NamedTuple, Optional, Tuple, List
import enum
from loguru import logger

from pinecone.utils.sentry import sentry_decorator as sentry
from pinecone.utils.progressbar import ProgressBar
from .service import deploy as service_deploy, stop as service_stop, ls as service_ls, describe as service_describe
from .graph import IndexGraph, IndexConfig

__all__ = [
    "create",
    "delete",
    "ls",
    "describe",
    "create_index",
    "delete_index",
    "describe_index",
    "list_indexes",
    "ResourceType",
    "ResourceDescription",
]


class ResourceType(enum.Enum):
    INDEX = "index"


class ResourceDescription(NamedTuple):
    """Description of a resource."""

    name: str
    kind: str
    status: dict
    config: dict


@sentry
def create(name: str, kind: str = "index", wait: bool = True, **kwargs) -> Optional[Tuple[dict, ProgressBar]]:
    """Creates a Pinecone resource.

    :param name: the name of the resource.
    :type name: str
    :param kind: what kind of resource. Defaults to ``index``.
    :type kind: str
    :param wait: wait for the resource to deploy. Defaults to ``True``
    :type wait: bool
    :param `**kwargs`: see resource-specific configurations.
        For example, you can refer to :class:`IndexConfig` for
        configuration options for a Pinecone index.
    """
    if kind == ResourceType.INDEX.value:
        return service_deploy(service_name=name, graph=IndexGraph(**kwargs), wait=wait)
    logger.warning("Unrecognized resource type '{}'.".format(kind))


@sentry
def delete(name: str, kind: str = "index", wait: bool = True) -> Optional[Tuple[dict, ProgressBar]]:
    """Deletes a Pinecone resource.

    :param name: the name of the resource.
    :type name: str
    :param kind: what kind of resource. Defaults to ``index``.
    :type kind: str
    :param wait: wait for the resource to deploy. Defaults to ``True``
    :type wait: bool
    """
    if kind == ResourceType.INDEX.value:
        return service_stop(service_name=name, wait=wait)
    logger.warning("Unrecognized resource type '{}'.".format(kind))


@sentry
def ls(kind: str = "index") -> Optional[List[str]]:
    """Lists all resources of a certain kind.

    :param kind: what kind of resource. Defaults to ``index``.
    :type kind: str
    """
    if kind == ResourceType.INDEX.value:
        return service_ls()
    logger.warning("Unrecognized resource type '{}'.".format(kind))


@sentry
def describe(name: str, kind: str = "index") -> Optional[ResourceDescription]:
    """Describes the resource.

    :param name: the name of the resource.
    :type name: str
    :param kind: what kind of resource. Defaults to ``index``.
    :type kind: str
    """
    if kind == ResourceType.INDEX.value:
        desc = service_describe(service_name=name)
        graph = desc.graph
        config = IndexConfig._from_graph(graph)._asdict()
        return ResourceDescription(name=desc.name, kind=kind, status=desc.status, config=config)
    logger.warning("Unrecognized resource type '{}'.".format(kind))


@sentry
def create_index(
    name: str,
    wait: bool = True,
    engine_type: str = "approximated",
    metric: str = "cosine",
    shards: int = 1,
    replicas: int = 1,
    gateway_replicas: int = 1,
    engine_args: dict = None,
) -> Optional[Tuple[dict, ProgressBar]]:
    """Creates a Pinecone index.

    :param name: the name of the index.
    :type name: str
    :param wait: wait for the index to deploy. Defaults to ``True``
    :type wait: bool
    :param engine_type: type of engine, one of {"approximated", "exact"}, defaults to "approximated".
        The "approximated" engine uses fast approximate search algorithms developed by Pinecone.
        The "exact" engine uses accurate exact search algorithms.
        It performs exhaustive searches and thus it is usually slower than the "approximated" engine.
    :type engine_type: str, optional
    :param metric: type of metric used in the vector index, one of {"cosine", "dotproduct", "euclidean"}, defaults to "cosine".
        Use "cosine" for cosine similarity,
        "dotproduct" for dot-product,
        and "euclidean" for euclidean distance.
    :type metric: str, optional
    :param shards: the number of shards for the engine, defaults to 1.
        As a general guideline, use 1 shard per 1 GB of data.
    :type shards: int, optional
    :param replicas: the number of replicas, defaults to 1.
        Use at least 2 replicas if you need high availability (99.99% uptime) for querying.
        For additional throughput (QPS) your service needs to support, provision additional replicas.
    :type replicas: int, optional
    :param gateway_replicas: number of replicas of both the gateway and the aggregator.
    :type gateway_replicas: int
    :param engine_args: advanced arguments for the engine instance in the graph.
    :type engine_args: dict
    """
    return create(
        name=name,
        kind=ResourceType.INDEX.value,
        wait=wait,
        engine_type=engine_type,
        metric=metric,
        shards=shards,
        replicas=replicas,
        gateway_replicas=gateway_replicas,
        engine_args=engine_args,
    )


@sentry
def delete_index(name: str, wait: bool = True) -> Optional[Tuple[dict, ProgressBar]]:
    """Deletes a Pinecone index.

    :param name: the name of the index.
    :type name: str
    :param wait: wait for the index to deploy. Defaults to ``True``
    :type wait: bool
    """
    return delete(name=name, kind=ResourceType.INDEX.value, wait=wait)


@sentry
def list_indexes() -> Optional[List[str]]:
    """Lists all indexes."""
    return ls(kind=ResourceType.INDEX.value)


@sentry
def describe_index(name: str) -> Optional[ResourceDescription]:
    """Describes an index.

    :param name: the name of the index.
    :type name: str
    """
    return describe(name=name, kind=ResourceType.INDEX.value)