"""Module for generating reflection subjects from personal memories.

This module provides the GetReflectionSubjectOp class which retrieves
unreflected memory nodes, generates reflection prompts with current insights,
invokes an LLM for fresh insights, parses the LLM responses, forms new
insight nodes, and updates memory statuses accordingly.
"""

from typing import List

from flowllm.core.context import C
from flowllm.core.op import BaseAsyncOp
from flowllm.core.schema import Message
from loguru import logger

from reme_ai.schema.memory import BaseMemory, PersonalMemory


@C.register_op()
class GetReflectionSubjectOp(BaseAsyncOp):
    """
    A specialized operation class responsible for retrieving unreflected memory nodes,
    generating reflection prompts with current insights, invoking an LLM for fresh insights,
    parsing the LLM responses, forming new insight nodes, and updating memory statuses accordingly.
    """

    file_path: str = __file__

    def new_insight_memory(self, insight_content: str, target: str) -> PersonalMemory:
        """
        Creates a new PersonalMemory for an insight with the given content.

        Args:
            insight_content (str): The content of the insight.
            target (str): The target person the insight is about.

        Returns:
            PersonalMemory: A new PersonalMemory instance representing the insight.
        """
        return PersonalMemory(
            workspace_id=self.context.get("workspace_id", ""),
            content=insight_content,
            target=target,
            reflection_subject=insight_content,  # Store the subject in the dedicated field
            author=getattr(self.llm, "model_name", "system"),
            metadata={
                "insight_type": "reflection_subject",
                "memory_type": "personal_topic",
            },
        )

    async def async_execute(self):
        """
        Generate reflection subjects (topics) from personal memories for insight extraction.

        Process:
        1. Retrieve personal memories and existing insights from context
        2. Check if sufficient memories exist for reflection
        3. Generate new reflection subjects using LLM
        4. Create insight memory objects for new subjects
        5. Store results in context for next operation
        """
        # Get memories from previous operation
        personal_memories = self.context.response.metadata.get("personal_memories", [])
        existing_insights = self.context.response.metadata.get("existing_insights", [])

        # Get operation parameters
        reflect_obs_cnt_threshold = self.op_params.get("reflect_obs_cnt_threshold", 10)
        reflect_num_questions = self.op_params.get("reflect_num_questions", 3)
        user_name = self.context.get("user_name", "user")

        # Validate sufficient memories for reflection
        if len(personal_memories) < reflect_obs_cnt_threshold:
            logger.info(f"Insufficient memories for reflection: {len(personal_memories)} < {reflect_obs_cnt_threshold}")
            self.context.response.metadata["insight_memories"] = []
            return

        # Extract existing insight subjects to avoid duplication
        existing_subjects = []
        if existing_insights:
            existing_subjects = [
                memory.content for memory in existing_insights if hasattr(memory, "content") and memory.content
            ]

        logger.info(f"Found {len(existing_subjects)} existing insight subjects")

        # Prepare memory content for LLM analysis
        memory_contents = []
        for memory in personal_memories:
            if hasattr(memory, "content") and memory.content.strip():
                memory_contents.append(memory.content.strip())

        if not memory_contents:
            logger.warning("No valid memory content found for reflection")
            self.context.response.metadata["insight_memories"] = []
            return

        # Generate reflection subjects using LLM
        insight_memories = await self._generate_reflection_subjects(
            memory_contents,
            existing_subjects,
            user_name,
            reflect_num_questions,
        )

        # Store results in context
        self.context.response.metadata["insight_memories"] = insight_memories
        logger.info(f"Generated {len(insight_memories)} new reflection subject memories")

    async def _generate_reflection_subjects(
        self,
        memory_contents: List[str],
        existing_subjects: List[str],
        user_name: str,
        num_questions: int,
    ) -> List[BaseMemory]:
        """
        Generate new reflection subjects using LLM analysis of memory contents.

        Args:
            memory_contents: List of memory content strings
            existing_subjects: List of already existing subject strings
            user_name: Target username
            num_questions: Maximum number of new subjects to generate

        Returns:
            List of PersonalMemory objects representing new reflection subjects
        """
        # Build LLM prompt
        system_prompt = self.prompt_format(
            prompt_name="get_reflection_subject_system",
            user_name=user_name,
            num_questions=num_questions,
        )
        few_shot = self.prompt_format(
            prompt_name="get_reflection_subject_few_shot",
            user_name=user_name,
        )
        user_query = self.prompt_format(
            prompt_name="get_reflection_subject_user_query",
            user_name=user_name,
            exist_keys=", ".join(existing_subjects) if existing_subjects else "None",
            user_query="\n".join(memory_contents),
        )

        full_prompt = f"{system_prompt}\n\n{few_shot}\n\n{user_query}"
        logger.info(f"Reflection subject prompt length: {len(full_prompt)} chars")

        def parse_reflection_response(message: Message) -> List[BaseMemory]:
            """Parse LLM response and create insight memories"""
            response_text = message.content
            logger.info(f"Reflection subjects response: {response_text}")

            # Parse new subjects using class method
            new_subjects = GetReflectionSubjectOp.parse_reflection_subjects_response(response_text, existing_subjects)

            # Create insight memory objects
            insight_memories = []
            for subject in new_subjects:
                insight_memory = self.new_insight_memory(
                    insight_content=subject,
                    target=user_name,
                )
                insight_memories.append(insight_memory)
                logger.info(f"Created reflection subject: {subject}")

            return insight_memories

        # Generate subjects using LLM
        return await self.llm.achat(messages=[Message(content=full_prompt)], callback_fn=parse_reflection_response)

    def get_language_value(self, value_dict: dict):
        """Get language-specific value from dictionary.

        Args:
            value_dict: Dictionary mapping language codes to values

        Returns:
            The value corresponding to the current language, or the English
            value as fallback if the current language is not found
        """
        return value_dict.get(self.language, value_dict.get("en"))

    @staticmethod
    def parse_reflection_subjects_response(response_text: str, existing_subjects: List[str] = None) -> List[str]:
        """Parse reflection subjects response to extract new subject attributes"""
        if existing_subjects is None:
            existing_subjects = []

        # Split response into lines and clean up
        lines = response_text.strip().split("\n")
        subjects = []

        for line in lines:
            line = line.strip()
            # Skip empty lines, "None" responses, and existing subjects
            # Check basic validity first
            if not line or line in ["无", "None", ""] or len(line) <= 1:
                continue
            # Check if it's a header or duplicate
            is_header = line.startswith("新增") or line.startswith("New ")
            if is_header or line in existing_subjects:
                continue
            subjects.append(line)

        logger.info(f"Parsed {len(subjects)} new reflection subjects from response")
        return subjects
