import os
import tempfile
import time
import unittest
from pathlib import Path
import uuid

from hiveos import db as db_module
from hiveos.db import init_db
from hiveos.intelligence.ai_agent import AIAgent
from hiveos.intelligence import tracing as tracing_module
from hiveos.runtime.states import TaskStatus, ZoneStallState
from hiveos.sim_agent import SimAgent
from hiveos.core import Core


class RuntimeIntegrationBase(unittest.TestCase):
    def setUp(self):
        self._original_trace_db_async = tracing_module.TRACE_DB_ASYNC
        tracing_module.TRACE_DB_ASYNC = False
        self._db_path = Path(tempfile.gettempdir()) / f"hive-test-{uuid.uuid4().hex}.db"
        db_module.DB_PATH = str(self._db_path)
        init_db()
        self._cores = []

    def tearDown(self):
        for core in self._cores:
            try:
                core.teardown()
            except Exception:
                pass
        tracing_module.TRACE_DB_ASYNC = self._original_trace_db_async
        try:
            if self._db_path.exists():
                self._db_path.unlink()
        except Exception:
            pass

    def make_core(self):
        core = Core(session_id="test-session")
        self._cores.append(core)
        return core


class TaskIngestionValidationTests(RuntimeIntegrationBase):
    def test_rejects_cyclic_dependencies(self):
        core = self.make_core()
        with self.assertRaises(ValueError):
            core.create_task(
                {
                    "task_name": "cycle",
                    "execution_mode": "concurrent",
                    "chunks": [
                        {"chunk_id": "A", "depends_on": "B", "required_capability": "x", "payload": {}},
                        {"chunk_id": "B", "depends_on": "A", "required_capability": "x", "payload": {}},
                    ],
                }
            )

    def test_rejects_unknown_dependency(self):
        core = self.make_core()
        with self.assertRaises(ValueError):
            core.create_task(
                {
                    "task_name": "unknown-dep",
                    "execution_mode": "concurrent",
                    "chunks": [
                        {"chunk_id": "A", "depends_on": "MISSING", "required_capability": "x", "payload": {}},
                    ],
                }
            )


class WatchdogTransitionTests(RuntimeIntegrationBase):
    def test_zone_blocks_when_no_progress(self):
        core = self.make_core()
        core.execution_hardening["stall_warn_ticks"] = 2
        core.execution_hardening["stall_fail_ticks"] = 0
        task_id = core.ingest_intent(
            {
                "task_name": "blocked-task",
                "execution_mode": "concurrent",
                "chunks": [{"chunk_id": "c1", "required_capability": "missing.cap", "payload": {}, "ttl": 1}],
            }
        )
        core.tick(verbose=True)
        core.tick(verbose=True)

        task = core.task_objects[task_id]
        zone = next(iter(core.zones.values()))
        self.assertEqual(TaskStatus.BLOCKED, task.status)
        self.assertEqual(ZoneStallState.BLOCKED, zone.stall_state)

    def test_zone_recovers_after_capability_injected(self):
        core = self.make_core()
        core.execution_hardening["stall_warn_ticks"] = 2
        core.execution_hardening["stall_fail_ticks"] = 0
        task_id = core.ingest_intent(
            {
                "task_name": "recover-task",
                "execution_mode": "concurrent",
                "chunks": [{"chunk_id": "c1", "required_capability": "recover.cap", "payload": {}, "ttl": 1}],
            }
        )
        core.tick(verbose=True)
        core.tick(verbose=True)
        self.assertEqual(TaskStatus.BLOCKED, core.task_objects[task_id].status)

        core.inject_agent(SimAgent(capabilities=["recover.cap"]))
        for _ in range(4):
            core.tick(verbose=True)

        self.assertEqual(TaskStatus.COMPLETED, core.task_objects[task_id].status)

    def test_zone_force_fail_marks_task_failed(self):
        core = self.make_core()
        core.execution_hardening["stall_warn_ticks"] = 1
        core.execution_hardening["stall_fail_ticks"] = 2
        task_id = core.ingest_intent(
            {
                "task_name": "fail-task",
                "execution_mode": "concurrent",
                "chunks": [{"chunk_id": "c1", "required_capability": "never.exists", "payload": {}, "ttl": 1}],
            }
        )
        core.tick(verbose=True)
        core.tick(verbose=True)

        task = core.task_objects[task_id]
        self.assertEqual(TaskStatus.FAILED, task.status)
        self.assertEqual(0, len(core.zones))


class GovernedPathTests(RuntimeIntegrationBase):
    def test_tool_registry_authorize_and_execute_memory_put(self):
        core = self.make_core()
        ai = AIAgent(capabilities=["ai"])
        core.inject_agent(ai)

        request = {
            "request_id": "req-1",
            "run_id": "run-1",
            "agent_id": ai.agent_id,
            "tool_id": "memory.put",
            "action": "write",
            "args": {"scope": "session", "key": "k", "value": {"v": 1}},
            "requested_scope": "session",
            "budget_cost": 0,
            "timestamp": "2026-01-01T00:00:00+00:00",
        }
        decision = core.tool_registry.authorize(ai, request)
        self.assertTrue(decision["allowed"])
        result = core.tool_registry.execute(request, context={"agent": ai, "run_id": "run-1"})
        self.assertEqual("session", result["scope"])
        self.assertEqual("k", result["key"])

    def test_proposal_validate_and_inject_child_task(self):
        core = self.make_core()
        parent_task_id = core.ingest_intent(
            {
                "task_name": "parent",
                "execution_mode": "concurrent",
                "capability_required": ["compute.generic"],
                "chunks": [{"chunk_id": "p1", "required_capability": "compute.generic", "payload": {}}],
            }
        )

        created = core.proposal_manager.create(
            agent_id="ai-1",
            run_id="run-proposal",
            task_id=parent_task_id,
            chunk_id=f"p1_{parent_task_id}",
            proposal_content={
                "artifact_type": "task_draft",
                "title": "child",
                "summary": "child summary",
                "body": {
                    "parent_task_id": parent_task_id,
                    "depth": 1,
                    "task_intent": {
                        "task_name": "child",
                        "execution_mode": "concurrent",
                        "capability_required": ["compute.generic"],
                        "chunks": [
                            {"chunk_id": "c1", "required_capability": "compute.generic", "payload": {}}
                        ],
                    },
                },
            },
        )
        validation = core.proposal_manager.validate(created["proposal_id"])
        self.assertTrue(validation["ok"])
        injected = core.proposal_manager.inject_validated(created["proposal_id"], core)
        self.assertIsNotNone(injected.get("child_task_id"))
        self.assertIn(injected["child_task_id"], core.task_objects)

    def test_sandbox_start_and_complete(self):
        core = self.make_core()
        result = core.tool_service.sandbox_start_run(
            {"objective": "test objective", "constraints": {"max_runtime_seconds": 2}},
            context={},
        )
        self.assertEqual("queued", result["state"])
        sandbox_run_id = result["sandbox_run_id"]

        deadline = time.time() + 5.0
        state = None
        while time.time() < deadline:
            core.sandbox_runtime.process_next()
            observed = core.sandbox_runtime.get_result(sandbox_run_id)
            state = (observed or {}).get("state")
            if state in {"completed", "failed", "timeout"}:
                break
            time.sleep(0.05)
        self.assertEqual("completed", state)

    def test_proposal_archive_single(self):
        core = self.make_core()
        created = core.proposal_manager.create(
            agent_id="ai-1",
            run_id="run-archive-one",
            task_id=1,
            chunk_id="chunk-a",
            proposal_content={
                "artifact_type": "task_draft",
                "title": "archive me",
                "body": {"task_intent": {"chunks": [{}]}},
            },
        )
        archived = core.proposal_manager.archive(created["proposal_id"], archive_source="test")
        self.assertIsNotNone(archived)
        self.assertEqual("archived", archived.get("status"))
        self.assertTrue(bool((archived.get("archive") or {}).get("archived")))

    def test_proposal_archive_older_than_filters_status(self):
        core = self.make_core()
        old_rejected = core.proposal_manager.create(
            agent_id="ai-1",
            run_id="run-archive-bulk",
            task_id=1,
            chunk_id="chunk-r",
            proposal_content={
                "artifact_type": "task_draft",
                "title": "reject me",
                "body": {"task_intent": {"chunks": [{}]}},
            },
        )
        core.proposal_manager.validate(old_rejected["proposal_id"])
        with core.proposal_manager._lock:
            core.proposal_manager._proposals[old_rejected["proposal_id"]]["created_at"] = "2024-01-01T00:00:00+00:00"
            core.proposal_manager._proposals[old_rejected["proposal_id"]]["validated_at"] = "2024-01-01T00:00:00+00:00"
            core.proposal_manager._proposals[old_rejected["proposal_id"]]["status"] = "rejected"

        old_validated = core.proposal_manager.create(
            agent_id="ai-1",
            run_id="run-archive-bulk",
            task_id=1,
            chunk_id="chunk-v",
            proposal_content={
                "artifact_type": "task_draft",
                "title": "keep validated",
                "body": {"task_intent": {"chunks": [{"chunk_id": "c1"}]}},
            },
        )
        core.proposal_manager.validate(old_validated["proposal_id"])
        with core.proposal_manager._lock:
            core.proposal_manager._proposals[old_validated["proposal_id"]]["created_at"] = "2024-01-01T00:00:00+00:00"
            core.proposal_manager._proposals[old_validated["proposal_id"]]["validated_at"] = "2024-01-01T00:00:00+00:00"

        archived_ids = core.proposal_manager.archive_older_than(
            older_than_seconds=60,
            statuses=["rejected"],
            archive_source="test",
        )
        self.assertIn(old_rejected["proposal_id"], archived_ids)
        self.assertNotIn(old_validated["proposal_id"], archived_ids)
        self.assertEqual("archived", core.proposal_manager.get(old_rejected["proposal_id"])["status"])
        self.assertEqual("validated", core.proposal_manager.get(old_validated["proposal_id"])["status"])


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