"""WF02 artifact lifecycle assertions.

Implements artifact generation assertions through lifecycle-produced
changesets and user-facing artifacts output.
"""

from __future__ import annotations

import json
import sys
from io import StringIO
from pathlib import Path

from wf02_test_generation_common import (
    assert_invariants_and_conventions,
    auth_module_fixture,
    create_trusted_plan,
    create_wf02_action,
    generate_tests_from_mock_provider,
    setup_lifecycle,
    validated_relative_test_path,
)

from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest
from cleveragents.application.services.plan_apply_service import PlanApplyService
from cleveragents.application.services.plan_execution_context import (
    PlanExecutionContext,
)
from cleveragents.domain.models.core.change import (
    ChangeEntry,
    ChangeOperation,
    InMemoryChangeSetStore,
)
from cleveragents.tool.builtins.changeset import ChangeSetCapture
from cleveragents.tool.builtins.file_tools import FILE_WRITE_SPEC


def _capture_changeset_from_file_lifecycle(
    plan_id: str,
    fixture_root: Path,
    generated_files: dict[str, str],
) -> tuple[str, InMemoryChangeSetStore]:
    """Produce a changeset through ChangeSetCapture + file-write lifecycle."""
    capture = ChangeSetCapture(
        plan_id=plan_id,
        resource_id="local/api-service-repo",
        sandbox_root=str(fixture_root),
    )
    wrapped_file_write = capture.wrap_tool(FILE_WRITE_SPEC)

    normalized: dict[str, str] = {}
    for rel_path, content in generated_files.items():
        output_path = validated_relative_test_path(fixture_root, rel_path)
        normalized_rel = output_path.relative_to(fixture_root.resolve()).as_posix()
        wrapped_file_write.handler(
            {
                "path": normalized_rel,
                "content": content,
                "create_dirs": True,
                "sandbox_root": str(fixture_root),
            }
        )
        normalized[normalized_rel] = content

    lifecycle_changeset = capture.to_spec_changeset()
    store = InMemoryChangeSetStore()
    execution_context = PlanExecutionContext(
        plan_id=plan_id,
        automation_profile="trusted",
        changeset_store=store,
    )
    changeset_id = execution_context.start_changeset()
    for entry in lifecycle_changeset.entries:
        operation = (
            ChangeOperation(entry.operation)
            if isinstance(entry.operation, str)
            else ChangeOperation(entry.operation.value)
        )
        execution_context.record_change(
            ChangeEntry(
                plan_id=plan_id,
                resource_id=entry.resource_id,
                tool_name=entry.tool_name,
                operation=operation,
                path=entry.path,
                before_hash=entry.before_hash,
                after_hash=entry.after_hash,
                timestamp=entry.timestamp,
            )
        )

    return changeset_id, store


def wf02_mocked_generation_artifacts() -> None:
    """Verify lifecycle-produced artifacts include provider-derived outputs."""
    lifecycle = setup_lifecycle()
    create_wf02_action(lifecycle)
    plan_id = create_trusted_plan(lifecycle)
    lifecycle.try_auto_run(plan_id)

    with auth_module_fixture() as (fixture_root, baseline_coverage):
        assert (fixture_root / "src" / "auth.py").is_file()
        assert baseline_coverage < 80.0

        generated_files = generate_tests_from_mock_provider(
            fixture_root,
            "src/auth",
            80,
            include_coverage_suite=True,
        )
        assert "tests/test_provider_mock_output.py" in generated_files

        changeset_id, store = _capture_changeset_from_file_lifecycle(
            plan_id,
            fixture_root,
            generated_files,
        )

        plan = lifecycle.get_plan(plan_id)
        plan.changeset_id = changeset_id
        plan.sandbox_refs = [f"sandbox-{plan_id[:8]}"]
        lifecycle.save_plan(plan)

        assert_invariants_and_conventions(
            fixture_root,
            plan_id,
            lifecycle,
            generated_files,
        )

        apply_service = PlanApplyService(
            lifecycle_service=lifecycle, changeset_store=store
        )
        capture = StringIO()
        old_stdout = sys.stdout
        sys.stdout = capture
        try:
            artifact_payload = apply_service.artifacts(plan_id, fmt="json")
        finally:
            sys.stdout = old_stdout

        rendered = artifact_payload or capture.getvalue().strip()
        parsed = json.loads(rendered)
        assert parsed["changeset_id"] == changeset_id
        raw_files = parsed["files_changed"]
        changed_files = {
            item["path"] if isinstance(item, dict) else str(item) for item in raw_files
        }
        expected_files = set(generated_files)
        assert len(changed_files) == len(expected_files), (
            "Artifact count mismatch: expected "
            f"{len(expected_files)} files, got {len(changed_files)}"
        )
        assert changed_files == expected_files, (
            "Artifact membership mismatch: expected "
            f"{sorted(expected_files)}, got {sorted(changed_files)}"
        )
        assert all(path.startswith("tests/") for path in changed_files), (
            f"Expected test-only artifacts, got {sorted(changed_files)}"
        )
        assert all(not path.startswith("src/") for path in changed_files), (
            "Invariant violated: production source path detected in artifacts"
        )

        persisted_changeset = store.get(changeset_id)
        assert persisted_changeset is not None
        persisted_paths = {entry.path for entry in persisted_changeset.entries}
        assert len(persisted_paths) == len(expected_files), (
            "Persisted artifact count mismatch: expected "
            f"{len(expected_files)} files, got {len(persisted_paths)}"
        )
        assert persisted_paths == expected_files, (
            "Persisted artifact membership mismatch: expected "
            f"{sorted(expected_files)}, got {sorted(persisted_paths)}"
        )

        facade = A2aLocalFacade(
            services={"plan_lifecycle_service": lifecycle},
        )
        artifacts_response = facade.dispatch(
            A2aRequest(
                method="_cleveragents/plan/artifacts",
                params={"plan_id": plan_id},
            )
        )
        assert artifacts_response.result is not None, artifacts_response.error
        response_data = artifacts_response.result
        assert response_data.get("plan_id") == plan_id
        assert response_data.get("changeset_id") == changeset_id
        sandbox_refs = response_data.get("sandbox_refs")
        assert isinstance(sandbox_refs, list)
        assert sandbox_refs, "Expected non-empty sandbox refs in artifacts response"

    print("wf02-mocked-generation-artifacts-ok")
