# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Licensed under the MIT License

"""
Training Corpus Directory Scaffold

Creates and manages the training/ subdirectory structure under the memories
directory, generates template markdown files, and helps the agent author
well-formed training documents.

Directory layout after scaffold:
  memories/
    training/
      stem/
        math/
        physics/
        ...
      code/
        algorithms/
        ...
      (one directory per domain.subdomain)
    personal/            ← agent RAG memory, never in corpus
    *.md                 ← legacy flat memories, backward-compatible

Each subdomain directory gets a _README.md explaining the schema.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from pathlib import Path

from familiar.skills.training.taxonomy import (
    DIFFICULTY_LEVELS,
    DOMAIN_TAXONOMY,
    INSTRUCTION_TYPES,
    validate_domain,
)

logger = logging.getLogger(__name__)

# Avoid circular import — corpus imports taxonomy, scaffold imports corpus lazily
try:
    from familiar.skills.training.corpus import CURRENT_CONSENT_VERSION
except ImportError:
    CURRENT_CONSENT_VERSION = "1.0"


# ── Path helpers ─────────────────────────────────────────────────────────────


def _get_memories_dir() -> Path:
    try:
        from familiar.core.paths import MEMORIES_DIR

        return MEMORIES_DIR
    except ImportError:
        return Path.home() / ".familiar" / "memories"


def get_training_source_dir(memories_dir: Path | None = None) -> Path:
    """Return memories/training/ — the root of the corpus source tree."""
    return (memories_dir or _get_memories_dir()) / "training"


def get_subdomain_dir(domain: str, subdomain: str, memories_dir: Path | None = None) -> Path:
    return get_training_source_dir(memories_dir) / domain / subdomain


# ── Scaffold ─────────────────────────────────────────────────────────────────


def scaffold_training_directories(memories_dir: Path | None = None) -> dict:
    """
    Create the full training/ directory tree under memories_dir.

    Creates one directory per domain.subdomain pair.
    Writes a _README.md into each subdomain directory explaining the schema.
    Idempotent — safe to run multiple times.

    Returns summary dict.
    """
    root = get_training_source_dir(memories_dir)
    root.mkdir(parents=True, exist_ok=True)

    # personal/ dir for agent-only memories
    personal = (memories_dir or _get_memories_dir()) / "personal"
    personal.mkdir(exist_ok=True)
    _write_personal_readme(personal)

    created_dirs = []
    existing_dirs = []

    for domain, info in DOMAIN_TAXONOMY.items():
        domain_dir = root / domain
        domain_dir.mkdir(exist_ok=True)
        _write_domain_readme(domain_dir, domain, info)

        for subdomain, desc in info["subdomains"].items():
            sub_dir = domain_dir / subdomain
            if sub_dir.exists():
                existing_dirs.append(f"{domain}/{subdomain}")
            else:
                sub_dir.mkdir()
                created_dirs.append(f"{domain}/{subdomain}")
            _write_subdomain_readme(sub_dir, domain, subdomain, desc)

    return {
        "root": str(root),
        "created": len(created_dirs),
        "existing": len(existing_dirs),
        "total_dirs": len(created_dirs) + len(existing_dirs),
        "new_dirs": created_dirs,
    }


def _write_domain_readme(domain_dir: Path, domain: str, info: dict) -> None:
    readme = domain_dir / "_README.md"
    subdomains = "\n".join(f"- `{sub}/` — {desc}" for sub, desc in info["subdomains"].items())
    content = f"""# {info["label"]}

DDC reference: {info["ddc"]}

{info["description"]}

## Subdomains

{subdomains}

## Usage

Place training documents in the appropriate subdomain directory.
Each file should include the [training frontmatter schema](../_README.md).
"""
    readme.write_text(content, encoding="utf-8")


def _write_subdomain_readme(sub_dir: Path, domain: str, subdomain: str, desc: str) -> None:
    readme = sub_dir / "_README.md"
    example_type = list(INSTRUCTION_TYPES.keys())[0]
    content = f"""# {domain}.{subdomain}

{desc}

## Training document schema

```yaml
---
topic: "Short descriptive topic name"
domain: "{domain}"
subdomain: "{subdomain}"
instruction_types:
  - "{example_type}"
difficulty: "beginner"          # beginner | intermediate | expert
audience: "general"             # general | professional | specialist | student
language: "en"
training_eligible: true
consent_version: "{CURRENT_CONSENT_VERSION}"
pairs:
  - instruction: "Clear, specific question or task."
    response: "Complete, accurate, expert-level answer."
    instruction_type: "{example_type}"
  - instruction: "A second question on the same topic with different framing."
    response: "Another complete answer."
    instruction_type: "explain"
---

# Supporting notes (agent context — not exported to corpus)

Additional background, references, or nuance the agent may use as RAG context.
This section is not directly used in training pairs.
```

## Instruction types available

{chr(10).join(f"- `{k}` — {v}" for k, v in INSTRUCTION_TYPES.items())}
"""
    readme.write_text(content, encoding="utf-8")


def _write_personal_readme(personal_dir: Path) -> None:
    readme = personal_dir / "_README.md"
    content = """# Personal Memory

This directory holds agent-specific memory that should never enter the training corpus:
episodic context, user preferences, session notes, personal schedules.

Files here are used for RAG retrieval only.
Set `training_eligible: false` (or omit the field) in any file placed here.
"""
    readme.write_text(content, encoding="utf-8")


# ── Template generation ───────────────────────────────────────────────────────


def generate_training_document(
    domain: str,
    subdomain: str,
    topic: str,
    instruction_types: list[str] | None = None,
    difficulty: str = "beginner",
    audience: str = "general",
    pairs: list[dict] | None = None,
    memories_dir: Path | None = None,
) -> tuple[Path, str]:
    """
    Generate a new training document in the correct subdomain directory.

    Creates the file with full frontmatter and stub content.
    Returns (file_path, content).
    Raises ValueError if domain/subdomain not in taxonomy.
    """
    valid, msg = validate_domain(domain, subdomain)
    if not valid:
        raise ValueError(msg)

    if difficulty not in DIFFICULTY_LEVELS:
        difficulty = "beginner"

    if instruction_types is None:
        instruction_types = ["how_to"]
    instruction_types = [t for t in instruction_types if t in INSTRUCTION_TYPES]
    if not instruction_types:
        instruction_types = ["how_to"]

    now = datetime.now(timezone.utc).isoformat()

    # Build pairs section
    if not pairs:
        pairs = [
            {
                "instruction": f"(Write your instruction here — a clear, specific question or task about {topic}.)",
                "response": "(Write the complete, expert-level response here.)",
                "instruction_type": instruction_types[0],
            }
        ]

    pairs_yaml = _format_pairs_yaml(pairs)

    content = f"""---
topic: "{topic}"
domain: "{domain}"
subdomain: "{subdomain}"
instruction_types:
{chr(10).join(f'  - "{t}"' for t in instruction_types)}
difficulty: "{difficulty}"
audience: "{audience}"
language: "en"
training_eligible: true
consent_version: "{CURRENT_CONSENT_VERSION}"
revision: 1
created_at: "{now}"
updated_at: "{now}"
pairs:
{pairs_yaml}
---

# {topic}

*(Supporting notes for agent RAG context — not exported to the training corpus.
Edit the `pairs:` section above to author the actual training examples.)*

## Notes

"""

    # Write to correct location
    sub_dir = get_subdomain_dir(domain, subdomain, memories_dir)
    sub_dir.mkdir(parents=True, exist_ok=True)

    slug = topic.lower().replace(" ", "-").replace("/", "-")[:60]
    slug = "".join(c for c in slug if c.isalnum() or c == "-")
    file_path = sub_dir / f"{slug}.md"

    # Don't overwrite existing
    counter = 1
    while file_path.exists():
        file_path = sub_dir / f"{slug}-{counter}.md"
        counter += 1

    file_path.write_text(content, encoding="utf-8")
    logger.info("Created training document: %s", file_path)
    return file_path, content


def _format_pairs_yaml(pairs: list[dict]) -> str:
    """Render a list of pair dicts as indented YAML for the frontmatter."""
    lines = []
    for p in pairs:
        instruction = str(p.get("instruction", "")).replace('"', '\\"')
        response = str(p.get("response", "")).replace('"', '\\"')
        itype = p.get("instruction_type", "how_to")
        lines.append(f'  - instruction: "{instruction}"')
        # Multi-line response: use block scalar if contains newlines
        if "\n" in response:
            lines.append("    response: |")
            for line in response.splitlines():
                lines.append(f"      {line}")
        else:
            lines.append(f'    response: "{response}"')
        lines.append(f'    instruction_type: "{itype}"')
    return "\n".join(lines)


# ── Discovery ─────────────────────────────────────────────────────────────────


def list_training_documents(
    memories_dir: Path | None = None,
    domain: str = "",
    subdomain: str = "",
) -> list[dict]:
    """
    List all training documents under training/ with their classification.

    Returns list of dicts: domain, subdomain, filename, topic,
    pair_count, training_eligible, quality.
    """
    root = get_training_source_dir(memories_dir)
    if not root.exists():
        return []

    from familiar.skills.training.corpus import parse_frontmatter

    results = []
    pattern = f"{domain}/{subdomain}/**/*.md" if domain or subdomain else "**/*.md"

    for md_path in sorted(root.glob(pattern)):
        if md_path.name.startswith("_"):
            continue  # skip READMEs
        try:
            text = md_path.read_text(encoding="utf-8")
            fm, body = parse_frontmatter(text)

            # Count explicit pairs
            pair_count = 0
            try:
                import yaml

                raw = yaml.safe_load(text.split("---", 2)[1] if "---" in text else "")
                if isinstance(raw, dict):
                    pair_count = len(raw.get("pairs") or [])
            except Exception:
                pass

            # Derive domain/subdomain from path if not in frontmatter
            rel = md_path.relative_to(root)
            path_parts = rel.parts
            file_domain = path_parts[0] if len(path_parts) > 1 else fm.domain or "general"
            file_subdomain = path_parts[1] if len(path_parts) > 2 else fm.domain or ""

            results.append(
                {
                    "path": str(md_path),
                    "filename": md_path.name,
                    "domain": fm.domain or file_domain,
                    "subdomain": getattr(fm, "subdomain", file_subdomain),
                    "topic": fm.topic or md_path.stem,
                    "pair_count": pair_count,
                    "training_eligible": fm.training_eligible,
                    "consent_version": fm.consent_version,
                    "quality": fm.quality,
                    "difficulty": getattr(fm, "difficulty", ""),
                }
            )
        except Exception as e:
            logger.debug("Could not parse %s: %s", md_path.name, e)

    return results


def get_domain_stats(memories_dir: Path | None = None) -> dict:
    """
    Return pair counts by domain/subdomain for corpus balance reporting.

    Returns dict: {domain: {subdomain: {eligible: int, pairs: int, files: int}}}
    """
    docs = list_training_documents(memories_dir)
    stats: dict = {}
    for doc in docs:
        d = doc["domain"]
        s = doc.get("subdomain", "")
        stats.setdefault(d, {}).setdefault(s, {"eligible": 0, "pairs": 0, "files": 0})
        stats[d][s]["files"] += 1
        stats[d][s]["pairs"] += doc.get("pair_count", 0)
        if doc.get("training_eligible"):
            stats[d][s]["eligible"] += 1
    return stats
