#!/usr/bin/env python3
"""Markdown Exporter CLI — export approved content as structured files.

Usage:
    python exporter.py project --output ~/reference-library/exports/ [--min-score 0.80]
    python exporter.py finetuning --output ~/reference-library/exports/training.jsonl
    python exporter.py index --output ~/reference-library/exports/INDEX.md
    python exporter.py crossrefs --input ~/reference-library/exports/
    python exporter.py verify --path ~/reference-library/exports/
    python exporter.py log --name "Jan 2025 Export" --type project_files --docs 40
"""

import json
import re
import sys
from collections import defaultdict
from datetime import datetime
from pathlib import Path

import click
from rich.console import Console
from rich.table import Table

from refcurator.db import db_session
from refcurator.utils import count_tokens, slugify

console = Console()


@click.group()
def cli():
    """Markdown Exporter — export approved references."""
    pass


@cli.command()
@click.option("--output", required=True, type=click.Path(), help="Output directory path")
@click.option("--min-score", default=0.80, type=float, help="Minimum quality score")
@click.option("--structure", default="nested_by_topic",
              type=click.Choice(["nested_by_topic", "flat"]))
@click.option("--include-metadata", is_flag=True, default=True, help="Include source metadata")
def project(output, min_score, structure, include_metadata):
    """Export approved content as markdown project files."""
    out_dir = Path(output).expanduser()
    out_dir.mkdir(parents=True, exist_ok=True)

    with db_session() as db:
        rows = db.fetch_all(
            """SELECT d.doc_id, d.title, d.url,
                      dc.structured_content, dc.summary, dc.key_concepts,
                      dc.token_count_distilled,
                      rl.quality_score,
                      s.credibility_tier, s.vendor, s.source_name
               FROM documents d
               JOIN distilled_content dc ON d.doc_id = dc.doc_id
               JOIN review_logs rl ON dc.distill_id = rl.distill_id
               JOIN sources s ON d.source_id = s.source_id
               WHERE dc.review_status = %s
                 AND rl.decision = %s
                 AND rl.review_id = (
                     SELECT MAX(rl2.review_id)
                     FROM review_logs rl2
                     WHERE rl2.distill_id = dc.distill_id
                 )
               ORDER BY rl.quality_score DESC""",
            ("approved", "approve"),
        )

    # Filter by min score
    rows = [r for r in rows if (r.get("quality_score") or 0) >= min_score]

    if not rows:
        console.print("[yellow]No approved documents found above minimum score.[/yellow]")
        return

    # Get topic mappings
    with db_session() as db:
        topic_rows = db.fetch_all(
            """SELECT dt.doc_id, t.topic_name, t.topic_slug
               FROM document_topics dt
               JOIN topics t ON dt.topic_id = t.topic_id"""
        )

    doc_topics = defaultdict(list)
    for tr in topic_rows:
        doc_topics[tr["doc_id"]].append(tr)

    exported = 0
    total_tokens = 0

    if structure == "nested_by_topic":
        exported, total_tokens = _export_nested(rows, doc_topics, out_dir, include_metadata)
    else:
        exported, total_tokens = _export_flat(rows, doc_topics, out_dir, include_metadata)

    console.print(
        f"[green]Exported {exported} documents ({total_tokens:,} tokens) to {out_dir}[/green]"
    )


@cli.command()
@click.option("--output", required=True, type=click.Path(), help="Output JSONL file path")
@click.option("--min-score", default=0.80, type=float, help="Minimum quality score")
@click.option("--max-tokens", default=4096, type=int, help="Max tokens per sample")
@click.option("--system-prompt", default="You are an expert on AI and prompt engineering.",
              help="System prompt for training samples")
def finetuning(output, min_score, max_tokens, system_prompt):
    """Export approved content as JSONL fine-tuning dataset."""
    out_path = Path(output).expanduser()
    out_path.parent.mkdir(parents=True, exist_ok=True)

    with db_session() as db:
        rows = db.fetch_all(
            """SELECT d.doc_id, d.title, d.url,
                      dc.structured_content, dc.summary,
                      dc.token_count_distilled,
                      rl.quality_score
               FROM documents d
               JOIN distilled_content dc ON d.doc_id = dc.doc_id
               JOIN review_logs rl ON dc.distill_id = rl.distill_id
               WHERE dc.review_status = %s
                 AND rl.decision = %s
                 AND rl.review_id = (
                     SELECT MAX(rl2.review_id)
                     FROM review_logs rl2
                     WHERE rl2.distill_id = dc.distill_id
                 )
               ORDER BY rl.quality_score DESC""",
            ("approved", "approve"),
        )

    rows = [r for r in rows if (r.get("quality_score") or 0) >= min_score]

    count = 0
    with open(out_path, "w") as f:
        for row in rows:
            content = row.get("structured_content", "")
            if count_tokens(content) > max_tokens:
                content = content[:max_tokens * 4]  # Approximate truncation

            sample = {
                "messages": [
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": f"Explain {row.get('title', 'this topic')}"},
                    {"role": "assistant", "content": content},
                ],
                "metadata": {
                    "source": row.get("url"),
                    "quality_score": float(row["quality_score"]) if row.get("quality_score") else None,
                    "doc_id": row.get("doc_id"),
                },
            }
            f.write(json.dumps(sample, ensure_ascii=False) + "\n")
            count += 1

    console.print(f"[green]Exported {count} samples to {out_path}[/green]")


@cli.command()
@click.option("--output", required=True, type=click.Path(), help="Output INDEX.md path")
@click.option("--exports-dir", type=click.Path(exists=True),
              help="Exports directory to scan (defaults to parent of output)")
def index(output, exports_dir):
    """Generate INDEX.md with table of contents."""
    out_path = Path(output).expanduser()
    scan_dir = Path(exports_dir).expanduser() if exports_dir else out_path.parent

    lines = [
        "# Reference Library Index",
        "",
        f"*Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}*",
        "",
    ]

    # Scan for topic directories
    topic_dirs = sorted([d for d in scan_dir.iterdir() if d.is_dir() and not d.name.startswith("_")])

    if topic_dirs:
        lines.append("## Topics\n")
        for topic_dir in topic_dirs:
            md_files = sorted(topic_dir.glob("*.md"))
            if md_files:
                topic_name = topic_dir.name.replace("-", " ").title()
                lines.append(f"### {topic_name}\n")
                for md_file in md_files:
                    if md_file.name.startswith("_"):
                        continue
                    title = _extract_md_title(md_file)
                    rel_path = md_file.relative_to(scan_dir)
                    lines.append(f"- [{title}]({rel_path})")
                lines.append("")

    # Scan for flat files
    flat_files = sorted([f for f in scan_dir.glob("*.md")
                         if f.name not in ("INDEX.md", "_index.md")])
    if flat_files:
        lines.append("## Documents\n")
        for md_file in flat_files:
            title = _extract_md_title(md_file)
            lines.append(f"- [{title}]({md_file.name})")
        lines.append("")

    out_path.write_text("\n".join(lines))
    console.print(f"[green]Generated INDEX.md at {out_path}[/green]")


@cli.command()
@click.option("--input", "input_dir", required=True, type=click.Path(exists=True),
              help="Exports directory to add cross-references to")
def crossrefs(input_dir):
    """Add cross-reference links between related documents."""
    scan_dir = Path(input_dir).expanduser()
    md_files = list(scan_dir.rglob("*.md"))

    if not md_files:
        console.print("[yellow]No markdown files found.[/yellow]")
        return

    # Build concept index: concept → list of (file, title)
    concept_index: dict[str, list[tuple[Path, str]]] = defaultdict(list)
    file_concepts: dict[Path, set[str]] = {}

    for md_file in md_files:
        if md_file.name in ("INDEX.md", "_index.md"):
            continue
        content = md_file.read_text(errors="replace")
        title = _extract_md_title(md_file)
        concepts = _extract_concepts(content)
        file_concepts[md_file] = concepts
        for concept in concepts:
            concept_index[concept].append((md_file, title))

    # Add cross-references
    modified = 0
    for md_file in md_files:
        if md_file.name in ("INDEX.md", "_index.md"):
            continue
        my_concepts = file_concepts.get(md_file, set())
        related: dict[str, str] = {}  # title → relative path

        for concept in my_concepts:
            for other_file, other_title in concept_index.get(concept, []):
                if other_file != md_file and other_title not in related:
                    try:
                        rel = other_file.relative_to(scan_dir)
                    except ValueError:
                        rel = other_file
                    related[other_title] = str(rel)

        if related and len(related) <= 10:
            content = md_file.read_text(errors="replace")
            # Remove existing Related section if present
            content = re.sub(r"\n## Related\n.*$", "", content, flags=re.DOTALL)
            content = content.rstrip() + "\n\n## Related\n\n"
            for title, path in sorted(related.items())[:5]:
                content += f"- [{title}]({path})\n"
            md_file.write_text(content)
            modified += 1

    console.print(f"[green]Added cross-references to {modified} files[/green]")


@cli.command()
@click.option("--path", required=True, type=click.Path(exists=True), help="Exports directory to verify")
def verify(path):
    """Verify export integrity."""
    scan_dir = Path(path).expanduser()
    md_files = list(scan_dir.rglob("*.md"))

    issues = []
    total_tokens = 0
    total_files = len(md_files)

    for md_file in md_files:
        content = md_file.read_text(errors="replace")
        tokens = count_tokens(content)
        total_tokens += tokens

        # Check for empty files
        if len(content.strip()) < 10:
            issues.append(f"Empty or near-empty: {md_file.relative_to(scan_dir)}")

        # Check for broken internal links
        for match in re.finditer(r"\[([^\]]+)\]\(([^)]+)\)", content):
            link_path = match.group(2)
            if link_path.startswith("http"):
                continue
            resolved = (md_file.parent / link_path).resolve()
            if not resolved.exists():
                issues.append(
                    f"Broken link in {md_file.relative_to(scan_dir)}: {link_path}"
                )

    # Check INDEX.md exists
    if not (scan_dir / "INDEX.md").is_file():
        issues.append("Missing INDEX.md")

    # Report
    console.print(f"\n[bold]Export Verification: {scan_dir}[/bold]")
    console.print(f"  Files: {total_files}")
    console.print(f"  Total tokens: {total_tokens:,}")

    if issues:
        console.print(f"\n[red]Issues ({len(issues)}):[/red]")
        for issue in issues[:20]:
            console.print(f"  - {issue}")
    else:
        console.print(f"\n[green]All checks passed.[/green]")

    click.echo(json.dumps({
        "files": total_files,
        "total_tokens": total_tokens,
        "issues": len(issues),
        "issue_details": issues[:20],
    }))


@cli.command()
@click.option("--name", required=True, help="Export job name")
@click.option("--type", "export_type", required=True,
              type=click.Choice(["project_files", "fine_tuning", "knowledge_base"]))
@click.option("--docs", required=True, type=int, help="Number of documents exported")
@click.option("--path", type=click.Path(), help="Export output path")
@click.option("--tokens", type=int, help="Total tokens exported")
def log(name, export_type, docs, path, tokens):
    """Log an export job to the database."""
    with db_session() as db:
        export_id = db.insert_returning_id(
            """INSERT INTO export_jobs
               (export_name, export_type, output_path, total_documents, total_tokens,
                status, started_at, completed_at)
               VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
            (name, export_type, path, docs, tokens,
             "completed", datetime.now().isoformat(), datetime.now().isoformat()),
        )

    console.print(f"[green]Logged export:[/green] export_id={export_id} — {name}")


# --- Helpers ---

def _export_nested(rows, doc_topics, out_dir, include_metadata):
    """Export documents in nested topic directory structure."""
    exported = 0
    total_tokens = 0
    topic_docs = defaultdict(list)

    for row in rows:
        topics = doc_topics.get(row["doc_id"], [])
        if topics:
            for t in topics:
                topic_docs[t["topic_slug"]].append(row)
        else:
            topic_docs["uncategorized"].append(row)

    for topic_slug, docs in sorted(topic_docs.items()):
        topic_dir = out_dir / topic_slug
        topic_dir.mkdir(parents=True, exist_ok=True)

        # Topic index
        topic_name = topic_slug.replace("-", " ").title()
        index_lines = [f"# {topic_name}\n"]

        for i, doc in enumerate(docs):
            filename = f"{i:02d}-{slugify(doc.get('title', 'untitled'))}.md"
            content = _format_document(doc, include_metadata)
            (topic_dir / filename).write_text(content)
            index_lines.append(f"- [{doc.get('title', 'Untitled')}]({filename})")
            exported += 1
            total_tokens += doc.get("token_count_distilled") or count_tokens(content)

        (topic_dir / "_index.md").write_text("\n".join(index_lines) + "\n")

    return exported, total_tokens


def _export_flat(rows, doc_topics, out_dir, include_metadata):
    """Export documents as flat files."""
    exported = 0
    total_tokens = 0

    for row in rows:
        topics = doc_topics.get(row["doc_id"], [])
        topic_prefix = topics[0]["topic_slug"] + "-" if topics else ""
        filename = f"{topic_prefix}{slugify(row.get('title', 'untitled'))}.md"
        content = _format_document(row, include_metadata)
        (out_dir / filename).write_text(content)
        exported += 1
        total_tokens += row.get("token_count_distilled") or count_tokens(content)

    return exported, total_tokens


def _format_document(row: dict, include_metadata: bool) -> str:
    """Format a document for export."""
    lines = []

    if include_metadata:
        lines.extend([
            f"# {row.get('title', 'Untitled')}",
            "",
            f"**Source:** {row.get('url', 'N/A')}",
            f"**Tier:** {row.get('credibility_tier', 'N/A')} | "
            f"**Vendor:** {row.get('vendor', 'N/A')} | "
            f"**Score:** {float(row['quality_score']):.2f}" if row.get("quality_score") else "",
            "",
            "---",
            "",
        ])

    content = row.get("structured_content", "")
    if content:
        lines.append(content)

    return "\n".join(lines)


def _extract_md_title(md_file: Path) -> str:
    """Extract title from a markdown file."""
    try:
        for line in md_file.read_text(errors="replace").split("\n")[:10]:
            if line.startswith("# ") and not line.startswith("##"):
                return line[2:].strip()
    except Exception:
        pass
    return md_file.stem.replace("-", " ").title()


def _extract_concepts(content: str) -> set[str]:
    """Extract key concepts from markdown content for cross-referencing."""
    concepts = set()

    # Extract from ## Key Concepts section
    m = re.search(r"## Key Concepts?\n(.*?)(?=\n##|\Z)", content, re.DOTALL)
    if m:
        for line in m.group(1).split("\n"):
            line = line.strip().lstrip("- *")
            if line and ":" in line:
                concept = line.split(":")[0].strip("*").strip()
                if 2 < len(concept) < 50:
                    concepts.add(concept.lower())

    # Extract bold terms
    for m in re.finditer(r"\*\*([^*]{3,40})\*\*", content):
        concepts.add(m.group(1).lower())

    return concepts


if __name__ == "__main__":
    cli()
