"""End-to-end tests for the chunker pipeline: heuristic pass → prompt generation → output validation."""

import json
from pathlib import Path

from shelfai.core.chunker import prefilter, ChunkResult
from shelfai.prompts import format_semantic_chunk_prompt


# ──────────────────────────────────────────────
# Fixtures
# ──────────────────────────────────────────────


REALISTIC_AGENT = """\
# DevOps Agent

You are a DevOps automation agent for the Acme platform team.

## Mission

Manage infrastructure, deployments, and incident response for the Acme SaaS platform.

## Rules

- Never run destructive operations without confirmation
- Always check current deployment status before proposing changes
- Escalate P1 incidents to the on-call engineer immediately

## System Integration

Read these files on every run:
1. `memory/agent/lessons.md`
2. `knowledge/infra_topology.md`

## Deployment Workflow

### Standard Deploy
1. Pull latest from main
2. Run `terraform plan` and review
3. Apply with `terraform apply`

### Rollback Procedure
1. Identify the last known good commit
2. Revert the deployment manifest
3. Verify health checks pass

## Incident Response

### Triage
- Check CloudWatch alarms
- Review recent deployments

### Communication
- Post in #incidents Slack channel
- Notify on-call if P1

## Database Operations

### Migrations
- Always run migrations in a transaction
- Test on staging first

### Backups
- Verify daily automated backups
- Test restore quarterly

## Monitoring Setup

### Alerting Thresholds
- Error rate > 1%: P2 alert
- Error rate > 5%: P1 alert
- Latency p99 > 2s: P2 alert
"""


MINIMAL_AGENT = """\
# Tiny Agent

Just do the thing.
"""


RULES_ONLY_AGENT = """\
# Rules Agent

## Rules

- Be safe
- Be kind

## Guidelines

- Ask before acting
"""


# ──────────────────────────────────────────────
# Heuristic pass tests
# ──────────────────────────────────────────────


class TestHeuristicPass:
    """Verify the heuristic chunker correctly classifies sections."""

    def test_extracts_always_loaded_sections(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)

        assert isinstance(result, ChunkResult)
        assert "soul" in result.chunks
        assert "rules" in result.chunks
        assert "read-order" in result.chunks
        for name in ("soul", "rules", "read-order"):
            assert result.chunks[name]["class"] == "always"

    def test_remaining_sections_are_task_class(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)

        assert "remaining" in result.chunks
        assert result.chunks["remaining"]["class"] == "task"

    def test_remaining_contains_all_unmatched_sections(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)

        remaining = result.chunks["remaining"]
        assert "sections" in remaining
        section_titles = [s["title"] for s in remaining["sections"]]
        assert "Deployment Workflow" in section_titles
        assert "Incident Response" in section_titles
        assert "Database Operations" in section_titles
        assert "Monitoring Setup" in section_titles
        # These should NOT be in remaining
        assert "Mission" not in section_titles
        assert "Rules" not in section_titles
        assert "System Integration" not in section_titles

    def test_savings_percentage_is_reasonable(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)

        assert 0 <= result.savings_pct <= 100
        assert result.always_lines > 0
        assert result.always_lines < result.original_lines

    def test_minimal_agent_without_h2_sections(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(MINIMAL_AGENT, encoding="utf-8")

        result = prefilter(agent_file)

        assert "remaining" in result.chunks
        assert result.chunks["remaining"]["sections"] == []
        assert result.always_lines == 0

    def test_agent_with_only_always_sections(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(RULES_ONLY_AGENT, encoding="utf-8")

        result = prefilter(agent_file)

        assert "rules" in result.chunks
        # Guidelines matches the rules pattern
        assert result.chunks["rules"]["class"] == "always"
        # No remaining sections if everything is always-loaded
        if "remaining" in result.chunks:
            assert result.chunks["remaining"]["lines"] == 0 or result.chunks["remaining"]["sections"] == []


# ──────────────────────────────────────────────
# Prompt generation tests
# ──────────────────────────────────────────────


class TestPromptGeneration:
    """Verify the semantic chunking prompt is well-formed."""

    def test_prompt_includes_all_section_titles(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)
        remaining = result.chunks["remaining"]
        prompt = format_semantic_chunk_prompt(remaining["sections"])

        for section in remaining["sections"]:
            assert section["title"] in prompt

    def test_prompt_includes_line_counts(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)
        remaining = result.chunks["remaining"]
        prompt = format_semantic_chunk_prompt(remaining["sections"])

        for section in remaining["sections"]:
            assert f"{section['lines']} lines" in prompt

    def test_prompt_includes_content_previews(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)
        remaining = result.chunks["remaining"]
        prompt = format_semantic_chunk_prompt(remaining["sections"])

        # First line of each section should appear in the prompt preview
        assert "Standard Deploy" in prompt
        assert "CloudWatch" in prompt

    def test_prompt_contains_json_example(self, tmp_path):
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)
        remaining = result.chunks["remaining"]
        prompt = format_semantic_chunk_prompt(remaining["sections"])

        assert '"sections"' in prompt
        assert '"trigger"' in prompt

    def test_prompt_with_empty_sections_list(self):
        prompt = format_semantic_chunk_prompt([])

        # Should still be a valid prompt, just with no sections
        assert "Remaining Sections" in prompt
        assert "Return ONLY valid JSON" in prompt

    def test_prompt_truncates_long_sections(self, tmp_path):
        """Sections with >5 lines should show preview with ..."""
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)
        remaining = result.chunks["remaining"]
        prompt = format_semantic_chunk_prompt(remaining["sections"])

        # At least one section has >5 lines and should be truncated
        has_truncated = any(
            s["lines"] > 5 for s in remaining["sections"]
        )
        if has_truncated:
            assert "..." in prompt


# ──────────────────────────────────────────────
# Round-trip: simulated LLM response validation
# ──────────────────────────────────────────────


class TestSemanticRoundTrip:
    """Verify that a well-formed LLM response to the prompt is usable."""

    def test_simulated_llm_response_parses_as_json(self, tmp_path):
        """Simulate what an LLM would return and verify it's valid."""
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        result = prefilter(agent_file)
        remaining = result.chunks["remaining"]
        section_titles = [s["title"] for s in remaining["sections"]]

        # Simulate a reasonable LLM response
        simulated_response = {
            "deployment": {
                "sections": ["Deployment Workflow"],
                "trigger": "Load when deploying, releasing, or rolling back.",
            },
            "incident-response": {
                "sections": ["Incident Response"],
                "trigger": "Load during incident triage or communication.",
            },
            "database": {
                "sections": ["Database Operations"],
                "trigger": "Load when running migrations or managing backups.",
            },
            "monitoring": {
                "sections": ["Monitoring Setup"],
                "trigger": "Load when configuring alerts or reviewing thresholds.",
            },
        }

        response_json = json.dumps(simulated_response)
        parsed = json.loads(response_json)

        # Every section from the LLM response should reference a real section
        for chunk_name, chunk_data in parsed.items():
            assert "sections" in chunk_data
            assert "trigger" in chunk_data
            assert isinstance(chunk_data["sections"], list)
            assert isinstance(chunk_data["trigger"], str)
            for section_ref in chunk_data["sections"]:
                assert section_ref in section_titles, (
                    f"LLM referenced '{section_ref}' but it's not in remaining sections: {section_titles}"
                )

        # Every remaining section should be assigned to exactly one chunk
        assigned = []
        for chunk_data in parsed.values():
            assigned.extend(chunk_data["sections"])
        assert sorted(assigned) == sorted(section_titles), (
            f"Not all sections assigned. Missing: {set(section_titles) - set(assigned)}"
        )

    def test_chunk_names_are_valid_filenames(self, tmp_path):
        """Chunk names from the prompt example should work as filenames."""
        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        simulated_response = {
            "deployment": {
                "sections": ["Deployment Workflow"],
                "trigger": "Load when deploying.",
            },
            "incident-response": {
                "sections": ["Incident Response"],
                "trigger": "Load during incidents.",
            },
        }

        for chunk_name in simulated_response:
            # Should be safe as a filename
            assert chunk_name == Path(chunk_name).name
            assert "/" not in chunk_name
            assert "\\" not in chunk_name
            assert ".." not in chunk_name


# ──────────────────────────────────────────────
# CLI integration test
# ──────────────────────────────────────────────


class TestChunkCLI:
    """Test the chunk command output end-to-end."""

    def test_chunk_cmd_preview_runs_without_error(self, tmp_path):
        """Run the chunk command in preview mode and verify output."""
        from typer.testing import CliRunner
        from shelfai.cli.main import app

        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        runner = CliRunner()
        result = runner.invoke(app, ["chunk", str(agent_file)])

        assert result.exit_code == 0
        assert "Pre-filter results" in result.output
        assert "soul" in result.output
        assert "rules" in result.output
        assert "Semantic chunking prompt" in result.output
        assert "Remaining Sections" in result.output

    def test_chunk_cmd_write_creates_files(self, tmp_path):
        """Run chunk --write and verify files are created."""
        from typer.testing import CliRunner
        from shelfai.cli.main import app

        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(REALISTIC_AGENT, encoding="utf-8")

        runner = CliRunner()
        result = runner.invoke(app, ["chunk", str(agent_file), "--write"])

        assert result.exit_code == 0

        chunks_dir = tmp_path / "chunks"
        assert chunks_dir.exists()
        # Should have soul, rules, read-order chunk files
        chunk_files = [f.stem for f in chunks_dir.glob("*.md")]
        assert "soul" in chunk_files
        assert "rules" in chunk_files
        assert "read-order" in chunk_files
        # remaining should NOT be written (it's for the LLM pass)
        assert "remaining" not in chunk_files

    def test_chunk_cmd_no_prompt_when_no_remaining_sections(self, tmp_path):
        """If all sections are always-loaded, no semantic prompt needed."""
        from typer.testing import CliRunner
        from shelfai.cli.main import app

        agent_file = tmp_path / "AGENT.md"
        agent_file.write_text(RULES_ONLY_AGENT, encoding="utf-8")

        runner = CliRunner()
        result = runner.invoke(app, ["chunk", str(agent_file)])

        assert result.exit_code == 0
        assert "Semantic chunking prompt" not in result.output
