"""Index metadata and configuration models."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pinecone.models._base import BaseModel
from pinecone.models._display import render_table


if TYPE_CHECKING:
    from pinecone.models.indexes_old._common import IndexStatus
    from pinecone.models.indexes_old.specs import IndexSpec


class IndexConfig(BaseModel, kw_only=True):
    """Index configuration data."""

    dimension_value: int | None
    metric_value: str
    spec_value: IndexSpec

    @property
    def dimension(self) -> int:
        """Get dimension from index configuration.

        Returns:
            Vector dimensionality for the index.

        Raises:
            ValueError: If dimension is not set (sparse indexes don't have dimension).
        """
        if self.dimension_value is None:
            raise ValueError("Index has no dimension (sparse indexes don't have dimension)")
        return self.dimension_value

    @property
    def metric(self) -> str:
        """Get metric from index configuration.

        Returns:
            Distance metric used by the index.
        """
        return self.metric_value

    @property
    def spec(self) -> IndexSpec:
        """Get spec from index configuration.

        Returns:
            Index specification (serverless or pod-based).
        """
        return self.spec_value


class IndexModel(BaseModel, kw_only=True):
    """Index metadata and configuration.

    Represents a Pinecone index with its configuration, status, and deployment details.
    Returned by index management operations like create(), describe(), and list().

    Attributes:
        name: Unique index name
        metric: Distance metric (cosine, euclidean, dotproduct)
        vector_type: Type of vectors (dense or sparse)
        host: Index host URL for data plane operations
        private_host: Private endpoint URL for BYOC indexes (None for non-BYOC)
        status_obj: Index status object with ready flag and state
        spec: Index specification (serverless, pod, or BYOC)
        dimension: Vector dimensionality (None for sparse indexes)
        deletion_protection: Whether deletion protection is enabled
        tags: Optional key-value tags
        embed: Embedding model configuration for indexes created with create_index_for_model().
            Includes model name, metric, dimension, field mapping, and parameters

    Example:
        >>> info = pc.indexes.describe("my-index")
        >>> print(f"{info.name}: {info.config.dimension}D")
        my-index: 1536D
        >>> print(f"Host: {info.host}")
        Host: my-index-abc123.svc.pinecone.io
        >>> # For BYOC indexes, use private_host for private endpoint access
        >>> if info.private_host:
        ...     print(f"Private Host: {info.private_host}")
        >>> # For model indexes, check embedding configuration
        >>> if info.embed:
        ...     print(f"Embedding model: {info.embed['model']}")
    """

    name: str
    metric: str
    vector_type: str
    host: str
    status_obj: IndexStatus
    spec: IndexSpec
    dimension: int | None = None
    private_host: str | None = None
    deletion_protection: str | None = None
    tags: dict[str, str] | None = None
    embed: dict[str, Any] | None = None

    @property
    def status(self) -> str:
        """Get index status as string.

        Returns:
            Current state of the index.
        """
        return self.status_obj.state

    @property
    def config(self) -> IndexConfig:
        """Get index configuration.

        Returns:
            Index configuration with dimension, metric, and spec.
        """
        return IndexConfig(
            dimension_value=self.dimension,
            metric_value=self.metric,
            spec_value=self.spec,
        )

    def __repr__(self) -> str:
        """Return summary representation."""
        parts = [
            f"name={self.name!r}",
            f"status={self.status!r}",
            f"dimension={self.dimension}" if self.dimension is not None else "sparse_index",
            f"metric={self.metric!r}",
            f"vector_type={self.vector_type!r}",
        ]

        if self.private_host is not None:
            parts.append(f"private_host={self.private_host!r}")
        if self.deletion_protection is not None:
            parts.append(f"deletion_protection={self.deletion_protection!r}")
        if self.tags:
            parts.append(f"tags={len(self.tags)} items")
        if self.embed:
            parts.append(f"embed={self.embed.get('model', 'unknown')!r}")

        return f"IndexModel({', '.join(parts)})"

    def __dir__(self) -> list[str]:
        """Return public API, hiding internal attributes."""
        attrs = set(super().__dir__())
        public = {name for name in attrs if not name.startswith("_")}
        return sorted(public)

    def _repr_pretty_(self, p: Any, cycle: bool) -> None:
        """Pretty-printer support for Ipython."""
        if cycle:
            p.text("IndexModel(...)")
            return

        p.text("IndexModel(")
        with p.group(2, "", ")"):
            p.breakable()
            p.text(f"name={self.name!r},")
            p.breakable()
            p.text(f"status={self.status!r},")
            p.breakable()
            if self.dimension is not None:
                p.text(f"dimension={self.dimension},")
            else:
                p.text("sparse_index,")
            p.breakable()
            p.text(f"metric={self.metric!r},")
            p.breakable()
            p.text(f"vector_type={self.vector_type!r},")
            p.breakable()
            p.text(f"host={self.host!r}")

            if self.private_host is not None:
                p.breakable()
                p.text(f"private_host={self.private_host!r}")
            if self.deletion_protection is not None:
                p.breakable()
                p.text(f"deletion_protection={self.deletion_protection!r}")
            if self.tags:
                p.breakable()
                p.text(f"tags={self.tags!r}")
            if self.embed:
                p.breakable()
                p.text(f"embed={self.embed!r}")

    def _repr_html_(self) -> str:
        """Jupyter notebook HTML representation."""
        # Determine index type from spec
        if self.spec.serverless is not None:
            spec_type = f"Serverless ({self.spec.serverless.cloud}/{self.spec.serverless.region})"
        elif self.spec.pod is not None:
            spec_type = f"Pod ({self.spec.pod.environment})"
        elif self.spec.byoc is not None:
            spec_type = f"BYOC ({self.spec.byoc.environment})"
        else:
            spec_type = "Unknown"

        rows: list[tuple[str, str | int]] = [
            ("Name:", self.name),
            ("Status:", self.status),
            ("Type:", spec_type),
            ("Dimension:", self.dimension if self.dimension is not None else "N/A (sparse)"),
            ("Metric:", self.metric),
            ("Vector Type:", self.vector_type),
            ("Host:", self.host),
        ]

        if self.private_host is not None:
            rows.append(("Private Host:", self.private_host))

        if self.deletion_protection is not None:
            rows.append(("Deletion Protection:", self.deletion_protection))

        if self.tags:
            tags_str = ", ".join(f"{k}={v}" for k, v in self.tags.items())
            rows.append(("Tags:", tags_str))

        if self.embed:
            embed_model = self.embed.get("model", "Unknown")
            rows.append(("Embedding Model:", embed_model))

        return render_table("IndexModel", rows)
