from __future__ import annotations

import unittest
from unittest.mock import patch

from fastapi.testclient import TestClient

from backend.lean_checker import LeanCheckResult
from backend.main import app


class ConjectureApiTests(unittest.TestCase):
    def setUp(self) -> None:
        self.client = TestClient(app)

    def test_health_endpoint(self) -> None:
        response = self.client.get("/health")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {"ok": True})

    @patch("backend.main.supabase_is_configured", return_value=False)
    def test_ready_endpoint_reports_supabase_state(self, _mock_configured) -> None:
        response = self.client.get("/ready")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.json(),
            {
                "ok": False,
                "supabase_configured": False,
                "research_worker_running": False,
            },
        )

    @patch("backend.main.store_verified_lemma", return_value="doc-123")
    @patch("backend.main.check_conjecture")
    def test_successful_conjecture_is_stored(self, mock_check, mock_store) -> None:
        mock_check.return_value = LeanCheckResult(
            conjecture="theorem t : True := by\n  trivial",
            normalized_code="theorem t : True := by\n  trivial",
            is_true=True,
            verification_status="verified",
            verification_summary="Lean accepted the proof.",
            compiler="lean",
            exit_code=0,
            stdout="",
            stderr="",
        )

        response = self.client.post(
            "/check-conjecture",
            json={
                "conjecture": "theorem t : True := by\n  trivial",
                "source_task_id": "task-1",
                "source_prompt": "prove True",
                "metadata": {"origin": "test"},
            },
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.json(),
            {
                "is_true": True,
                "verification_status": "verified",
                "verification_summary": "Lean accepted the proof.",
                "normalized_code": "theorem t : True := by\n  trivial",
                "stored": True,
                "document_id": "doc-123",
                "compiler": "lean",
                "stdout": "",
                "stderr": "",
                "storage_error": None,
            },
        )
        mock_store.assert_called_once()

    @patch("backend.main.store_verified_lemma")
    @patch("backend.main.check_conjecture")
    def test_false_conjecture_is_not_stored(self, mock_check, mock_store) -> None:
        mock_check.return_value = LeanCheckResult(
            conjecture="theorem bad : True := by\n  exact False.elim ?h",
            normalized_code="theorem bad : True := by\n  exact False.elim ?h",
            is_true=False,
            verification_status="typecheck_error",
            verification_summary="Lean rejected the proof during type checking.",
            compiler="lean",
            exit_code=1,
            stdout="",
            stderr="type mismatch",
        )

        response = self.client.post(
            "/check-conjecture",
            json={"conjecture": "theorem bad : True := by\n  exact False.elim ?h"},
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["is_true"], False)
        self.assertEqual(response.json()["verification_status"], "typecheck_error")
        self.assertEqual(response.json()["stored"], False)
        mock_store.assert_not_called()

    @patch("backend.main.store_verified_lemma", side_effect=RuntimeError("supabase down"))
    @patch("backend.main.check_conjecture")
    def test_storage_failure_still_returns_true(self, mock_check, _mock_store) -> None:
        mock_check.return_value = LeanCheckResult(
            conjecture="theorem t : True := by\n  trivial",
            normalized_code="theorem t : True := by\n  trivial",
            is_true=True,
            verification_status="verified",
            verification_summary="Lean accepted the proof.",
            compiler="lean",
            exit_code=0,
            stdout="",
            stderr="",
        )

        response = self.client.post(
            "/check-conjecture",
            json={"conjecture": "theorem t : True := by\n  trivial"},
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["is_true"], True)
        self.assertEqual(response.json()["stored"], False)
        self.assertEqual(response.json()["storage_error"], "supabase down")

    def test_empty_conjecture_is_rejected(self) -> None:
        response = self.client.post("/check-conjecture", json={"conjecture": ""})
        self.assertEqual(response.status_code, 422)

    @patch("backend.main.submit_research_job", return_value="job-123")
    def test_research_job_submission(self, mock_submit) -> None:
        response = self.client.post(
            "/research-jobs",
            json={
                "requester_node_id": "node-1",
                "requester_secret_hash": "secret-hash",
                "domain": "naturals",
                "model": "test-model",
                "total_budget": 1.5,
                "budget_per_attempt": 0.25,
                "max_concurrent_episodes": 2,
                "max_attempts_per_episode": 3,
                "seed_conjecture": "Prove n = n",
                "objective": "autonomous_research",
                "metadata": {"origin": "test"},
            },
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(), {"job_id": "job-123", "status": "active"})
        mock_submit.assert_called_once()

    @patch("backend.main.submit_research_job", side_effect=RuntimeError("missing Supabase config"))
    def test_research_job_submission_runtime_error_returns_503(self, _mock_submit) -> None:
        response = self.client.post(
            "/research-jobs",
            json={
                "requester_node_id": "node-1",
                "requester_secret_hash": "secret-hash",
                "domain": "naturals",
                "model": "test-model",
                "total_budget": 1.5,
                "budget_per_attempt": 0.25,
                "max_concurrent_episodes": 2,
                "max_attempts_per_episode": 3,
                "seed_conjecture": "Prove n = n",
                "objective": "autonomous_research",
                "metadata": {"origin": "test"},
            },
        )
        self.assertEqual(response.status_code, 503)
        self.assertEqual(response.json(), {"detail": "missing Supabase config"})

    @patch("backend.main.get_research_job", return_value={"id": "job-123", "status": "active", "episodes": []})
    def test_get_research_job_detail(self, mock_get) -> None:
        response = self.client.get(
            "/research-jobs/job-123",
            params={
                "requester_node_id": "node-1",
                "requester_secret_hash": "secret-hash",
            },
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["id"], "job-123")
        mock_get.assert_called_once()

    @patch("backend.main.get_research_job", side_effect=RuntimeError("missing Supabase config"))
    def test_get_research_job_detail_runtime_error_returns_503(self, _mock_get) -> None:
        response = self.client.get(
            "/research-jobs/job-123",
            params={
                "requester_node_id": "node-1",
                "requester_secret_hash": "secret-hash",
            },
        )
        self.assertEqual(response.status_code, 503)
        self.assertEqual(response.json(), {"detail": "missing Supabase config"})


if __name__ == "__main__":
    unittest.main()
