"""arifos_wiki_tools.indexer — Repo ingestion: scan, chunk, extract, write index."""

from __future__ import annotations

import hashlib
import json
import time
from collections import Counter
from pathlib import Path
from typing import Any

from arifos_wiki_tools.ignore import load_gitignore_patterns, should_index
from arifos_wiki_tools.models import ChunkRecord, FileRecord, to_jsonl
from arifos_wiki_tools.symbols import detect_language, extract_symbols

# Max lines per chunk for indexing
_MAX_CHUNK_LINES = 80


def sha256_text(text: str) -> str:
    """SHA-256 hex digest of a string."""
    return hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()


def safe_read(path: Path) -> str | None:
    """
    Read a file as UTF-8 text, or None if it appears binary.

    Rejects files containing null bytes in the first 4 KB.
    """
    try:
        raw = path.read_bytes()
    except OSError:
        return None
    if b"\x00" in raw[:4096]:
        return None
    try:
        return raw.decode("utf-8")
    except UnicodeDecodeError:
        return raw.decode("utf-8", errors="replace")


def chunk_text(
    rel_path: str,
    language: str,
    text: str,
    symbols: list[dict],
    max_lines: int = _MAX_CHUNK_LINES,
) -> list[ChunkRecord]:
    """
    Split source text into overlapping line windows.

    Symbols are assigned to the chunk they appear in.
    """
    lines = text.splitlines()
    if not lines:
        return []

    chunks: list[ChunkRecord] = []
    for start in range(0, len(lines), max_lines):
        end = min(start + max_lines, len(lines))
        body = "\n".join(lines[start:end])
        start_line = start + 1  # 1-indexed
        end_line = end

        chunk_symbols = [
            s
            for s in symbols
            if isinstance(s.get("line"), int) and start_line <= int(s["line"]) <= end_line
        ]

        chunk_hash = sha256_text(f"{rel_path}:{start_line}:{end_line}:{body}")
        chunks.append(
            ChunkRecord(
                chunk_id=f"{rel_path}:{start_line}-{end_line}:{chunk_hash[:12]}",
                rel_path=rel_path,
                language=language,
                start_line=start_line,
                end_line=end_line,
                text=body,
                symbols=chunk_symbols,
                sha256=chunk_hash,
            )
        )
    return chunks


def write_generated_wiki(
    repo: Path,
    scope_name: str,
    files: list[FileRecord],
    chunks: list[ChunkRecord],
    manifest: dict,
) -> Path:
    """
    Write a minimal generated wiki to <repo>/wiki/generated/.

    Creates overview.md, structure.md, and index.md.
    """
    wiki_dir = repo / "wiki" / "generated"
    wiki_dir.mkdir(parents=True, exist_ok=True)

    languages = manifest.get("languages", {})
    key_files = sorted(files, key=lambda f: (len(f.rel_path.split("/")), f.rel_path))[:50]
    symbol_files = [f for f in files if f.symbols]

    overview_lines = [
        f"# {scope_name} Generated Wiki — Overview",
        "",
        "*This page was auto-generated by arifos_wiki_tools forge v0.1.*",
        "*It is evidence, not authority. Judgment remains with the human.*",
        "",
        "## Index Summary",
        "",
        f"- Files indexed: {len(files)}",
        f"- Chunks indexed: {len(chunks)}",
        f"- Languages: {', '.join(f'{k}={v}' for k, v in languages.items()) or 'none'}",
        "",
        "## Key Files",
        "",
    ]
    for f in key_files:
        overview_lines.append(f"- `{f.rel_path}` — {f.language}, {f.line_count} lines")

    overview_lines.extend(["", "---", f"*Generated at unix={manifest['created_at_unix']}*"])
    (wiki_dir / "overview.md").write_text("\n".join(overview_lines) + "\n", encoding="utf-8")

    structure_lines = [
        f"# {scope_name} Generated Wiki — Repository Structure",
        "",
        "## Files with detected symbols",
        "",
    ]
    for f in symbol_files[:200]:
        structure_lines.append(f"### `{f.rel_path}`")
        for s in f.symbols[:30]:
            line = s.get("line", "?")
            doc = f" — {s.get('doc')}" if s.get("doc") else ""
            structure_lines.append(f"- `{s.get('kind')}` **{s.get('name')}** at line {line}{doc}")
        structure_lines.append("")

    (wiki_dir / "structure.md").write_text("\n".join(structure_lines) + "\n", encoding="utf-8")

    index_lines = [
        f"# {scope_name} Generated Wiki",
        "",
        "- [Overview](overview.md)",
        "- [Repository Structure](structure.md)",
        "",
        "---",
        "*Evidence is not authority. Review cited line ranges before accepting conclusions.*",
    ]
    (wiki_dir / "index.md").write_text("\n".join(index_lines) + "\n", encoding="utf-8")

    return wiki_dir


def ingest_repo(
    repo_path: str | Path,
    scope_name: str = "arifOS",
    include_globs: list[str] | None = None,
    exclude_globs: list[str] | None = None,
    write_wiki: bool = True,
) -> dict:
    """
    Walk a repository and produce a wiki index.

    Output artifacts (written to ``<repo>/.arifos/``):
        wiki_index.jsonl   — all chunks with symbols and line ranges
        wiki_files.jsonl   — file-level inventory
        wiki_manifest.json — metadata + paths

    If ``write_wiki=True``, also writes ``wiki/generated/`` markdown pages.

    Returns a dict with:
        scope_name, repo_path, created_at_unix, files_indexed,
        chunks_indexed, languages, index_path, files_path,
        generated_wiki_path (if write_wiki=True)
    """
    repo = Path(repo_path).expanduser().resolve()
    if not repo.exists() or not repo.is_dir():
        raise ValueError(f"Repository path does not exist or is not a directory: {repo}")

    files: list[FileRecord] = []
    chunks: list[ChunkRecord] = []
    language_counts: Counter[str] = Counter()

    gitignore_patterns = load_gitignore_patterns(repo)

    for path in sorted(repo.rglob("*")):
        if not path.is_file():
            continue
        if not should_index(
            path,
            repo,
            include_globs=include_globs,
            exclude_globs=exclude_globs,
            gitignore_patterns=gitignore_patterns,
        ):
            continue
        text = safe_read(path)
        if text is None:
            continue

        rel = path.relative_to(repo).as_posix()
        lang = detect_language(rel)
        syms = extract_symbols(rel, text)
        line_count = len(text.splitlines())
        digest = sha256_text(text)
        language_counts[lang] += 1

        files.append(
            FileRecord(
                path=str(path),
                rel_path=rel,
                language=lang,
                size_bytes=path.stat().st_size,
                sha256=digest,
                line_count=line_count,
                symbols=syms,
            )
        )
        chunks.extend(chunk_text(rel, lang, text, syms))

    out_dir = repo / ".arifos"
    out_dir.mkdir(exist_ok=True)
    index_path = out_dir / "wiki_index.jsonl"
    files_path = out_dir / "wiki_files.jsonl"
    manifest_path = out_dir / "wiki_manifest.json"

    to_jsonl(chunks, index_path)
    to_jsonl(files, files_path)

    manifest: dict[str, Any] = {
        "scope_name": scope_name,
        "repo_path": str(repo),
        "created_at_unix": int(time.time()),
        "files_indexed": len(files),
        "chunks_indexed": len(chunks),
        "languages": dict(language_counts.most_common()),
        "index_path": str(index_path),
        "files_path": str(files_path),
    }
    manifest_path.write_text(
        json.dumps(manifest, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )

    generated_wiki_path: Path | None = None
    if write_wiki:
        generated_wiki_path = write_generated_wiki(repo, scope_name, files, chunks, manifest)
        manifest["generated_wiki_path"] = str(generated_wiki_path)
        manifest_path.write_text(
            json.dumps(manifest, indent=2, ensure_ascii=False),
            encoding="utf-8",
        )

    return manifest
