"""Create method for indexes namespace."""

from __future__ import annotations

import re

from typing import TYPE_CHECKING, Any, cast

import orjson

from pinecone._internal.constants import INDEXES_API_VERSION
from pinecone._internal.logging import get_logger
from pinecone._internal.validation import require_in_range, require_non_empty


if TYPE_CHECKING:
    from pinecone.__interface__ import IndexSpec
    from pinecone.__interface__.indexes import IndexInfoProtocol


logger = get_logger(__name__)


def create(
    self: Any,
    name: str,
    *,
    dimension: int | None = None,
    metric: str = "cosine",
    spec: IndexSpec,
    deletion_protection: str = "disabled",
    tags: dict[str, str] | None = None,
    vector_type: str = "dense",
    timeout: int | None = None,
) -> IndexInfoProtocol:
    """Create a new index.

    Args:
        name: Unique index name (1-45 chars, lowercase alphanumeric or '-')
        dimension: Vector dimensionality (1-20000). Required for dense indexes,
            omit for sparse indexes
        metric: Distance metric ('cosine', 'euclidean', or 'dotproduct').
            Defaults to 'cosine'. Must be 'dotproduct' for sparse indexes
        spec: Index specification. Can be a ServerlessSpec, PodSpec, or ByocSpec dict:
            - Serverless (on-demand): {"cloud": "aws"|"gcp"|"azure", "region": "..."}
            - Serverless (dedicated): {"cloud": "...", "region": "...", "read_capacity": {...}}
            - Pod: {"environment": "...", "pod_type": "...", "pods": 1, "replicas": 1, "shards": 1}
              (pods, replicas, and shards are optional and default to 1)
            - BYOC: {"environment": "...", "read_capacity": {...}}
        deletion_protection: Whether deletion protection is enabled
            ('disabled' or 'enabled'). Defaults to 'disabled'
        tags: Optional key-value tags for the index. Keys must be 80 chars or less,
            values 120 chars or less
        vector_type: Type of vectors ('dense' or 'sparse'). Defaults to 'dense'
        timeout: Optional timeout in seconds

    Returns:
        IndexInfo with the created index metadata and configuration

    Raises:
        BadRequestError: If parameters are invalid
        ConflictError: If index with the name already exists
        NotFoundError: If cloud/region combination is invalid for serverless
        APIError: If the API returns an error response
        APIConnectionError: If the request fails to connect
        TimeoutError: If the request times out

    Example with serverless (on-demand):
        >>> info = pc.indexes.create(
        ...     name="my-serverless-index",
        ...     dimension=1536,
        ...     metric="cosine",
        ...     spec={"cloud": "aws", "region": "us-east-1"},
        ... )
        >>> print(f"Created {info.name} at {info.host}")

    Example with serverless (dedicated capacity):
        >>> info = pc.indexes.create(
        ...     name="my-dedicated-index",
        ...     dimension=1536,
        ...     metric="cosine",
        ...     spec={
        ...         "cloud": "aws",
        ...         "region": "us-east-1",
        ...         "read_capacity": {
        ...             "mode": "Dedicated",
        ...             "dedicated": {
        ...                 "node_type": "b1",
        ...                 "scaling": "Manual",
        ...                 "manual": {"shards": 2, "replicas": 1},
        ...             },
        ...         },
        ...     },
        ... )

    Example with sparse vectors:
        >>> info = pc.indexes.create(
        ...     name="sparse-index",
        ...     metric="dotproduct",
        ...     vector_type="sparse",
        ...     spec={"cloud": "gcp", "region": "us-east1"},
        ... )

    Example with tags and deletion protection:
        >>> info = pc.indexes.create(
        ...     name="production-index",
        ...     dimension=768,
        ...     spec={"cloud": "aws", "region": "us-west-2"},
        ...     deletion_protection="enabled",
        ...     tags={"environment": "prod", "team": "ml"},
        ... )
    """
    # 1. Validate inputs
    require_non_empty("name", name)
    require_non_empty("metric", metric)
    require_non_empty("vector_type", vector_type)

    # Validate index name format
    if len(name) > 45:
        raise ValueError(f"Index name '{name}' exceeds 45 character limit ({len(name)} chars)")

    if not re.match(r"^[a-z0-9-]+$", name):
        raise ValueError(
            f"Index name '{name}' contains invalid characters. "
            "Name must contain only lowercase letters, numbers, and hyphens."
        )

    if dimension is not None:
        require_in_range("dimension", dimension, 1, 20000)

    # Validate dimension is required for dense vectors
    if vector_type == "dense" and dimension is None:
        raise ValueError("dimension is required for dense indexes")

    # Validate dimension is not provided for sparse vectors
    if vector_type == "sparse" and dimension is not None:
        raise ValueError("dimension should not be specified for sparse indexes")

    # Validate tags
    if tags:
        for key, value in tags.items():
            if len(key) > 80:
                raise ValueError(f"Tag key '{key}' exceeds 80 character limit ({len(key)} chars)")
            if len(value) > 120:
                raise ValueError(
                    f"Tag value for key '{key}' exceeds 120 character limit ({len(value)} chars)"
                )

    # 2. Log operation start
    logger.info(
        "Creating index",
        name=name,
        dimension=dimension,
        metric=metric,
        vector_type=vector_type,
    )

    # 3. Build request body
    request_body: dict[str, object] = {
        "name": name,
        "metric": metric,
        "vector_type": vector_type,
        "deletion_protection": deletion_protection,
        "spec": self._normalize_spec(cast("dict[str, Any]", spec)),
    }

    if dimension is not None:
        request_body["dimension"] = dimension

    if tags:
        request_body["tags"] = tags

    try:
        # 4. Make HTTP request
        response = self._http.post(
            f"{self._base_url}/indexes",
            content=orjson.dumps(request_body),
            headers={
                "Content-Type": "application/json",
                "X-Pinecone-Api-Version": INDEXES_API_VERSION,
            },
            timeout=timeout,
        )

        # 5. Transform response via adapter
        data = orjson.loads(response.content)
        result = self._adapter.to_index_model(data)

        # 6. Log success
        logger.debug(
            "Index created successfully",
            name=name,
            host=result.host,
        )

        # Cast to protocol type - IndexModel structurally satisfies IndexInfoProtocol
        return cast("IndexInfoProtocol", result)

    except Exception:
        # 7. Log failure with context
        logger.exception(
            "Failed to create index",
            name=name,
            dimension=dimension,
            metric=metric,
        )
        raise
