from __future__ import annotations

from pathlib import Path
from types import SimpleNamespace

from fastapi.testclient import TestClient

from beaver.memory.runs import RunRecord
from beaver.interfaces.web.app import create_app
from beaver.memory.skills import SkillDraftEvalReport, SkillLearningCandidate
from beaver.services.agent_service import AgentService
from beaver.skills.specs import SkillVersion


class StubEvaluator:
    def __init__(self) -> None:
        self.calls = 0

    async def evaluate(self, *, candidate, draft, provider_bundle, replay_runner=None):
        self.calls += 1
        return SkillDraftEvalReport(
            report_id="eval-existing",
            skill_name=draft.skill_name,
            draft_id=draft.draft_id,
            candidate_id=candidate.candidate_id,
            passed=True,
            baseline_score_avg=0.5,
            candidate_score_avg=0.8,
            score_delta=0.3,
            regression_count=0,
            improved_count=1,
            unchanged_count=0,
            status="completed",
        )


def test_skill_learning_candidates_and_run_once_api(tmp_path: Path) -> None:
    service = AgentService(workspace=tmp_path)
    loaded = service.create_loop().boot()
    loaded.skill_learning_store.record_learning_candidate(  # type: ignore[union-attr]
        SkillLearningCandidate(
            candidate_id="candidate-1",
            kind="new_skill",
            source_run_ids=[],
            source_session_ids=[],
            related_skill_names=[],
            reason="test",
        )
    )
    app = create_app(service=service, manage_service_lifecycle=False)

    with TestClient(app) as client:
        candidates = client.get("/api/skills/candidates").json()
        run_once = client.post("/api/skills/learning/run-once").json()

    assert candidates[0]["candidate_id"] == "candidate-1"
    assert "risk_level" in candidates[0]
    assert run_once["processed"] >= 0


def test_skill_learning_candidates_payload_prefers_original_task_text(tmp_path: Path) -> None:
    service = AgentService(workspace=tmp_path)
    loaded = service.create_loop().boot()
    now = "2026-06-11T00:00:00+00:00"
    loaded.skill_learning_service.run_store.append_run_record(  # type: ignore[union-attr]
        RunRecord(
            run_id="run-original",
            session_id="session-task",
            task_id="task-1",
            attempt_index=1,
            task_text="Compare direct production restart with staging rollout",
            started_at=now,
            ended_at=now,
            success=False,
            finish_reason="stop",
            feedback={"feedback_type": "revise", "comment": "I do not see the docs"},
            activated_skills=[],
            validation_result=None,
        )
    )
    loaded.skill_learning_service.run_store.append_run_record(  # type: ignore[union-attr]
        RunRecord(
            run_id="run-final",
            session_id="session-task",
            task_id="task-1",
            attempt_index=2,
            task_text="I do not see the docs",
            started_at=now,
            ended_at=now,
            success=True,
            finish_reason="stop",
            feedback={"feedback_type": "satisfied", "acceptance_type": "accept"},
            activated_skills=[],
            validation_result={"accepted": True, "score": 0.9},
        )
    )
    loaded.skill_learning_store.record_learning_candidate(  # type: ignore[union-attr]
        SkillLearningCandidate(
            candidate_id="new:task:task-1",
            kind="new_skill",
            source_run_ids=["run-original", "run-final"],
            source_session_ids=["session-task"],
            related_skill_names=[],
            reason="test",
            evidence={"task_id": "task-1", "theme": "i do not see the docs"},
        )
    )
    app = create_app(service=service, manage_service_lifecycle=False)

    with TestClient(app) as client:
        candidates = client.get("/api/skills/candidates").json()

    payload = next(item for item in candidates if item["candidate_id"] == "new:task:task-1")
    assert payload["evidence"]["theme"] == "Compare direct production restart with staging rollout"
    assert payload["evidence"]["task_text"] == "Compare direct production restart with staging rollout"


def test_generate_draft_does_not_run_review_checks(tmp_path: Path, monkeypatch) -> None:
    service = AgentService(workspace=tmp_path)
    loaded = service.create_loop().boot()
    draft = loaded.skill_learning_pipeline.draft_service.create_new_skill_draft(  # type: ignore[union-attr]
        skill_name="filesystem-operation",
        proposed_content="# Filesystem Operation\n\nUse files safely.",
        proposed_frontmatter={"description": "filesystem", "tools": []},
        created_by="test",
        reason="test",
    )
    loaded.skill_learning_store.record_learning_candidate(  # type: ignore[union-attr]
        SkillLearningCandidate(
            candidate_id="candidate-existing",
            kind="revise_skill",
            source_run_ids=["run-1"],
            source_session_ids=["session-1"],
            related_skill_names=["filesystem-operation"],
            reason="revise",
            status="draft_ready",
            draft_skill_name=draft.skill_name,
            draft_id=draft.draft_id,
        )
    )
    evaluator = StubEvaluator()
    loaded.skill_learning_pipeline.evaluator = evaluator  # type: ignore[union-attr]
    monkeypatch.setattr(
        service,
        "_make_provider_bundle_for_task",
        lambda loaded, kwargs: SimpleNamespace(main_provider=object()),
    )
    app = create_app(service=service, manage_service_lifecycle=False)

    with TestClient(app) as client:
        response = client.post("/api/skills/candidates/candidate-existing/draft")

    assert response.status_code == 200
    payload = response.json()
    assert evaluator.calls == 0
    assert payload["draft_id"] == draft.draft_id
    assert payload["safety_report"] is None
    assert payload["eval_report"] is None
    assert loaded.skill_learning_pipeline.get_eval_report(draft.skill_name, draft.draft_id) is None  # type: ignore[union-attr]


def test_submit_draft_runs_safety_and_eval(tmp_path: Path, monkeypatch) -> None:
    service = AgentService(workspace=tmp_path)
    loaded = service.create_loop().boot()
    draft = loaded.skill_learning_pipeline.draft_service.create_new_skill_draft(  # type: ignore[union-attr]
        skill_name="filesystem-operation",
        proposed_content="# Filesystem Operation\n\nUse files safely.",
        proposed_frontmatter={"description": "filesystem", "tools": []},
        created_by="test",
        reason="test",
    )
    loaded.skill_learning_store.record_learning_candidate(  # type: ignore[union-attr]
        SkillLearningCandidate(
            candidate_id="candidate-existing",
            kind="revise_skill",
            source_run_ids=["run-1"],
            source_session_ids=["session-1"],
            related_skill_names=["filesystem-operation"],
            reason="revise",
            status="draft_ready",
            draft_skill_name=draft.skill_name,
            draft_id=draft.draft_id,
        )
    )
    evaluator = StubEvaluator()
    loaded.skill_learning_pipeline.evaluator = evaluator  # type: ignore[union-attr]
    monkeypatch.setattr(
        service,
        "_make_provider_bundle_for_task",
        lambda loaded, kwargs: SimpleNamespace(main_provider=object()),
    )
    app = create_app(service=service, manage_service_lifecycle=False)

    with TestClient(app) as client:
        response = client.post(f"/api/skills/{draft.skill_name}/drafts/{draft.draft_id}/submit")

    assert response.status_code == 200
    payload = response.json()
    assert evaluator.calls == 1
    assert payload["status"] == "in_review"
    assert payload["safety_report"]["passed"] is True
    assert payload["eval_report"]["report_id"] == "eval-existing"


def test_draft_payload_includes_target_version_for_revision(tmp_path: Path) -> None:
    service = AgentService(workspace=tmp_path)
    loaded = service.create_loop().boot()
    loaded.skill_spec_store.write_skill_version(  # type: ignore[union-attr]
        SkillVersion(
            skill_name="filesystem-operation",
            version="v0001",
            content_hash="hash-v1",
            summary_hash="summary-v1",
            created_at="2026-06-01T00:00:00+00:00",
            created_by="test",
            change_reason="initial",
            parent_version=None,
            review_state="published",
            frontmatter={"description": "filesystem", "name": "filesystem-operation", "tools": []},
            summary="filesystem",
            tool_hints=[],
        ),
        "# Filesystem Operation\n\nUse files.",
    )
    loaded.skill_spec_store.set_current_version("filesystem-operation", "v0001")  # type: ignore[union-attr]
    draft = loaded.skill_learning_pipeline.draft_service.create_revision_draft(  # type: ignore[union-attr]
        skill_name="filesystem-operation",
        base_version="v0001",
        proposed_content="# Filesystem Operation\n\nUse files better.",
        proposed_frontmatter={"description": "filesystem", "name": "filesystem-operation", "tools": []},
        created_by="test",
        reason="revise",
    )
    app = create_app(service=service, manage_service_lifecycle=False)

    with TestClient(app) as client:
        response = client.get("/api/skills/drafts")

    assert response.status_code == 200
    payload = next(item for item in response.json() if item["draft_id"] == draft.draft_id)
    assert payload["proposal_kind"] == "revise_skill"
    assert payload["base_version"] == "v0001"
    assert payload["target_version"] == "v0002"
    assert payload["base_skill"]["version"] == "v0001"
    assert payload["base_skill"]["content"] == "# Filesystem Operation\n\nUse files."
    assert payload["base_skill"]["frontmatter"]["name"] == "filesystem-operation"
