"""
Unit tests for the project journal protocol.
"""

import json
import pytest
from pathlib import Path
from datetime import datetime

from agent_orchestrator.journal.status_packet import (
    StatusPacket,
    TaskArtifacts,
    create_success_packet,
    create_blocked_packet,
    create_failed_packet,
    create_approval_packet,
)
from agent_orchestrator.journal.project_journal import ProjectJournal


class TestTaskArtifacts:
    """Tests for TaskArtifacts dataclass."""

    def test_default_values(self):
        """Test default artifact values."""
        artifacts = TaskArtifacts()
        assert artifacts.diff_summary == ""
        assert artifacts.tests_run == []
        assert artifacts.test_results == {"passed": 0, "failed": 0, "skipped": 0}
        assert artifacts.files_modified == []
        assert artifacts.tokens_input == 0

    def test_to_dict(self):
        """Test converting artifacts to dictionary."""
        artifacts = TaskArtifacts(
            diff_summary="Added login endpoint",
            files_modified=["src/auth.py", "tests/test_auth.py"],
            test_results={"passed": 5, "failed": 0, "skipped": 1},
        )
        d = artifacts.to_dict()

        assert d["diff_summary"] == "Added login endpoint"
        assert d["files_modified"] == ["src/auth.py", "tests/test_auth.py"]
        assert d["test_results"]["passed"] == 5

    def test_from_dict(self):
        """Test creating artifacts from dictionary."""
        data = {
            "diff_summary": "Test summary",
            "files_modified": ["file1.py"],
            "tokens_input": 1000,
        }
        artifacts = TaskArtifacts.from_dict(data)

        assert artifacts.diff_summary == "Test summary"
        assert artifacts.files_modified == ["file1.py"]
        assert artifacts.tokens_input == 1000

    def test_tests_passed(self):
        """Test checking if all tests passed."""
        passing = TaskArtifacts(test_results={"passed": 10, "failed": 0, "skipped": 2})
        failing = TaskArtifacts(test_results={"passed": 8, "failed": 2, "skipped": 0})

        assert passing.tests_passed() is True
        assert failing.tests_passed() is False

    def test_has_critical_risks(self):
        """Test detecting critical risks."""
        no_risks = TaskArtifacts(risk_items_encountered=[])
        minor_risks = TaskArtifacts(risk_items_encountered=["warning: deprecated API"])
        critical_risks = TaskArtifacts(risk_items_encountered=["CRITICAL: credential exposure"])

        assert no_risks.has_critical_risks() is False
        assert minor_risks.has_critical_risks() is False
        assert critical_risks.has_critical_risks() is True


class TestStatusPacket:
    """Tests for StatusPacket dataclass."""

    def test_default_values(self):
        """Test default packet values."""
        packet = StatusPacket(agent_id="test", task_id="task-1")

        assert packet.agent_id == "test"
        assert packet.task_id == "task-1"
        assert packet.status == "completed"
        assert isinstance(packet.timestamp, datetime)
        assert isinstance(packet.artifacts, TaskArtifacts)

    def test_to_dict(self):
        """Test converting packet to dictionary."""
        packet = StatusPacket(
            agent_id="agent-1",
            task_id="task-1",
            status="completed",
            progress_summary="Implemented feature",
            blockers=["Waiting for review"],
            next_steps=["Run tests"],
        )
        d = packet.to_dict()

        assert d["agent_id"] == "agent-1"
        assert d["status"] == "completed"
        assert d["blockers"] == ["Waiting for review"]
        assert "timestamp" in d

    def test_to_json(self):
        """Test converting packet to JSON."""
        packet = StatusPacket(agent_id="agent-1", task_id="task-1")
        json_str = packet.to_json()

        # Should be valid JSON
        parsed = json.loads(json_str)
        assert parsed["agent_id"] == "agent-1"

    def test_from_dict(self):
        """Test creating packet from dictionary."""
        data = {
            "agent_id": "agent-1",
            "task_id": "task-1",
            "status": "blocked",
            "blockers": ["Missing dependency"],
            "artifacts": {
                "files_modified": ["src/main.py"],
            },
        }
        packet = StatusPacket.from_dict(data)

        assert packet.agent_id == "agent-1"
        assert packet.status == "blocked"
        assert packet.blockers == ["Missing dependency"]
        assert packet.artifacts.files_modified == ["src/main.py"]

    def test_from_json(self):
        """Test creating packet from JSON string."""
        json_str = '{"agent_id": "test", "task_id": "task-1", "status": "completed"}'
        packet = StatusPacket.from_json(json_str)

        assert packet.agent_id == "test"
        assert packet.status == "completed"

    def test_is_successful(self):
        """Test checking if run was successful."""
        success = StatusPacket(agent_id="a", task_id="t", status="completed")
        blocked = StatusPacket(agent_id="a", task_id="t", status="blocked")
        failed = StatusPacket(agent_id="a", task_id="t", status="failed")

        assert success.is_successful() is True
        assert blocked.is_successful() is False
        assert failed.is_successful() is False

    def test_needs_human_intervention(self):
        """Test checking if human intervention needed."""
        normal = StatusPacket(agent_id="a", task_id="t", status="completed")
        blocked = StatusPacket(agent_id="a", task_id="t", status="blocked")
        needs_approval = StatusPacket(agent_id="a", task_id="t", status="needs_approval")
        has_blockers = StatusPacket(agent_id="a", task_id="t", status="completed", blockers=["x"])

        assert normal.needs_human_intervention() is False
        assert blocked.needs_human_intervention() is True
        assert needs_approval.needs_human_intervention() is True
        assert has_blockers.needs_human_intervention() is True


class TestFactoryFunctions:
    """Tests for packet factory functions."""

    def test_create_success_packet(self):
        """Test creating a success packet."""
        packet = create_success_packet(
            agent_id="agent-1",
            task_id="task-1",
            summary="Completed feature",
            files_modified=["src/feature.py"],
            next_steps=["Run tests"],
        )

        assert packet.status == "completed"
        assert packet.progress_summary == "Completed feature"
        assert packet.artifacts.files_modified == ["src/feature.py"]
        assert packet.next_steps == ["Run tests"]

    def test_create_blocked_packet(self):
        """Test creating a blocked packet."""
        packet = create_blocked_packet(
            agent_id="agent-1",
            task_id="task-1",
            summary="Waiting for dependency",
            blockers=["Missing package X", "Need API key"],
        )

        assert packet.status == "blocked"
        assert len(packet.blockers) == 2

    def test_create_failed_packet(self):
        """Test creating a failed packet."""
        packet = create_failed_packet(
            agent_id="agent-1",
            task_id="task-1",
            error_message="Connection timeout",
        )

        assert packet.status == "failed"
        assert "Connection timeout" in packet.error_message

    def test_create_approval_packet(self):
        """Test creating an approval-needed packet."""
        packet = create_approval_packet(
            agent_id="agent-1",
            task_id="task-1",
            summary="Ready to deploy",
            action_requiring_approval="Deploy to production",
        )

        assert packet.status == "needs_approval"
        assert "Deploy to production" in packet.blockers[0]


class TestProjectJournal:
    """Tests for ProjectJournal class."""

    @pytest.fixture
    def journal(self, temp_dir: Path) -> ProjectJournal:
        """Create a test journal."""
        return ProjectJournal(temp_dir)

    def test_read_initial_state(self, journal: ProjectJournal):
        """Test reading state when no file exists."""
        state = journal.read_state()

        assert state["version"] == "1.0"
        assert "current_objectives" in state
        assert "decisions_made" in state
        assert "constraints" in state

    def test_write_status(self, journal: ProjectJournal):
        """Test writing a status packet."""
        packet = StatusPacket(
            agent_id="agent-1",
            task_id="task-1",
            status="completed",
            progress_summary="Implemented login",
            artifacts=TaskArtifacts(files_modified=["src/auth.py"]),
            state_changes={"shared_context": {"auth_method": "jwt"}},
            next_steps=["Add tests"],
        )
        journal.write_status(packet)

        # Check state was updated
        state = journal.read_state()
        assert state["updated_by"] == "agent-1"
        assert state["shared_context"]["auth_method"] == "jwt"

        # Check journal file was created
        assert journal.journal_file.exists()

    def test_record_decision(self, journal: ProjectJournal):
        """Test recording a decision."""
        journal.record_decision(
            agent_id="agent-1",
            decision="Use PostgreSQL",
            rationale="Team familiarity",
            reversible=True,
        )

        state = journal.read_state()
        assert len(state["decisions_made"]) == 1
        assert state["decisions_made"][0]["decision"] == "Use PostgreSQL"

    def test_add_objective(self, journal: ProjectJournal):
        """Test adding an objective."""
        obj_id = journal.add_objective(
            description="Implement user auth",
            priority=5,
            assigned_agent="claude_code",
        )

        assert obj_id.startswith("obj-")

        state = journal.read_state()
        objectives = state["current_objectives"]
        assert len(objectives) == 1
        assert objectives[0]["description"] == "Implement user auth"
        assert objectives[0]["assigned_agent"] == "claude_code"

    def test_update_objective_status(self, journal: ProjectJournal):
        """Test updating objective status."""
        obj_id = journal.add_objective("Test objective")
        journal.update_objective_status(obj_id, "completed")

        state = journal.read_state()
        assert state["current_objectives"][0]["status"] == "completed"

    def test_add_risk(self, journal: ProjectJournal):
        """Test adding a risk."""
        risk_id = journal.add_risk(
            description="API rate limits",
            severity="medium",
            mitigation="Implement caching",
        )

        assert risk_id.startswith("risk-")

        state = journal.read_state()
        assert len(state["open_risks"]) == 1

    def test_add_constraint(self, journal: ProjectJournal):
        """Test adding a constraint."""
        journal.add_constraint("No direct database access")
        journal.add_constraint("All endpoints need auth")

        state = journal.read_state()
        assert len(state["constraints"]) == 2

        # Adding duplicate should not create new entry
        journal.add_constraint("No direct database access")
        state = journal.read_state()
        assert len(state["constraints"]) == 2

    def test_get_recent_decisions(self, journal: ProjectJournal):
        """Test getting recent decisions."""
        for i in range(10):
            journal.record_decision(
                agent_id=f"agent-{i}",
                decision=f"Decision {i}",
                rationale="Testing",
            )

        recent = journal.get_recent_decisions(limit=3)
        assert len(recent) == 3
        # Should be the most recent
        assert recent[-1]["decision"] == "Decision 9"

    def test_get_active_objectives(self, journal: ProjectJournal):
        """Test getting active (non-completed) objectives."""
        journal.add_objective("Active 1")
        obj_id = journal.add_objective("To complete")
        journal.add_objective("Active 2")

        journal.update_objective_status(obj_id, "completed")

        active = journal.get_active_objectives()
        assert len(active) == 2

    def test_get_context_for_agent(self, journal: ProjectJournal):
        """Test getting context suitable for agent injection."""
        journal.add_constraint("Be careful with auth")
        journal.record_decision("agent-1", "Use JWT", "Standard")
        journal.add_objective("Build login")

        context = journal.get_context_for_agent()

        assert "project_state" in context
        assert "constraints" in context
        assert "recent_decisions" in context
        assert "active_objectives" in context

    def test_reset(self, journal: ProjectJournal):
        """Test resetting the journal."""
        journal.record_decision("agent-1", "Test decision", "Testing")
        journal.add_objective("Test objective")

        journal.reset()

        state = journal.read_state()
        assert len(state["decisions_made"]) == 0
        assert len(state["current_objectives"]) == 0
