from __future__ import annotations

from typing import TYPE_CHECKING, Generic, Type, TypeVar
from uuid import UUID

import pandas as pd

from .proxy import Proxy

if TYPE_CHECKING:
    from ..client import Client
    from .refs import RefList


ProxyClass = TypeVar("ProxyClass", bound=Proxy)


class ShortenedUUID(UUID):
    def __str__(self):
        return f"{super().__str__()[:5]}..."


def simplified(value, property):
    from .property import Id
    from .refs import Reference

    match property:
        case Id():
            return ShortenedUUID(value.hex)
        case Reference(proxy_type=pt):
            if pt.proxy_schema.name_id:
                return value.name
            elif isinstance(value, Proxy):
                return ShortenedUUID(value.proxy_id.hex)
            elif isinstance(value, ProxyList):
                return f"{pt.proxy_schema.class_name}[{len(value)}]"
            else:
                return value
        case _:
            return value


class ProxyList(Generic[ProxyClass]):
    """
    Base class for "dynamic lists" of proxies.

    The instances maintain lists of IDs and translate list operations
    as if the list actually contained proxy objects.
    """

    def __init__(self, client: Client, proxy_type: Type[ProxyClass]):
        self.client = client
        self.proxy_type = proxy_type
        self.registry = client.registry_for(proxy_type)

    @property
    def coll(self) -> list[UUID]:
        raise NotImplementedError

    def resolve_proxy(self, item):
        """This is the main routine that transforms elements of
        the list to proxies.
        """
        return self.registry.fetch_proxy(item)

    def get_slice(self, slc: slice) -> list[UUID]:
        """Return a list of UUIDs for the given slice.

        This method is used to implement slicing operations on the
        ProxyList. It should be overriden in subclasses depending on
        the underlying implementation the the collection.
        """
        raise ValueError("Slices are not supported yet")

    def __iter__(self):
        for item in self.coll:
            yield self.resolve_proxy(item)

    def __len__(self):
        return len(self.coll)

    def __getitem__(self, item):
        if isinstance(item, slice):
            return ProxyVec(self.client, self.proxy_type, self.get_slice(item))
        else:
            return self.resolve_proxy(self.coll[item])

    def __repr__(self):
        return f"{self.proxy_type.__name__}{repr(self.coll)}"

    def __eq__(self, other):
        try:
            return len(self) == len(other) and (
                all(p == q for p, q in zip(self, other))
            )
        except Exception:
            return False

    @property
    def ids(self):
        return list(self.coll)

    def to_df(self, *additional_fields, fields=None, simplify=True):
        """Generate a pandas dataframe for the list of proxy entities.

        The dataframe is generated by fetching and tabulating a
        subset of fields, for each entity in the list.
        """
        schema = self.proxy_type.proxy_schema

        if fields is None:
            fields = schema.short_list(set(additional_fields))
        elif fields in (True, "all", "ALL"):
            fields = list(schema.all_fields) + list(additional_fields)
        else:
            fields = list(fields) + list(additional_fields)
        data = {field: list() for field in fields}

        for proxy in self:
            for field in fields:
                if simplify:
                    property = schema.all_fields[field]
                    data[field].append(
                        simplified(getattr(proxy, field, pd.NA), property)
                    )
                else:
                    data[field].append(getattr(proxy, field, pd.NA))
        return pd.DataFrame(data=data)

    @property
    def df(self):
        """Return a dataframe over the default fields."""
        return self.to_df()

    @property
    def DF(self):
        """Return a dataframe over all fields."""
        return self.to_df(fields=True, simplify=False)


class ProxyVec(ProxyList):
    """A list of IDs appearing as proxies.

    The underlying data is a list of UUIDs. At each element access, the
    correpsonding element is fetched from the registry.
    """

    def __init__(
        self, client: Client, proxy_type: Type[ProxyClass], members: list[UUID]
    ):
        """Initialize the vector with the client, the proxy type, and the list of IDs.

        Arguments:
            client: the client object
            proxy_type: the type of the proxies
            members: the list of UUIDs
        """
        super().__init__(client, proxy_type)
        self.members: list[UUID] = members

    @property
    def coll(self):
        return self.members

    def get_slice(self, slc: slice) -> list[UUID]:
        return self.members[slc]


class ProxySublist(ProxyList):
    """A proxy class that translates collection operations to operations
    on an entity sub-collection.
    """

    def __init__(self, property: RefList, owner: Proxy):
        super().__init__(owner.proxy_registry.catalog, property.proxy_type)
        self.property = property
        self.owner = owner

    def __delitem__(self, key):
        raise NotImplementedError(
            f"delitem  {self.property.owner.__name__}.{self.property.name}"
        )

    def __iadd__(self, **kwargs):
        raise NotImplementedError(
            f"iadd {self.property.owner.__name__}.{self.property.name}"
        )

    @property
    def coll(self):
        return self.property.get(self.owner)

    def get_slice(self, slc: slice) -> list[UUID]:
        return self.property.get(self.owner)[slc]
