"""Session name generation service.

Automatically generates meaningful session names using a fast, cheap model
based on the first user message in a chat session.
"""

from alloy_runtime_sdk.api_client.client import ApiClient
from alloy_runtime_types.dtos.generation import DirectModelSource, GenerateTextRequest
from alloy_runtime_types.enums.provider import Provider
from alloy_runtime_sdk.logging.config import get_logger

logger = get_logger(__name__)

# Fallback models to try for name generation (fast, cheap models)
# These are ordered by preference - will try each in sequence until one works
# Note: Model names must match what's configured in the system
FALLBACK_MODELS: list[tuple[str, str]] = [
    ("google", "gemini-2.0-flash-lite"),
    ("google", "gemini-1.5-flash"),
    ("google", "gemini-2.0-flash"),
    ("openai", "gpt-4o-mini"),
    ("openai", "gpt-3.5-turbo"),
    ("anthropic", "claude-3-5-haiku-latest"),
    ("anthropic", "claude-3-haiku-20240307"),
    ("groq", "llama-3.1-8b-instant"),
    ("groq", "llama3-8b-8192"),
    ("deepseek", "deepseek-chat"),
]

# Prompt for generating session names
NAME_GENERATION_PROMPT = """Generate a short, descriptive title (3-6 words) for a chat conversation based on the user's first message. The title should capture the main topic or intent.

Rules:
- Return ONLY the title, nothing else
- No quotes, punctuation at the end, or explanations
- Use title case
- Be specific but concise
- If the message is a greeting or very generic, create a generic but friendly title

Examples:
- "Help me write a Python script" -> "Python Script Assistance"
- "What's the weather like?" -> "Weather Information Request"
- "Hello!" -> "New Conversation"
- "Debug this React component" -> "React Component Debugging"

User's first message:
{message}

Title:"""


class SessionNameGenerator:
    """Generates session names using a fast, cheap model.

    Tries multiple fallback models to ensure name generation works even if
    some providers are not configured.

    Usage:
        generator = SessionNameGenerator()
        name = await generator.generate_name(client, "Help me write a function")
        # Returns something like "Function Writing Help"
    """

    # Cheap models that are OK to use for naming (won't cost much)
    _CHEAP_MODEL_PATTERNS = [
        "flash",
        "mini",
        "nano",
        "haiku",
        "instant",
        "3.5-turbo",
        "8b",
        "7b",
    ]

    def __init__(
        self,
        fallback_models: list[tuple[str, str]] | None = None,
        max_message_length: int = 500,
    ) -> None:
        """Initialize the session name generator.

        Args:
            fallback_models: List of (provider_key, model_name) tuples to try.
                            Defaults to FALLBACK_MODELS.
            max_message_length: Maximum length of user message to include in prompt.
        """
        self._fallback_models = fallback_models or FALLBACK_MODELS
        self._max_message_length = max_message_length

    def _is_cheap_model(self, model_name: str) -> bool:
        """Check if a model is considered cheap enough for naming tasks."""
        model_lower = model_name.lower()
        return any(pattern in model_lower for pattern in self._CHEAP_MODEL_PATTERNS)

    async def generate_name(
        self,
        client: ApiClient,
        first_message: str,
        session_provider_key: str | None = None,
        session_model_name: str | None = None,
    ) -> str | None:
        """Generate a session name based on the first message.

        Tries each fallback model in sequence until one succeeds.
        If a session model is provided and it's cheap, tries that first.

        Args:
            client: ApiClient for making API calls.
            first_message: The user's first message in the session.
            session_provider_key: Optional provider key from the session (tried first if cheap).
            session_model_name: Optional model name from the session (tried first if cheap).

        Returns:
            Generated session name (3-6 words), or None if all models fail.
        """
        logger.info(
            "session_name_generator_starting",
            first_message_length=len(first_message),
            session_provider_key=session_provider_key,
            session_model_name=session_model_name,
        )

        prompt = NAME_GENERATION_PROMPT.format(message=first_message)

        # Build list of models to try
        models_to_try: list[tuple[str, str]] = []

        # If session has a cheap model, try it first (it's likely configured)
        if (
            session_provider_key
            and session_model_name
            and self._is_cheap_model(session_model_name)
        ):
            models_to_try.append((session_provider_key, session_model_name))
            logger.info(
                "session_name_using_session_model_first",
                provider=session_provider_key,
                model=session_model_name,
            )
        else:
            logger.info(
                "session_name_not_using_session_model",
                session_provider_key=session_provider_key,
                session_model_name=session_model_name,
                is_cheap=self._is_cheap_model(session_model_name)
                if session_model_name
                else None,
            )

        # Add fallback models
        models_to_try.extend(self._fallback_models)
        logger.info(
            "session_name_models_to_try",
            total_models=len(models_to_try),
            fallback_models=len(self._fallback_models),
        )

        for idx, (provider_key, model_name) in enumerate(models_to_try):
            try:
                logger.info(
                    "session_name_generation_attempt",
                    attempt_number=idx + 1,
                    provider=provider_key,
                    model=model_name,
                )

                model_source = DirectModelSource(
                    provider_key=Provider(provider_key),
                    provider_model_name=model_name,
                )

                request = GenerateTextRequest(
                    model_source=model_source,
                    user_message=prompt,
                    stream=False,
                    tags=["system.session_naming"],
                )

                response = await client.generate_text(request)
                logger.info(
                    "session_name_generation_response",
                    provider=provider_key,
                    model=model_name,
                    raw_output=response.output_text if response.output_text else None,
                )

                generated_name = self._clean_name(response.output_text)

                if generated_name:
                    logger.info(
                        "session_name_generated_success",
                        provider=provider_key,
                        model=model_name,
                        generated_name=generated_name,
                    )
                    return generated_name
                else:
                    logger.warning(
                        "session_name_cleaned_to_empty",
                        provider=provider_key,
                        model=model_name,
                        raw_output=response.output_text,
                    )

            except Exception as e:
                logger.info(
                    "session_name_generation_model_failed",
                    attempt_number=idx + 1,
                    provider=provider_key,
                    model=model_name,
                    error=str(e),
                    error_type=type(e).__name__,
                )
                continue

        logger.warning(
            "session_name_generation_all_models_failed",
            models_tried=len(models_to_try),
        )
        return None

    def _clean_name(self, raw_name: str) -> str | None:
        """Clean and validate the generated name.

        Args:
            raw_name: Raw output from the model.

        Returns:
            Cleaned name (max 100 chars), or None if invalid.
        """
        # Strip whitespace and quotes
        name = raw_name.strip().strip("\"'")

        # Remove common prefixes the model might add
        prefixes_to_remove = ["Title:", "title:", "Name:", "name:"]
        for prefix in prefixes_to_remove:
            if name.startswith(prefix):
                name = name[len(prefix) :].strip()

        # Validate length
        if not name or len(name) < 2:
            return None

        return name
