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

"""Tests for training corpus taxonomy and directory scaffold."""

import tempfile
from pathlib import Path

import pytest

from familiar.skills.training.taxonomy import (
    AUDIENCE_TYPES,
    DIFFICULTY_LEVELS,
    DOMAIN_TAXONOMY,
    INSTRUCTION_TYPES,
    PAIR_TYPES,
    list_taxonomy,
    suggest_domain,
    validate_domain,
    validate_instruction_type,
)

# ── Taxonomy structure ────────────────────────────────────────────────────────


class TestDomainTaxonomy:
    def test_ten_top_level_domains(self):
        assert len(DOMAIN_TAXONOMY) == 10

    def test_all_domains_have_required_keys(self):
        for domain, info in DOMAIN_TAXONOMY.items():
            assert "label" in info, f"{domain} missing label"
            assert "ddc" in info, f"{domain} missing ddc"
            assert "description" in info, f"{domain} missing description"
            assert "subdomains" in info, f"{domain} missing subdomains"

    def test_each_domain_has_subdomains(self):
        for domain, info in DOMAIN_TAXONOMY.items():
            assert len(info["subdomains"]) >= 3, f"{domain} has < 3 subdomains"

    def test_known_domains_present(self):
        for expected in ("stem", "code", "reasoning", "humanities", "practical", "general"):
            assert expected in DOMAIN_TAXONOMY

    def test_fifteen_instruction_types(self):
        assert len(INSTRUCTION_TYPES) == 15

    def test_difficulty_levels(self):
        assert DIFFICULTY_LEVELS == ("beginner", "intermediate", "expert")

    def test_audience_types(self):
        assert "general" in AUDIENCE_TYPES
        assert "professional" in AUDIENCE_TYPES

    def test_pair_types(self):
        assert "instruction" in PAIR_TYPES


# ── Validation ────────────────────────────────────────────────────────────────


class TestValidateDomain:
    def test_valid_domain_only(self):
        valid, msg = validate_domain("practical")
        assert valid is True
        assert msg == "ok"

    def test_valid_domain_subdomain(self):
        valid, msg = validate_domain("practical", "cooking")
        assert valid is True

    def test_invalid_domain(self):
        valid, msg = validate_domain("fantasy")
        assert valid is False
        assert "fantasy" in msg

    def test_invalid_subdomain(self):
        valid, msg = validate_domain("practical", "teleportation")
        assert valid is False
        assert "teleportation" in msg

    def test_all_taxonomy_entries_pass_validation(self):
        for domain, info in DOMAIN_TAXONOMY.items():
            for subdomain in info["subdomains"]:
                valid, msg = validate_domain(domain, subdomain)
                assert valid is True, f"Failed: {domain}.{subdomain} — {msg}"

    def test_empty_subdomain_is_ok(self):
        valid, _ = validate_domain("code", "")
        assert valid is True


class TestValidateInstructionType:
    def test_known_types_valid(self):
        for itype in ("how_to", "explain", "compare", "code", "qa", "plan"):
            assert validate_instruction_type(itype) is True

    def test_unknown_type_invalid(self):
        assert validate_instruction_type("magic") is False


# ── Utilities ─────────────────────────────────────────────────────────────────


class TestListTaxonomy:
    def test_returns_string(self):
        result = list_taxonomy()
        assert isinstance(result, str)

    def test_contains_all_domains(self):
        result = list_taxonomy()
        for domain in DOMAIN_TAXONOMY:
            assert domain in result

    def test_contains_instruction_types(self):
        result = list_taxonomy()
        assert "how_to" in result
        assert "explain" in result


class TestSuggestDomain:
    def test_food_suggests_practical_cooking(self):
        # "food" and "recipes" both appear in practical.cooking description
        result = suggest_domain("food recipes")
        assert result is not None
        assert "practical" in result

    def test_python_suggests_code(self):
        # "python" and "language" appear in code.python description
        result = suggest_domain("python language")
        assert result is not None
        assert "code" in result

    def test_calculus_suggests_stem(self):
        # "calculus" appears in stem.math description
        result = suggest_domain("calculus")
        assert result is not None

    def test_no_match_returns_none(self):
        # Very unusual topic with zero overlap
        result = suggest_domain("zzzzxxx")
        assert result is None


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


class TestScaffoldTrainingDirectories:
    def test_creates_full_tree(self):
        from familiar.skills.training.scaffold import scaffold_training_directories

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            result = scaffold_training_directories(mem_dir)

            assert result["total_dirs"] > 0
            assert result["created"] > 0
            # training root exists
            assert (mem_dir / "training").is_dir()
            # personal dir exists
            assert (mem_dir / "personal").is_dir()

    def test_idempotent(self):
        from familiar.skills.training.scaffold import scaffold_training_directories

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            r1 = scaffold_training_directories(mem_dir)
            r2 = scaffold_training_directories(mem_dir)
            assert r2["created"] == 0
            assert r2["existing"] == r1["total_dirs"]

    def test_subdomain_dirs_created(self):
        from familiar.skills.training.scaffold import scaffold_training_directories

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            scaffold_training_directories(mem_dir)
            assert (mem_dir / "training" / "practical" / "cooking").is_dir()
            assert (mem_dir / "training" / "code" / "python").is_dir()
            assert (mem_dir / "training" / "stem" / "math").is_dir()

    def test_readme_written_in_subdirs(self):
        from familiar.skills.training.scaffold import scaffold_training_directories

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            scaffold_training_directories(mem_dir)
            readme = mem_dir / "training" / "practical" / "cooking" / "_README.md"
            assert readme.exists()
            content = readme.read_text()
            assert "practical.cooking" in content
            assert "training_eligible" in content


class TestGenerateTrainingDocument:
    def test_creates_file_in_correct_location(self):
        from familiar.skills.training.scaffold import generate_training_document

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            file_path, content = generate_training_document(
                domain="practical",
                subdomain="cooking",
                topic="knife sharpening",
                memories_dir=mem_dir,
            )
            assert file_path.exists()
            assert file_path.parent == mem_dir / "training" / "practical" / "cooking"
            assert "knife-sharpening" in file_path.name

    def test_frontmatter_fields_present(self):
        from familiar.skills.training.scaffold import generate_training_document

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            _, content = generate_training_document(
                domain="code",
                subdomain="python",
                topic="list comprehensions",
                memories_dir=mem_dir,
            )
            assert 'domain: "code"' in content
            assert 'subdomain: "python"' in content
            assert 'topic: "list comprehensions"' in content
            assert "training_eligible: true" in content
            assert "pairs:" in content

    def test_invalid_domain_raises(self):
        from familiar.skills.training.scaffold import generate_training_document

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            with pytest.raises(ValueError, match="Unknown domain"):
                generate_training_document(
                    domain="fantasy",
                    subdomain="dragons",
                    topic="fire breathing",
                    memories_dir=mem_dir,
                )

    def test_no_overwrite_collision(self):
        from familiar.skills.training.scaffold import generate_training_document

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            p1, _ = generate_training_document("practical", "cooking", "salt", memories_dir=mem_dir)
            p2, _ = generate_training_document("practical", "cooking", "salt", memories_dir=mem_dir)
            assert p1 != p2
            assert p1.exists()
            assert p2.exists()

    def test_explicit_pairs_in_content(self):
        from familiar.skills.training.scaffold import generate_training_document

        pairs = [
            {
                "instruction": "How do you sharpen a knife?",
                "response": "Use a whetstone.",
                "instruction_type": "how_to",
            },
            {
                "instruction": "Why sharpen knives?",
                "response": "For safety and precision.",
                "instruction_type": "explain",
            },
        ]
        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            _, content = generate_training_document(
                "practical", "cooking", "knife care", pairs=pairs, memories_dir=mem_dir
            )
            assert "How do you sharpen a knife?" in content
            assert "whetstone" in content
            assert "why sharpen" in content.lower()


class TestGetDomainStats:
    def test_empty_returns_empty_dict(self):
        from familiar.skills.training.scaffold import get_domain_stats

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            stats = get_domain_stats(mem_dir)
            assert stats == {}

    def test_counts_documents(self):
        from familiar.skills.training.scaffold import generate_training_document, get_domain_stats

        with tempfile.TemporaryDirectory() as tmp:
            mem_dir = Path(tmp) / "memories"
            mem_dir.mkdir()
            generate_training_document("practical", "cooking", "topic1", memories_dir=mem_dir)
            generate_training_document("practical", "cooking", "topic2", memories_dir=mem_dir)
            generate_training_document("code", "python", "topic3", memories_dir=mem_dir)
            stats = get_domain_stats(mem_dir)
            assert stats["practical"]["cooking"]["files"] == 2
            assert stats["code"]["python"]["files"] == 1
