"""Session title generation.

Generates short, descriptive titles for sessions using LLM.
"""

import logging
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

from open_agent_kit.features.team.activity.processor.utils import (
    is_recovery_prompt,
)
from open_agent_kit.features.team.activity.store.sessions import (
    is_session_sufficient,
)

if TYPE_CHECKING:
    from open_agent_kit.features.team.activity.prompts import (
        PromptTemplateConfig,
    )
    from open_agent_kit.features.team.activity.store import (
        ActivityStore,
    )
    from open_agent_kit.features.team.activity.store.models import (
        Session,
    )

logger = logging.getLogger(__name__)


def _get_parent_session_title(
    session: "Session",
    activity_store: "ActivityStore",
) -> str | None:
    """Get title from parent session if linked.

    Args:
        session: Current session.
        activity_store: Activity store for fetching parent.

    Returns:
        Parent session title if available, None otherwise.
    """
    if not session.parent_session_id:
        return None

    parent = activity_store.get_session(session.parent_session_id)
    if parent and parent.title:
        return parent.title

    return None


def generate_session_title(
    session_id: str,
    activity_store: "ActivityStore",
    prompt_config: "PromptTemplateConfig",
    call_llm: Callable[[str], dict[str, Any]],
    min_activities: int | None = None,
) -> str | None:
    """Generate a short title for a session based on its prompts.

    Handles special cases:
    - Skips sessions below quality threshold (< min_activities)
    - Filters out continuation placeholders from prompts (from session transitions)
    - For linked sessions with only placeholders, derives title from parent session

    Args:
        session_id: Session ID to generate title for.
        activity_store: Activity store for fetching batches.
        prompt_config: Prompt template configuration.
        call_llm: Function to call LLM.
        min_activities: Minimum activities threshold. Defaults to MIN_SESSION_ACTIVITIES.
            Pass session_quality.min_activities if available.

    Returns:
        Title text if generated, None otherwise.
    """
    # Check quality threshold - skip sessions with insufficient activities
    if not is_session_sufficient(activity_store, session_id, min_activities=min_activities):
        logger.debug(f"Session {session_id} below quality threshold, skipping title generation")
        return None

    # Get session info for parent linking check
    session = activity_store.get_session(session_id)
    if not session:
        logger.debug(f"Session {session_id} not found, skipping title")
        return None

    # Skip sessions with manually edited titles
    if session.title_manually_edited:
        logger.debug(f"Session {session_id} has manually edited title, skipping generation")
        return None

    # Get prompt batches for this session
    batches = activity_store.get_session_prompt_batches(session_id, limit=10)
    if not batches:
        logger.debug(f"No prompt batches for session {session_id}, skipping title")
        return None

    # Get title template
    title_template = prompt_config.get_template("session-title")
    if not title_template:
        logger.debug("No session-title prompt template found, skipping title")
        return None

    # Format prompt batches for context, filtering out continuation placeholders
    batch_lines = []
    for i, batch in enumerate(batches[:10], 1):
        user_prompt = batch.user_prompt or "(no prompt captured)"

        # Skip continuation placeholders (from session transitions)
        if is_recovery_prompt(user_prompt):
            continue

        # Truncate long prompts
        if len(user_prompt) > 200:
            user_prompt = user_prompt[:197] + "..."
        batch_lines.append(f"{i}. {user_prompt}")

    # If all prompts were placeholders, try to use parent session context
    if not batch_lines:
        parent_title = _get_parent_session_title(session, activity_store)
        if parent_title:
            # Generate a continuation title from parent
            continuation_title = f"Continue: {parent_title}"
            if len(continuation_title) > 80:
                continuation_title = continuation_title[:77] + "..."
            try:
                activity_store.update_session_title(session_id, continuation_title)
                logger.info(
                    f"Generated continuation title for session {session_id}: {continuation_title}"
                )
                return continuation_title
            except (OSError, ValueError, TypeError) as e:
                logger.error(f"Failed to store continuation title: {e}", exc_info=True)
                return None
        else:
            # No parent or parent has no title - skip for now, will retry later
            logger.debug(
                f"Session {session_id} has only placeholder prompts and no parent "
                "title available, skipping title generation"
            )
            return None

    prompt_batches_text = "\n".join(batch_lines)

    # Inject parent context for child sessions so the LLM differentiates titles
    parent_context = ""
    if session.parent_session_id:
        parent_title = _get_parent_session_title(session, activity_store)
        if parent_title:
            parent_context = (
                f"\n\n## Parent Session\n"
                f'This is a continuation of: "{parent_title}"\n'
                f"Generate a DIFFERENT title that captures what THIS specific session accomplished."
            )

    # Build prompt
    prompt = title_template.prompt
    prompt = prompt.replace("{{prompt_batches}}", prompt_batches_text)
    prompt = prompt.replace("{{parent_context}}", parent_context)

    # Call LLM
    result = call_llm(prompt)

    if not result.get("success"):
        logger.warning(f"Session title LLM call failed: {result.get('error')}")
        return None

    # Extract title text
    raw_response = result.get("raw_response", "")
    title: str = str(raw_response).strip() if raw_response else ""
    if not title or len(title) < 3:
        logger.debug("Session title too short or empty")
        return None

    # Clean up common LLM artifacts (quotes around the title)
    if title.startswith('"') and title.endswith('"'):
        title = title[1:-1]
    # Remove trailing punctuation
    title = title.rstrip(".")

    # Truncate if too long (should be 5-10 words)
    if len(title) > 80:
        title = title[:77] + "..."

    # Store title in session
    try:
        activity_store.update_session_title(session_id, title)
        logger.info(f"Generated title for session {session_id}: {title}")
        return title
    except (OSError, ValueError, TypeError) as e:
        logger.error(f"Failed to store session title: {e}", exc_info=True)
        return None


def generate_title_from_summary(
    session_id: str,
    summary: str,
    activity_store: "ActivityStore",
    prompt_config: "PromptTemplateConfig",
    call_llm: Callable[[str], dict[str, Any]],
) -> str | None:
    """Generate a title from a session summary.

    This is more accurate than generating from prompt batches because
    the summary captures the full session context.

    Args:
        session_id: Session ID to generate title for.
        summary: The session summary text.
        activity_store: Activity store for storing the title.
        prompt_config: Prompt template configuration.
        call_llm: Function to call LLM.

    Returns:
        Title text if generated, None otherwise.
    """
    if not summary or len(summary) < 10:
        logger.debug(f"Summary too short for session {session_id}, skipping title from summary")
        return None

    # Skip sessions with manually edited titles
    session = activity_store.get_session(session_id)
    if session and session.title_manually_edited:
        logger.debug(f"Session {session_id} has manually edited title, skipping generation")
        return None

    # Get title-from-summary template
    title_template = prompt_config.get_template("session-title-from-summary")
    if not title_template:
        logger.debug("No session-title-from-summary template found, falling back to prompt-based")
        return None

    # Build prompt with summary
    prompt = title_template.prompt
    # Truncate very long summaries
    truncated_summary = summary[:2000] if len(summary) > 2000 else summary
    prompt = prompt.replace("{{session_summary}}", truncated_summary)

    # Inject parent context for child sessions so the LLM differentiates titles
    parent_context = ""
    if session and session.parent_session_id:
        parent_title = _get_parent_session_title(session, activity_store)
        if parent_title:
            parent_context = (
                f"\n\n## Parent Session\n"
                f'This is a continuation of: "{parent_title}"\n'
                f"Generate a DIFFERENT title that captures what THIS specific session accomplished."
            )
    prompt = prompt.replace("{{parent_context}}", parent_context)

    # Call LLM
    result = call_llm(prompt)

    if not result.get("success"):
        logger.warning(f"Session title from summary LLM call failed: {result.get('error')}")
        return None

    # Extract title text
    raw_response = result.get("raw_response", "")
    title: str = str(raw_response).strip() if raw_response else ""
    if not title or len(title) < 3:
        logger.debug("Session title from summary too short or empty")
        return None

    # Clean up common LLM artifacts
    if title.startswith('"') and title.endswith('"'):
        title = title[1:-1]
    # Remove trailing punctuation
    title = title.rstrip(".")

    # Truncate if too long
    if len(title) > 80:
        title = title[:77] + "..."

    # Store title in session
    try:
        activity_store.update_session_title(session_id, title)
        logger.info(f"Generated title from summary for session {session_id}: {title}")
        return title
    except (OSError, ValueError, TypeError) as e:
        logger.error(f"Failed to store session title from summary: {e}", exc_info=True)
        return None


def generate_pending_titles(
    activity_store: "ActivityStore",
    prompt_config: "PromptTemplateConfig",
    call_llm: Callable[[str], dict[str, Any]],
    limit: int = 5,
    min_activities: int | None = None,
) -> int:
    """Generate titles for sessions that don't have them.

    Called periodically by background processing to ensure all sessions
    get titles, even if they were created before the title feature was added.

    Sessions below the quality threshold (< min_activities) are
    filtered out since they will never be titled, summarized, or embedded.

    Args:
        activity_store: Activity store for fetching sessions.
        prompt_config: Prompt template configuration.
        call_llm: Function to call LLM.
        limit: Maximum sessions to process per call.
        min_activities: Minimum activities threshold. Defaults to MIN_SESSION_ACTIVITIES.
            Pass session_quality.min_activities if available.

    Returns:
        Number of titles generated.
    """
    sessions = activity_store.get_sessions_needing_titles(limit=limit)

    if not sessions:
        return 0

    # Filter out sessions below quality threshold (they'll be cleaned up later)
    quality_sessions = [
        s
        for s in sessions
        if is_session_sufficient(activity_store, s.id, min_activities=min_activities)
    ]

    if not quality_sessions:
        logger.debug(
            f"Found {len(sessions)} sessions needing titles but none meet quality threshold"
        )
        return 0

    generated = 0
    for session in quality_sessions:
        try:
            title = generate_session_title(
                session.id,
                activity_store,
                prompt_config,
                call_llm,
                min_activities=min_activities,
            )
            if title:
                generated += 1
        except (OSError, ValueError, TypeError, RuntimeError) as e:
            logger.warning(f"Failed to generate title for session {session.id[:8]}: {e}")

    return generated
