"""Indexing pipeline for docsctx."""

from __future__ import annotations

import hashlib
from collections import deque
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse

from docsctx.db.repository import DocumentRecord, DocsRepository
from docsctx.loader import LoadedDocument, fetch_url, load_path

SUPPORTED_EXTENSIONS = {".txt", ".text", ".md", ".markdown", ".html", ".htm", ".pdf", ".docx", ".doc"}
SKIP_DIRS = {".git", ".vscode", "node_modules", "vendor", "__pycache__", "target", "build", "dist"}


def estimate_tokens(text: str) -> int:
    """Estimate token count using the same heuristic as the Go implementation (rune_count / 4)."""
    return max(1, len(text) // 4)


@dataclass(slots=True)
class IndexSummary:
    indexed_documents: int = 0
    skipped_documents: int = 0


class DocsIndexer:
    def __init__(self, repository: DocsRepository, chunk_size: int = 1200, chunk_overlap: int = 150) -> None:
        self.repository = repository
        self.chunk_size = max(200, chunk_size)
        self.chunk_overlap = max(0, min(chunk_overlap, self.chunk_size - 50))
        self.repository.create_schema()

    def index_path(self, target: str | Path) -> IndexSummary:
        path = Path(target)
        summary = IndexSummary()
        paths = [path] if path.is_file() else list(self._iter_paths(path))
        for candidate in paths:
            loaded = load_path(candidate)
            if loaded is None or not loaded.content:
                summary.skipped_documents += 1
                continue
            self._store_document(loaded)
            summary.indexed_documents += 1
        return summary

    def index_url(
        self,
        url: str,
        max_pages: int = 100,
        max_depth: int = 2,
        allow_external: bool = False,
        same_path_prefix: bool = True,
        timeout: float = 10.0,
    ) -> IndexSummary:
        parsed_root = urlparse(url)
        allowed_host = parsed_root.netloc
        path_prefix = parsed_root.path.rstrip("/")
        queue: deque[tuple[str, int]] = deque([(url, 0)])
        seen: set[str] = set()
        summary = IndexSummary()

        while queue and (max_pages <= 0 or summary.indexed_documents < max_pages):
            current, depth = queue.popleft()
            if current in seen:
                continue
            seen.add(current)

            try:
                loaded, links = fetch_url(current, timeout=timeout)
            except Exception:
                summary.skipped_documents += 1
                continue

            if loaded.content:
                self._store_document(loaded)
                summary.indexed_documents += 1
            else:
                summary.skipped_documents += 1

            if max_depth > 0 and depth >= max_depth:
                continue

            for link in links:
                parsed = urlparse(link)
                if parsed.scheme not in {"http", "https"}:
                    continue
                if not allow_external and parsed.netloc != allowed_host:
                    continue
                if same_path_prefix and path_prefix and not parsed.path.startswith(path_prefix):
                    continue
                if link not in seen:
                    queue.append((link, depth + 1))

        return summary

    def _iter_paths(self, root: Path):
        for path in root.rglob("*"):
            if path.is_dir():
                continue
            if any(part in SKIP_DIRS for part in path.parts):
                continue
            if path.suffix.lower() not in SUPPORTED_EXTENSIONS:
                continue
            yield path

    def _store_document(self, loaded: LoadedDocument) -> None:
        normalized_content = loaded.content.strip()
        chunks = self._chunk_text(normalized_content)
        checksum = hashlib.sha256(normalized_content.encode("utf-8")).hexdigest()
        self.repository.upsert_document(
            DocumentRecord(
                source_uri=loaded.source_uri,
                title=loaded.title.strip() or loaded.source_uri,
                doc_type=loaded.doc_type,
                checksum=checksum,
                content=normalized_content,
                chunks=chunks,
            )
        )

    def _chunk_text(self, content: str) -> list[str]:
        if len(content) <= self.chunk_size:
            return [content]

        chunks: list[str] = []
        start = 0
        while start < len(content):
            end = min(len(content), start + self.chunk_size)
            chunks.append(content[start:end].strip())
            if end >= len(content):
                break
            start = max(end - self.chunk_overlap, start + 1)
        return [chunk for chunk in chunks if chunk]
