"""
Indexer - Project document and Insight indexer

Splits documents into paragraph-level chunks, generates vectors via Embedder,
and stores them in VectorStore for semantic search.

Index sources:
    - Project docs: CONTRIBUTING_AI.md, CONTEXT.md, DECISIONS.md, ROADMAP.md, PRD.md
    - Insight YAML: title + body + tags
    - Code files (optional): docstring / function signatures
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional

import yaml

from ..insight.embedder import Embedder, EmbedderConfig
from .vector_store import VectorDocument, VectorStore

logger = logging.getLogger(__name__)

# Default document files to index
DEFAULT_DOC_FILES = [
    "CONTRIBUTING_AI.md",
    "docs/CONTEXT.md",
    "docs/DECISIONS.md",
    "docs/ROADMAP.md",
    "docs/PRD.md",
    "docs/CHANGELOG.md",
]


@dataclass
class IndexStats:
    """Index statistics"""

    documents_indexed: int = 0
    insights_indexed: int = 0
    chunks_total: int = 0
    skipped: int = 0
    errors: List[str] = field(default_factory=list)


def _split_markdown_by_heading(text: str, source: str) -> List[Dict[str, str]]:
    """Split Markdown document into chunks by heading

    Each chunk contains a heading and its content until the next same-level or higher-level heading.
    """
    lines = text.splitlines()
    chunks: List[Dict[str, str]] = []
    current_heading = ""
    current_lines: List[str] = []

    for line in lines:
        stripped = line.strip()
        if stripped.startswith("#"):
            # Save current chunk
            if current_lines:
                content = "\n".join(current_lines).strip()
                if content:
                    chunks.append({
                        "heading": current_heading,
                        "content": content,
                        "source": source,
                    })
            current_heading = stripped
            current_lines = [line]
        else:
            current_lines.append(line)

    # Last chunk
    if current_lines:
        content = "\n".join(current_lines).strip()
        if content:
            chunks.append({
                "heading": current_heading,
                "content": content,
                "source": source,
            })

    return chunks


def _insight_to_text(insight_data: dict) -> str:
    """Convert Insight YAML data to indexable text"""
    parts = []
    title = insight_data.get("title", "")
    if title:
        parts.append(str(title))

    body = insight_data.get("body", "")
    if isinstance(body, dict):
        # Structured body (scenario/approach/validation/constraints)
        for key, val in body.items():
            if isinstance(val, list):
                parts.append(f"{key}: {'; '.join(str(v) for v in val)}")
            elif val:
                parts.append(f"{key}: {val}")
    elif body:
        parts.append(str(body))

    summary = insight_data.get("summary", "")
    if summary:
        parts.append(str(summary))

    tags = insight_data.get("tags", [])
    if tags:
        parts.append(f"Tags: {', '.join(str(t) for t in tags)}")

    category = insight_data.get("category", "")
    if category:
        parts.append(f"Category: {category}")

    return "\n".join(parts)


class Indexer:
    """Project indexer

    Usage:
        indexer = Indexer(project_root)
        stats = indexer.index_all()
    """

    def __init__(
        self,
        project_root: Path,
        embedder: Optional[Embedder] = None,
        store: Optional[VectorStore] = None,
        doc_files: Optional[List[str]] = None,
    ):
        self._project_root = project_root
        self._doc_files = doc_files or list(DEFAULT_DOC_FILES)

        # Embedder
        if embedder:
            self._embedder = embedder
        else:
            self._embedder = Embedder(EmbedderConfig(backend="auto"))

        # VectorStore
        if store:
            self._store = store
        else:
            db_path = project_root / ".vibecollab" / "vectors" / "index.db"
            self._store = VectorStore(db_path=db_path, dimensions=self._embedder.dimensions)

    @property
    def store(self) -> VectorStore:
        return self._store

    @property
    def embedder(self) -> Embedder:
        return self._embedder

    def index_all(self) -> IndexStats:
        """Index all documents and Insights"""
        stats = IndexStats()

        # Index documents
        doc_stats = self.index_documents()
        stats.documents_indexed += doc_stats.documents_indexed
        stats.chunks_total += doc_stats.chunks_total
        stats.skipped += doc_stats.skipped
        stats.errors.extend(doc_stats.errors)

        # Index Insights
        ins_stats = self.index_insights()
        stats.insights_indexed += ins_stats.insights_indexed
        stats.chunks_total += ins_stats.chunks_total
        stats.errors.extend(ins_stats.errors)

        return stats

    def index_documents(self) -> IndexStats:
        """Index project document files"""
        stats = IndexStats()

        for doc_file in self._doc_files:
            full_path = self._project_root / doc_file
            if not full_path.exists():
                stats.skipped += 1
                continue

            try:
                text = full_path.read_text(encoding="utf-8")
                if not text.strip():
                    stats.skipped += 1
                    continue

                chunks = _split_markdown_by_heading(text, doc_file)
                if not chunks:
                    stats.skipped += 1
                    continue

                # Batch embed
                texts = [c["content"] for c in chunks]
                vectors = self._embedder.embed_texts(texts)

                docs = []
                for i, (chunk, vector) in enumerate(zip(chunks, vectors)):
                    doc_id = f"doc:{doc_file}:{i}"
                    docs.append(VectorDocument(
                        doc_id=doc_id,
                        text=chunk["content"][:2000],  # Truncate long text
                        vector=vector,
                        source=doc_file,
                        source_type="document",
                        metadata={"heading": chunk["heading"]},
                    ))

                self._store.upsert_batch(docs)
                stats.documents_indexed += 1
                stats.chunks_total += len(docs)

            except Exception as e:
                stats.errors.append(f"{doc_file}: {e}")
                logger.warning("Failed to index document: %s -- %s", doc_file, e)

        return stats

    def index_insights(self) -> IndexStats:
        """Index Insight YAML files"""
        stats = IndexStats()
        insights_dir = self._project_root / ".vibecollab" / "insights"

        if not insights_dir.exists():
            return stats

        insight_files = sorted(insights_dir.glob("INS-*.yaml"))
        if not insight_files:
            return stats

        docs = []
        texts = []
        for ins_file in insight_files:
            try:
                with open(ins_file, "r", encoding="utf-8") as f:
                    data = yaml.safe_load(f)
                if not data:
                    continue

                ins_id = data.get("id", ins_file.stem)
                text = _insight_to_text(data)
                if not text.strip():
                    continue

                texts.append(text)
                docs.append({
                    "ins_id": ins_id,
                    "text": text,
                    "file": str(ins_file.relative_to(self._project_root)),
                    "data": data,
                })

            except Exception as e:
                stats.errors.append(f"{ins_file.name}: {e}")

        if not texts:
            return stats

        # Batch embed
        vectors = self._embedder.embed_texts(texts)

        vec_docs = []
        for doc_info, vector in zip(docs, vectors):
            vec_docs.append(VectorDocument(
                doc_id=f"insight:{doc_info['ins_id']}",
                text=doc_info["text"][:2000],
                vector=vector,
                source=doc_info["file"],
                source_type="insight",
                metadata={
                    "title": doc_info["data"].get("title", ""),
                    "tags": doc_info["data"].get("tags", []),
                    "category": doc_info["data"].get("category", ""),
                },
            ))

        count = self._store.upsert_batch(vec_docs)
        stats.insights_indexed = count
        stats.chunks_total += count

        return stats

    def search(
        self,
        query: str,
        top_k: int = 10,
        source_type: Optional[str] = None,
        min_score: float = 0.0,
    ) -> List:
        """Semantic search"""
        query_vector = self._embedder.embed_text(query)
        return self._store.search(
            query_vector,
            top_k=top_k,
            source_type=source_type,
            min_score=min_score,
        )
