import logging
from typing import Any

from semantic_models.utils.spec_utils import get_distinct_values_mode, get_sample_limit, is_auto_values_enabled

logger = logging.getLogger(__name__)
MAX_ATTRIBUTE_VALUES_LIMIT = 1000


def _fetch_dynamic_sample_values(
    semantic_model_id: str,
    version_id: str,
    entity_name: str,
    attribute_name: str,
    limit: int,
) -> list[str]:
    from semantic_models.resolution import SourceKey
    from semantic_models.services.column_values import fetch_column_values

    source = SourceKey(
        semantic_model_id=semantic_model_id,
        version_id=version_id,
        entity_name=entity_name,
    )
    return fetch_column_values(source, attribute_name, limit)


def list_entities(spec: dict) -> list[dict]:
    """
    List all entities with compact summaries.

    This tool returns a high-level overview of all entities in the semantic model,
    suitable for the agent to decide which entities are relevant to a query.

    Args:
        spec: SemanticModelSpec as dict

    Returns:
        List of entity summaries:
        [
            {
                "id": "customers",
                "name": "Customers",
                "description": "Customer master data...",
                "attribute_count": 15,
                "metric_count": 3,
                "filter_count": 2,
                "has_relationships": true
            }
        ]
    """
    logger.info("=" * 60)
    logger.info("TOOL: list_entities")
    logger.info("=" * 60)

    entities = spec.get("entities", [])
    relationships = spec.get("relationships", [])

    logger.info("  Found %d entities and %d relationships in spec", len(entities), len(relationships))

    # Build set of entity names that have relationships
    entities_with_relationships = set()
    for rel in relationships:
        entities_with_relationships.add(rel.get("firstEntity"))
        entities_with_relationships.add(rel.get("secondEntity"))

    summaries = []
    for entity in entities:
        entity_id = entity.get("name", "")
        attr_count = len(entity.get("attributes", []))
        metric_count = len(entity.get("metrics", []))
        filter_count = len(entity.get("filters", []))
        has_rels = entity_id in entities_with_relationships

        summary = {
            "id": entity_id,
            "name": entity.get("name", entity_id),
            "description": entity.get("description") or "",
            "attribute_count": attr_count,
            "metric_count": metric_count,
            "filter_count": filter_count,
            "has_relationships": has_rels,
        }
        summaries.append(summary)

        logger.info(
            "  - Entity '%s': %d attrs, %d metrics, %d filters, has_rels=%s",
            entity_id,
            attr_count,
            metric_count,
            filter_count,
            has_rels,
        )

    logger.info("TOOL RESULT: list_entities returned %d entity summaries", len(summaries))
    return summaries


def get_attribute_values(
    spec: dict,
    entity_id: str,
    attribute_name: str,
    limit: int = 20,
    *,
    semantic_model_id: str | None = None,
    version_id: str | None = None,
) -> dict[str, Any]:
    """Get distinct values for one attribute when targeted value hints are needed."""
    logger.info("=" * 60)
    logger.info("TOOL: get_attribute_values")
    logger.info("=" * 60)
    logger.info("  entity_id: %s", entity_id)
    logger.info("  attribute: %s", attribute_name)
    logger.info("  requested_limit: %s", limit)

    if not isinstance(limit, int):
        limit = 20
    limit = max(1, min(limit, MAX_ATTRIBUTE_VALUES_LIMIT))

    entity = next((e for e in spec.get("entities", []) if e.get("name") == entity_id), None)
    if entity is None:
        error = f"Entity '{entity_id}' not found"
        logger.warning("TOOL RESULT: get_attribute_values - %s", error)
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": [],
            "source": "NONE",
            "mode": "NONE",
            "error": error,
        }

    attr = next((a for a in entity.get("attributes", []) if a.get("name") == attribute_name), None)
    if attr is None:
        error = f"Attribute '{attribute_name}' not found on entity '{entity_id}'"
        logger.warning("TOOL RESULT: get_attribute_values - %s", error)
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": [],
            "source": "NONE",
            "mode": "NONE",
            "error": error,
        }

    if attr.get("type") == "EXPRESSION":
        error = f"Attribute '{entity_id}.{attribute_name}' is computed; only physical columns are supported"
        logger.warning("TOOL RESULT: get_attribute_values - %s", error)
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": [],
            "source": "NONE",
            "mode": "NONE",
            "error": error,
        }

    mode = get_distinct_values_mode(attr) or "NONE"
    manual_values = list(attr.get("manualValues") or [])

    if manual_values:
        truncated = len(manual_values) > limit
        values = manual_values[:limit]
        logger.info(
            "TOOL RESULT: get_attribute_values -> %d values from MANUAL mode%s",
            len(values),
            " [truncated]" if truncated else "",
        )
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": values,
            "source": "MANUAL",
            "mode": "MANUAL",
            "truncated": truncated,
        }

    if not is_auto_values_enabled(attr):
        if mode == "MANUAL":
            message = f"Attribute '{entity_id}.{attribute_name}' is in MANUAL mode but has no configured values"
        else:
            message = (
                f"Distinct values are disabled for '{entity_id}.{attribute_name}' "
                "(set distinctValuesHandlingMode to AUTO_INDEX or provide MANUAL values)"
            )
        logger.info("TOOL RESULT: get_attribute_values -> no values (%s)", message)
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": [],
            "source": "NONE",
            "mode": mode,
            "message": message,
        }

    sample_limit = get_sample_limit(attr)
    effective_limit = min(limit, sample_limit) if sample_limit else limit

    if not semantic_model_id or not version_id:
        error = "Semantic model context is missing; cannot fetch AUTO distinct values"
        logger.warning("TOOL RESULT: get_attribute_values - %s", error)
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": [],
            "source": "NONE",
            "mode": mode,
            "error": error,
        }

    try:
        values = _fetch_dynamic_sample_values(
            semantic_model_id=semantic_model_id,
            version_id=version_id,
            entity_name=entity_id,
            attribute_name=attribute_name,
            limit=effective_limit,
        )
        logger.info(
            "TOOL RESULT: get_attribute_values -> %d values from AUTO mode (limit=%d)",
            len(values),
            effective_limit,
        )
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": values,
            "source": "AUTO",
            "mode": mode,
            "truncated": sample_limit is not None and effective_limit < sample_limit and len(values) >= effective_limit,
        }
    except Exception as e:
        logger.warning("TOOL RESULT: get_attribute_values FAILED for %s.%s: %s", entity_id, attribute_name, e)
        return {
            "entity_id": entity_id,
            "attribute": attribute_name,
            "values": [],
            "source": "NONE",
            "mode": mode,
            "error": str(e),
        }


def get_entity_attributes(
    spec: dict,
    entity_id: str,
    compact: bool = False,
    *,
    semantic_model_id: str | None = None,
    version_id: str | None = None,
    private_editor_data: dict | None = None,
) -> dict[str, Any] | None:
    """
    Get detailed attributes for an entity.

    Args:
        spec: SemanticModelSpec as dict
        entity_id: ID of the entity to get attributes for
        compact: If True, return only name and type (for large entities)

    Returns:
        Entity details or None if not found:
        {
            "entity_id": "customers",
            "name": "Customers",
            "description": "...",
            "source_type": "dataset",
            "source_info": {"project_key": "...", "dataset": "..."},
            "instructions": "...",
            "attributes": [
                {
                    "name": "customer_id",
                    "type": "INTEGER",
                    "description": "...",
                    "expression": "customer_id",
                    "sample_values": ["1", "2", "3"],
                    "resolvable": true,
                    "instructions": "..."
                }
            ],
            "metrics": [...],
            "filters": [...],
            "primaryKey": ["customer_id"]
        }
    """
    logger.info("=" * 60)
    logger.info("TOOL: get_entity_attributes")
    logger.info("=" * 60)
    logger.info("  entity_id: %s", entity_id)
    logger.info("  compact: %s", compact)

    entities = spec.get("entities", [])

    # Find the entity
    entity = None
    for e in entities:
        if e.get("name") == entity_id:
            entity = e
            break

    if entity is None:
        logger.warning("TOOL RESULT: get_entity_attributes - entity '%s' NOT FOUND", entity_id)
        return None

    # Build source info
    source_type = "DATASET"
    source_info = {
        "datasetRef": entity.get("datasetRef"),
    }

    # Process attributes
    attributes = []
    for attr in entity.get("attributes", []):
        if compact:
            # Compact mode: only name and type
            attr_info = {
                "name": attr.get("name"),
                "type": attr.get("type"),
                "dssType": attr.get("dssType"),
            }
        else:
            # Full mode: all details
            manual_values = attr.get("manualValues") or []
            mode = get_distinct_values_mode(attr)
            sample_values_source = "MANUAL" if manual_values else "NONE"

            if not manual_values and is_auto_values_enabled(attr):
                sample_limit = get_sample_limit(attr)
                if (
                    sample_limit
                    and semantic_model_id
                    and version_id
                    and attr.get("type") != "EXPRESSION"
                ):
                    try:
                        fetch_limit = sample_limit
                        manual_values = _fetch_dynamic_sample_values(
                            semantic_model_id,
                            version_id,
                            entity_id,
                            attr.get("name"),
                            fetch_limit,
                        )
                        if manual_values:
                            sample_values_source = "AUTO"
                    except Exception as e:
                        logger.warning(
                            "Failed to fetch sample values for %s.%s: %s", entity_id, attr.get("name"), e
                        )
            elif mode == "MANUAL":
                logger.info("    sample values for %s.%s: 0 (MANUAL mode, empty list)", entity_id, attr.get("name"))
            attr_info = {
                "name": attr.get("name"),
                "type": attr.get("type"),
                "dssType": attr.get("dssType"),
                "description": attr.get("description"),
                "column": attr.get("column"),
                "pseudoSQLExpression": attr.get("pseudoSQLExpression"),
                "manualValues": manual_values,
                "sampleValuesSource": sample_values_source,
                "resolveInUserRequests": attr.get("resolveInUserRequests", False),
                "llmInstructions": attr.get("llmInstructions"),
            }
            if manual_values:
                logger.info(
                    "    sample values for %s.%s: %d (%s)",
                    entity_id,
                    attr.get("name"),
                    len(manual_values),
                    sample_values_source,
                )
        attributes.append(attr_info)

    # Process metrics
    metrics = []
    for metric in entity.get("metrics", []):
        if compact:
            metric_info = {
                "name": metric.get("name"),
                "pseudoSQLExpression": metric.get("pseudoSQLExpression"),
            }
        else:
            metric_info = {
                "name": metric.get("name"),
                "description": metric.get("description"),
                "pseudoSQLExpression": metric.get("pseudoSQLExpression"),
                "llmInstructions": metric.get("llmInstructions"),
            }
        metrics.append(metric_info)

    # Process filters
    filters = []
    for filt in entity.get("filters", []):
        if compact:
            filter_info = {
                "name": filt.get("name"),
                "pseudoSQLExpression": filt.get("pseudoSQLExpression"),
            }
        else:
            filter_info = {
                "name": filt.get("name"),
                "description": filt.get("description"),
                "pseudoSQLExpression": filt.get("pseudoSQLExpression"),
                "llmInstructions": filt.get("llmInstructions"),
            }
        filters.append(filter_info)

    result = {
        "entity_id": entity_id,
        "name": entity.get("name", entity_id),
        "description": entity.get("description"),
        "source_type": source_type,
        "source_info": source_info,
        "attributes": attributes,
        "metrics": metrics,
        "filters": filters,
        "primaryKey": (entity.get("primaryKey") or {}).get("attributes", []),
    }

    logger.info("  Entity found: %s", entity.get("name", entity_id))
    logger.info("  Source: %s -> %s", source_type, source_info)
    logger.info("  Attributes: %d columns", len(attributes))
    if attributes and not compact:
        for attr in attributes[:5]:  # Show first 5
            logger.info("    - %s (%s)", attr.get("name"), attr.get("type"))
        if len(attributes) > 5:
            logger.info("    ... and %d more", len(attributes) - 5)
    logger.info("  Metrics: %d", len(metrics))
    logger.info("  Filters: %d", len(filters))
    logger.info(
        "TOOL RESULT: get_entity_attributes returned entity '%s' with %d attrs (compact=%s)",
        entity_id,
        len(attributes),
        compact,
    )
    return result


def get_relationships(spec: dict, entity_ids: list[str]) -> list[dict]:
    """
    Get relationships between specified entities.

    Args:
        spec: SemanticModelSpec as dict
        entity_ids: List of entity IDs to get relationships for

    Returns:
        List of relevant relationships:
        [
            {
                "firstEntity": "orders",
                "secondEntity": "customers",
                "pseudoSQLExpression": "orders.customer_id = customers.id",
                "description": "Order belongs to customer"
            }
        ]
    """
    logger.info("=" * 60)
    logger.info("TOOL: get_relationships")
    logger.info("=" * 60)
    logger.info("  entity_ids: %s", entity_ids)

    relationships = spec.get("relationships", [])
    entity_set = set(entity_ids)

    logger.info("  Total relationships in spec: %d", len(relationships))

    relevant = []
    for rel in relationships:
        left = rel.get("firstEntity")
        right = rel.get("secondEntity")

        # Include if both entities are in the requested set
        if left in entity_set and right in entity_set:
            relevant.append(
                {
                    "firstEntity": left,
                    "secondEntity": right,
                    "pseudoSQLExpression": rel.get("pseudoSQLExpression"),
                    "description": rel.get("description"),
                }
            )
            logger.info("  Found: %s <-> %s", left, right)

    logger.info("TOOL RESULT: get_relationships found %d relationships for entities %s", len(relevant), entity_ids)
    return relevant
