from __future__ import annotations

import logging
from collections.abc import Callable

from app.core.rag.contracts import RagDocument, RagSource
from app.core.rag.indexing.docs.chunkers.markdown_chunker import MarkdownDocChunker
from app.core.rag.indexing.docs.classifier import DocsClassifier
from app.core.rag.indexing.docs.content_parser import DocsContentParser
from app.core.rag.indexing.docs.document_builder import DocsDocumentBuilder
from app.core.rag.indexing.docs.entity_extractor import DocsEntityExtractor
from app.core.rag.indexing.docs.fact_extractor import DocsFactExtractor
from app.core.rag.indexing.docs.file_filter import DocsFileFilter
from app.core.rag.indexing.docs.frontmatter_parser import DocsFrontmatterParser
from app.core.rag.indexing.docs.frontmatter_view import DocsFrontmatterView
from app.core.rag.indexing.docs.integration_extractor import DocsIntegrationExtractor
from app.core.rag.indexing.docs.relation_extractor import DocsRelationExtractor
from app.core.rag.indexing.docs.support_layer_builder import DocsSupportLayerBuilder
from app.core.rag.indexing.docs.workflow_extractor import DocsWorkflowExtractor

LOGGER = logging.getLogger(__name__)


class DocsIndexingPipeline:
    def __init__(self) -> None:
        self._filter = DocsFileFilter()
        self._classifier = DocsClassifier()
        self._chunker = MarkdownDocChunker()
        self._builder = DocsDocumentBuilder()
        self._support_builder = DocsSupportLayerBuilder()
        self._frontmatter = DocsFrontmatterParser()
        self._content = DocsContentParser()
        self._facts = DocsFactExtractor()
        self._entities = DocsEntityExtractor()
        self._workflows = DocsWorkflowExtractor()
        self._relations = DocsRelationExtractor()
        self._integrations = DocsIntegrationExtractor()

    def supports(self, path: str) -> bool:
        return self._filter.should_index(path)

    def index_file(self, *, repo_id: str, commit_sha: str | None, path: str, content: str) -> list[RagDocument]:
        source = RagSource(repo_id=repo_id, commit_sha=commit_sha, path=path)
        frontmatter, body = self._frontmatter.split(content)
        if bool(frontmatter.get("__frontmatter_parse_error__")):
            LOGGER.error("docs pipeline partial index: path=%s reason=frontmatter_parse_error", path)
        doc_kind = self._classifier.classify(path)
        sections = self._chunker.chunk(body)
        parsed = self._content.parse(
            frontmatter,
            body,
            fallback_title=self._fallback_title(path),
            sections=sections,
        )
        frontmatter_view = DocsFrontmatterView(parsed.frontmatter)
        docs: list[RagDocument] = []
        docs.append(
            self._builder.build_document_catalog(
                source,
                parsed.frontmatter,
                parsed.summary,
                doc_kind,
                fallback_title=parsed.title,
            )
        )
        for section in sections:
            docs.append(self._builder.build_doc_chunk(source, section, parsed.frontmatter, doc_kind))
        document_id = frontmatter_view.document_id or source.path
        for fact in self._safe_extract(
            extractor_name="fact_extractor",
            path=path,
            run=lambda: self._facts.extract(parsed.frontmatter, sections),
        ):
            docs.append(
                self._support_builder.build_fact(
                    source,
                    subject_id=fact["subject_id"] or document_id,
                    predicate=fact["predicate"],
                    obj=fact["object"],
                    object_ref=fact.get("object_ref"),
                    anchor=fact["anchor"],
                    domain=frontmatter_view.domain,
                    subdomain=frontmatter_view.subdomain,
                )
            )
        for entity in self._safe_extract(
            extractor_name="entity_extractor",
            path=path,
            run=lambda: self._entities.extract(parsed.frontmatter),
        ):
            docs.append(self._builder.build_entity_record(source, parsed.frontmatter, entity))
        for workflow in self._safe_extract(
            extractor_name="workflow_extractor",
            path=path,
            run=lambda: self._workflows.extract(parsed.detail_sections),
        ):
            docs.append(self._support_builder.build_workflow_record(source, parsed.frontmatter, workflow))
        for edge in self._safe_extract(
            extractor_name="relation_extractor",
            path=path,
            run=lambda: self._relations.extract(parsed.frontmatter, source_id=document_id),
        ):
            docs.append(self._support_builder.build_relation_record(source, parsed.frontmatter, edge))
        for integration in self._safe_extract(
            extractor_name="integration_extractor",
            path=path,
            run=lambda: self._integrations.extract(sections, path=path),
        ):
            docs.append(self._support_builder.build_integration_record(source, parsed.frontmatter, integration))
        return docs

    def _fallback_title(self, path: str) -> str:
        tail = path.rsplit("/", 1)[-1]
        stem = tail.rsplit(".", 1)[0]
        return stem.replace("-", " ").replace("_", " ").strip().title()

    def _safe_extract(self, *, extractor_name: str, path: str, run: Callable[[], list]) -> list:
        try:
            return run()
        except Exception as exc:
            LOGGER.warning(
                "docs pipeline extractor warning: path=%s extractor=%s reason=%s",
                path,
                extractor_name,
                exc.__class__.__name__,
            )
            LOGGER.error(
                "docs pipeline partial index: path=%s extractor=%s reason=%s",
                path,
                extractor_name,
                exc.__class__.__name__,
            )
            return []
