"""Memory indexer for vector storage.

Parses MEMORY.md and conversation history into chunks for indexing.
"""

from __future__ import annotations

import re
from datetime import datetime
from pathlib import Path
from typing import Any

from oclawma.memory.store import MemoryChunk


class MemoryIndexer:
    """Indexes memory files into chunks for vector storage."""

    # Maximum chunk size (characters)
    DEFAULT_CHUNK_SIZE = 1000
    DEFAULT_CHUNK_OVERLAP = 200

    def __init__(
        self,
        chunk_size: int = DEFAULT_CHUNK_SIZE,
        chunk_overlap: int = DEFAULT_CHUNK_OVERLAP,
    ) -> None:
        """Initialize the memory indexer.

        Args:
            chunk_size: Maximum size of each chunk in characters.
            chunk_overlap: Overlap between chunks in characters.
        """
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap

    def parse_memory_md(self, content: str, source: str = "MEMORY.md") -> list[MemoryChunk]:
        """Parse MEMORY.md content into chunks.

        Args:
            content: Content of MEMORY.md.
            source: Source identifier.

        Returns:
            List of memory chunks.
        """
        chunks = []

        # Split by sections (lines starting with ##)
        sections = re.split(r"\n(?=##\s)", content)

        for section in sections:
            section = section.strip()
            if not section:
                continue

            # Extract section title
            title_match = re.match(r"##+\s+(.+)", section)
            section_title = title_match.group(1) if title_match else "General"

            # Determine chunk type from content
            chunk_type = self._determine_chunk_type(section, section_title)

            # Split large sections into smaller chunks
            section_chunks = self._chunk_text(section, self.chunk_size, self.chunk_overlap)

            for i, chunk_text in enumerate(section_chunks):
                metadata = {
                    "section": section_title,
                    "chunk_index": i,
                    "total_chunks": len(section_chunks),
                }

                chunks.append(
                    MemoryChunk(
                        content=chunk_text,
                        source=source,
                        chunk_type=chunk_type,
                        created_at=datetime.now(),
                        metadata=metadata,
                    )
                )

        return chunks

    def parse_conversation_history(
        self,
        messages: list[dict[str, Any]],
        source: str = "conversation",
    ) -> list[MemoryChunk]:
        """Parse conversation history into chunks.

        Args:
            messages: List of conversation messages.
            source: Source identifier.

        Returns:
            List of memory chunks.
        """
        chunks = []

        for i, msg in enumerate(messages):
            role = msg.get("role", "unknown")
            content = msg.get("content", "")

            if not content or not content.strip():
                continue

            # Skip system messages
            if role == "system":
                continue

            # Determine chunk type based on role and content
            chunk_type = self._determine_conversation_chunk_type(content, role)

            metadata = {
                "role": role,
                "message_index": i,
            }

            # Split long messages
            text_chunks = self._chunk_text(content, self.chunk_size, self.chunk_overlap)

            for j, chunk_text in enumerate(text_chunks):
                chunk_metadata = {
                    **metadata,
                    "chunk_index": j,
                    "total_chunks": len(text_chunks),
                }

                chunks.append(
                    MemoryChunk(
                        content=chunk_text,
                        source=source,
                        chunk_type=chunk_type,
                        created_at=datetime.now(),
                        metadata=chunk_metadata,
                    )
                )

        return chunks

    def parse_daily_notes(
        self,
        content: str,
        date: str,
        source: str = "daily_notes",
    ) -> list[MemoryChunk]:
        """Parse daily notes file into chunks.

        Args:
            content: Content of daily notes.
            date: Date string (YYYY-MM-DD).
            source: Source identifier.

        Returns:
            List of memory chunks.
        """
        chunks = []

        # Split by sections
        sections = re.split(r"\n(?=##?\s)", content)

        for section in sections:
            section = section.strip()
            if not section:
                continue

            # Extract section title
            title_match = re.match(r"##?\s+(.+)", section)
            section_title = title_match.group(1) if title_match else "Events"

            chunk_type = self._determine_chunk_type(section, section_title)

            section_chunks = self._chunk_text(section, self.chunk_size, self.chunk_overlap)

            for i, chunk_text in enumerate(section_chunks):
                metadata = {
                    "date": date,
                    "section": section_title,
                    "chunk_index": i,
                    "total_chunks": len(section_chunks),
                }

                chunks.append(
                    MemoryChunk(
                        content=chunk_text,
                        source=source,
                        chunk_type=chunk_type,
                        created_at=datetime.now(),
                        metadata=metadata,
                    )
                )

        return chunks

    def index_memory_file(
        self,
        file_path: Path,
        embedding_provider: Any,
        store: Any,
    ) -> int:
        """Index a memory file into the vector store.

        Args:
            file_path: Path to memory file.
            embedding_provider: Provider for generating embeddings.
            store: Vector memory store.

        Returns:
            Number of chunks indexed.
        """
        if not file_path.exists():
            return 0

        content = file_path.read_text(encoding="utf-8")
        source = file_path.name

        # Parse into chunks
        if source[:4].isdigit() and len(source) > 5 and source[4] == "-":
            # Daily notes file
            date = source.split(".")[0]
            chunks = self.parse_daily_notes(content, date, source)
        else:
            # Regular memory file
            chunks = self.parse_memory_md(content, source)

        if not chunks:
            return 0

        # Delete existing chunks from this source
        store.delete_by_source(source)

        # Generate embeddings
        texts = [chunk.content for chunk in chunks]
        embeddings = embedding_provider.embed_sync(texts)

        # Add to store
        store.add_chunks(chunks, embeddings)

        return len(chunks)

    def index_conversation(
        self,
        messages: list[dict[str, Any]],
        conversation_id: str,
        embedding_provider: Any,
        store: Any,
    ) -> int:
        """Index a conversation into the vector store.

        Args:
            messages: List of conversation messages.
            conversation_id: Unique identifier for the conversation.
            embedding_provider: Provider for generating embeddings.
            store: Vector memory store.

        Returns:
            Number of chunks indexed.
        """
        source = f"conversation_{conversation_id}"

        # Delete existing chunks from this conversation
        store.delete_by_source(source)

        # Parse into chunks
        chunks = self.parse_conversation_history(messages, source)

        if not chunks:
            return 0

        # Generate embeddings
        texts = [chunk.content for chunk in chunks]
        embeddings = embedding_provider.embed_sync(texts)

        # Add to store
        store.add_chunks(chunks, embeddings)

        return len(chunks)

    def _determine_chunk_type(self, content: str, section_title: str) -> str:
        """Determine the type of chunk based on content and section.

        Args:
            content: Chunk content.
            section_title: Section title.

        Returns:
            Chunk type string.
        """
        title_lower = section_title.lower()
        content_lower = content.lower()

        # Check for principles/deals
        if any(word in title_lower for word in ["principle", "deal", "core", "identity"]):
            return "principle"

        # Check for boundaries
        if any(word in title_lower for word in ["boundary", "limit", "rule", "hard line"]):
            return "boundary"

        # Check for technical info
        if any(word in title_lower for word in ["technical", "setup", "config", "tool"]):
            return "technical"

        # Check for learnings
        if any(word in title_lower for word in ["learning", "lesson", "insight"]):
            return "learning"

        # Check for events/tasks
        if any(word in content_lower for word in ["completed", "started", "finished", "task"]):
            return "event"

        return "note"

    def _determine_conversation_chunk_type(self, content: str, role: str) -> str:
        """Determine chunk type for conversation messages.

        Args:
            content: Message content.
            role: Message role (user, assistant, etc.).

        Returns:
            Chunk type string.
        """
        content_lower = content.lower()

        if role == "assistant":
            # Check if it's a fact/learning
            if any(
                phrase in content_lower
                for phrase in [
                    "remember",
                    "i learned",
                    "note that",
                    "important",
                    "should know",
                    "keep in mind",
                    "reminder",
                ]
            ):
                return "fact"

            # Check for decisions
            if any(
                phrase in content_lower
                for phrase in ["decided", "agreed", "will do", "plan is", "going to", "schedule"]
            ):
                return "decision"

            return "response"

        elif role == "user":
            # Check for preferences
            if any(
                phrase in content_lower
                for phrase in ["i prefer", "i like", "i want", "please", "don't", "always", "never"]
            ):
                return "preference"

            return "query"

        return "message"

    def _chunk_text(
        self,
        text: str,
        chunk_size: int,
        chunk_overlap: int,
    ) -> list[str]:
        """Split text into overlapping chunks.

        Args:
            text: Text to split.
            chunk_size: Maximum chunk size.
            chunk_overlap: Overlap between chunks.

        Returns:
            List of text chunks.
        """
        if len(text) <= chunk_size:
            return [text]

        chunks = []
        start = 0

        while start < len(text):
            end = start + chunk_size

            # Try to break at a sentence or paragraph boundary
            if end < len(text):
                # Look for sentence boundary
                for i in range(min(end, len(text) - 1), start + chunk_size // 2, -1):
                    if text[i] in ".!?" and (i + 1 >= len(text) or text[i + 1].isspace()):
                        end = i + 1
                        break
                else:
                    # Look for paragraph boundary
                    for i in range(min(end, len(text) - 1), start + chunk_size // 2, -1):
                        if text[i : i + 2] == "\n\n":
                            end = i + 2
                            break
                    else:
                        # Look for any whitespace
                        for i in range(min(end, len(text) - 1), start + chunk_size // 2, -1):
                            if text[i].isspace():
                                end = i + 1
                                break

            chunks.append(text[start:end].strip())
            start = end - chunk_overlap

        return chunks
