from unittest.mock import Mock, patch

from fastapi import HTTPException


class DummySignals:
    def __init__(self, available=True, metrics_initialized=True):
        self._available = available
        self._metrics_initialized = metrics_initialized
        self.update_metrics = Mock()

    def is_available(self):
        return self._available


class TestTelemetryEndpoints:
    def test_emit_flaky_run_event(self, client, auth_header):
        signals = DummySignals(available=True, metrics_initialized=False)

        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("autodebug.otel.flakiness.get_flakiness_signals", return_value=signals), \
             patch("autodebug.otel.flakiness.record_flaky_run") as mock_flaky_run, \
             patch("autodebug.otel.flakiness.record_flake_fixed") as mock_flake_fixed:
            response = client.post(
                "/v1/otel/flakiness/event",
                headers=auth_header,
                json={
                    "event_type": "flaky_run",
                    "test_name": "tests/test_api.py::test_x",
                    "test_file": "tests/test_api.py",
                    "project_id": "proj_1",
                    "run_id": "run_1",
                    "category": "ordering",
                    "root_cause": "race",
                    "attributes": {"request_id": "req_1"},
                },
            )

        assert response.status_code == 200
        data = response.json()
        assert data["event_recorded"] is True
        assert data["otel_available"] is True
        assert data["metrics_available"] is False

        mock_flaky_run.assert_called_once()
        call_kwargs = mock_flaky_run.call_args.kwargs
        assert call_kwargs["test_name"] == "tests/test_api.py::test_x"
        assert call_kwargs["request_id"] == "req_1"
        mock_flake_fixed.assert_not_called()

    def test_emit_flake_fixed_event_uses_default_fix_type(self, client, auth_header):
        signals = DummySignals()

        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("autodebug.otel.flakiness.get_flakiness_signals", return_value=signals), \
             patch("autodebug.otel.flakiness.record_flake_fixed") as mock_flake_fixed:
            response = client.post(
                "/v1/otel/flakiness/event",
                headers=auth_header,
                json={
                    "event_type": "flake_fixed",
                    "test_name": "tests/test_api.py::test_x",
                    "project_id": "proj_1",
                },
            )

        assert response.status_code == 200
        mock_flake_fixed.assert_called_once_with(
            test_name="tests/test_api.py::test_x",
            project_id="proj_1",
            fix_type="manual",
            time_to_fix_seconds=None,
        )

    def test_ingest_turn_outcome_increments_metrics(self, client, auth_header):
        with patch("api.metrics.inc") as mock_inc:
            response = client.post(
                "/v1/telemetry/turn_outcome",
                headers=auth_header,
                json={
                    "outcome": "BLOCKED",
                    "reason_code": "blocked_tool_failure",
                    "reason_detail": "session_timeout",
                    "turn_id": "turn_1",
                },
            )

        assert response.status_code == 200
        assert response.json() == {"status": "ok"}
        
        # Verify metric increments
        assert mock_inc.call_count == 2
        mock_inc.assert_any_call("autonomous.blocked_tool_failures")
        mock_inc.assert_any_call("autonomous.policy.timeout")

    def test_emit_event_unknown_type_returns_400(self, client, auth_header):
        signals = DummySignals()

        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("autodebug.otel.flakiness.get_flakiness_signals", return_value=signals):
            response = client.post(
                "/v1/otel/flakiness/event",
                headers=auth_header,
                json={
                    "event_type": "unknown_event",
                    "test_name": "tests/test_api.py::test_x",
                },
            )

        assert response.status_code == 400
        assert "Unknown event type" in response.json()["detail"]

    def test_update_metrics_calls_signal_exporter(self, client, auth_header):
        signals = DummySignals(available=False, metrics_initialized=True)

        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("autodebug.otel.flakiness.get_flakiness_signals", return_value=signals):
            response = client.post(
                "/v1/otel/flakiness/metrics",
                headers=auth_header,
                json={
                    "project_id": "proj_1",
                    "flake_rate": 0.25,
                    "mttf_seconds": 12.5,
                    "stability_score": 92.0,
                },
            )

        assert response.status_code == 200
        signals.update_metrics.assert_called_once_with(
            flake_rate=0.25,
            mttf_seconds=12.5,
            stability_score=92.0,
            project_id="proj_1",
        )

    def test_status_returns_signal_availability(self, client, auth_header):
        signals = DummySignals(available=True, metrics_initialized=True)

        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("autodebug.otel.flakiness.get_flakiness_signals", return_value=signals):
            response = client.get("/v1/otel/status", headers=auth_header)

        assert response.status_code == 200
        assert response.json() == {
            "otel_available": True,
            "metrics_available": True,
            "event_recorded": False,
        }

    def test_auth_error_is_propagated(self, client, auth_header):
        with patch(
            "api.routers.telemetry.get_current_user",
            side_effect=HTTPException(status_code=401, detail="Invalid auth"),
        ):
            response = client.get("/v1/otel/status", headers=auth_header)

        assert response.status_code == 401
        assert response.json()["detail"] == "Invalid auth"

    def test_scan_reliability_metrics_rollup(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch(
                 "api.metrics.snapshot",
                 return_value={
                     "scan.codebase.runs": 10,
                     "scan.api.prod_errors.failed": 2,
                     "scan.api.issue_groups.failed": 1,
                     "scan.api.sessions.failed": 3,
                     "scan.api.http_status_error": 8,
                     "scan.api.request_error": 2,
                     "scan.semgrep.findings": 5,
                     "scan.semgrep.partial_error": 1,
                     "scan.semgrep.error": 2,
                     "scan.semgrep.timeout": 1,
                     "scan.semgrep.no_findings": 4,
                     "scan.fidelity.full": 4,
                     "scan.fidelity.degraded": 6,
                     "scan.confidence.high": 4,
                     "scan.confidence.low": 6,
                     "scan.guard.degraded_applied": 6,
                     "scan.guard.evidence_demoted": 1,
                 },
             ):
            response = client.get("/v1/otel/scan/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["total_runs"] == 10
        assert data["production_errors_failed"] == 2
        assert data["issue_groups_failed"] == 1
        assert data["sessions_failed"] == 3
        assert data["fidelity_full_runs"] == 4
        assert data["fidelity_degraded_runs"] == 6
        assert data["confidence_high_runs"] == 4
        assert data["confidence_low_runs"] == 6
        assert data["degraded_guard_applied_runs"] == 6
        assert data["evidence_demoted_runs"] == 1
        assert data["api_failures_per_run"] == 0.6
        assert data["semgrep_error_rate_per_run"] == 0.4
        assert data["fidelity_full_rate"] == 0.4
        assert data["confidence_high_rate"] == 0.4
        assert data["degraded_guard_application_rate"] == 0.6
        assert data["evidence_demotion_rate"] == 0.1

    def test_scan_reliability_metrics_zero_runs_safe_division(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("api.metrics.snapshot", return_value={}):
            response = client.get("/v1/otel/scan/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["total_runs"] == 0
        assert data["fidelity_full_runs"] == 0
        assert data["fidelity_degraded_runs"] == 0
        assert data["degraded_guard_applied_runs"] == 0
        assert data["api_failures_per_run"] == 0.0
        assert data["semgrep_error_rate_per_run"] == 0.0
        assert data["fidelity_full_rate"] == 0.0
        assert data["confidence_high_rate"] == 0.0
        assert data["degraded_guard_application_rate"] == 0.0
        assert data["evidence_demotion_rate"] == 0.0

    def test_agent_reliability_metrics_rollup(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch(
                 "api.metrics.snapshot",
                 return_value={
                     "autonomous.runs": 20,
                     "autonomous.completions": 12,
                     "autonomous.give_up": 5,
                     "autonomous.policy.completion_evidence_blocked": 3,
                     "autonomous.policy.completion_grounding_blocked": 2,
                     "autonomous.policy.tool_dependency_blocked": 4,
                     "autonomous.policy.low_signal_backtrack": 1,
                     "autonomous.low_signal.early_stop": 2,
                 },
             ):
            response = client.get("/v1/otel/agent/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["total_runs"] == 20
        assert data["completion_rate"] == 0.6
        assert data["give_up_rate"] == 0.25
        assert data["policy_block_rate_per_run"] == 0.5
        assert data["low_signal_early_stop"] == 2

    def test_agent_reliability_metrics_zero_runs_safe_division(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("api.metrics.snapshot", return_value={}):
            response = client.get("/v1/otel/agent/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["total_runs"] == 0
        assert data["completion_rate"] == 0.0
        assert data["policy_block_rate_per_run"] == 0.0

    def test_replay_reliability_metrics_rollup(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch(
                 "api.metrics.snapshot",
                 return_value={
                     "replay.diff.runs": 12,
                     "replay.diff.divergent": 5,
                     "replay.diff.identical": 7,
                     "replay.diff.first_type.http": 2,
                     "replay.diff.first_type.sql": 1,
                 },
             ):
            response = client.get("/v1/otel/replay/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["total_diff_runs"] == 12
        assert data["divergent_runs"] == 5
        assert data["identical_runs"] == 7
        assert data["first_type_http"] == 2
        assert data["first_type_sql"] == 1
        assert data["first_type_other"] == 2
        assert data["divergence_rate"] == 5 / 12

    def test_replay_reliability_metrics_zero_runs_safe_division(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("api.metrics.snapshot", return_value={}):
            response = client.get("/v1/otel/replay/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["total_diff_runs"] == 0
        assert data["divergence_rate"] == 0.0

    def test_initiative_reliability_metrics_rollup(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch(
                 "api.metrics.snapshot",
                 return_value={
                     "scan.codebase.runs": 10,
                     "scan.api.prod_errors.failed": 2,
                     "scan.api.issue_groups.failed": 1,
                     "scan.api.sessions.failed": 3,
                     "scan.api.http_status_error": 8,
                     "scan.api.request_error": 2,
                     "scan.semgrep.findings": 5,
                     "scan.semgrep.partial_error": 1,
                     "scan.semgrep.error": 2,
                     "scan.semgrep.timeout": 1,
                     "scan.semgrep.no_findings": 4,
                     "scan.fidelity.full": 4,
                     "scan.fidelity.degraded": 6,
                     "scan.confidence.high": 4,
                     "scan.confidence.low": 6,
                     "scan.guard.degraded_applied": 6,
                     "scan.guard.evidence_demoted": 1,
                     "autonomous.runs": 20,
                     "autonomous.completions": 12,
                     "autonomous.give_up": 5,
                     "autonomous.policy.completion_evidence_blocked": 3,
                     "autonomous.policy.completion_grounding_blocked": 2,
                     "autonomous.policy.tool_dependency_blocked": 4,
                     "autonomous.policy.low_signal_backtrack": 1,
                     "autonomous.low_signal.early_stop": 2,
                     "replay.diff.runs": 12,
                     "replay.diff.divergent": 5,
                     "replay.diff.identical": 7,
                     "replay.diff.first_type.http": 2,
                     "replay.diff.first_type.sql": 1,
                 },
             ):
            response = client.get("/v1/otel/initiative/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["has_scan_signal"] is True
        assert data["has_agent_signal"] is True
        assert data["has_replay_signal"] is True
        assert data["scan"]["api_failures_per_run"] == 0.6
        assert data["scan"]["fidelity_degraded_runs"] == 6
        assert data["scan"]["fidelity_full_rate"] == 0.4
        assert data["agent"]["completion_rate"] == 0.6
        assert data["replay"]["divergence_rate"] == 5 / 12

    def test_initiative_reliability_metrics_zero_signal(self, client, auth_header):
        with patch("api.routers.telemetry.get_current_user", return_value={"sub": "u"}), \
             patch("api.metrics.snapshot", return_value={}):
            response = client.get("/v1/otel/initiative/reliability", headers=auth_header)

        assert response.status_code == 200
        data = response.json()
        assert data["has_scan_signal"] is False
        assert data["has_agent_signal"] is False
        assert data["has_replay_signal"] is False
        assert data["scan"]["total_runs"] == 0
        assert data["agent"]["total_runs"] == 0
        assert data["replay"]["total_diff_runs"] == 0
