import tempfile
import unittest
from pathlib import Path
import uuid

import hiveos.config as config_module
from hiveos import db as db_module
from hiveos.config import USER_CAPABILITIES_HEADER
from hiveos.db import init_db
from hiveos.services import terminal_service as terminal_service_module
from hiveos.intelligence import tracing as tracing_module
from hiveos.session_manager import session_manager
from web_interface import app


class TerminalRuntimeApiTests(unittest.TestCase):
    def setUp(self):
        self._original_trace_db_async = tracing_module.TRACE_DB_ASYNC
        self._original_trace_sampling_enabled = tracing_module.TRACE_SAMPLING_ENABLED
        self._original_trace_log_path = tracing_module.TRACE_LOG_PATH

        self._db_path = Path(tempfile.gettempdir()) / f"hive-test-{uuid.uuid4().hex}.db"
        db_module.DB_PATH = str(self._db_path)
        init_db()

        self._trace_log_path = str(Path(tempfile.gettempdir()) / f"hive-trace-{uuid.uuid4().hex}.log")
        tracing_module.TRACE_LOG_PATH = self._trace_log_path
        tracing_module.TRACE_DB_ASYNC = False
        tracing_module.TRACE_SAMPLING_ENABLED = False

        self._cleanup_sessions()
        self.client = app.test_client()

    def tearDown(self):
        self._cleanup_sessions()
        tracing_module.TRACE_DB_ASYNC = self._original_trace_db_async
        tracing_module.TRACE_SAMPLING_ENABLED = self._original_trace_sampling_enabled
        tracing_module.TRACE_LOG_PATH = self._original_trace_log_path
        try:
            if self._db_path.exists():
                self._db_path.unlink()
        except Exception:
            pass
        self._cleanup_trace_files()

    def _cleanup_sessions(self):
        session_ids = list(session_manager.sessions.keys())
        for session_id in session_ids:
            try:
                session_manager.delete_session(session_id)
            except Exception:
                pass

    def _cleanup_trace_files(self):
        base = Path(self._trace_log_path)
        for path in [base] + list(base.parent.glob(f"{base.name}.*")):
            try:
                if path.exists():
                    path.unlink()
            except Exception:
                pass

    def _headers(self, user_id, session_id=None):
        headers = {
            "X-User-ID": user_id,
            USER_CAPABILITIES_HEADER: "admin",
        }
        if session_id:
            headers["X-Session-ID"] = session_id
        return headers

    def _create_session(self, user_id):
        response = self.client.post(
            "/create_session",
            json={"label": "terminal-test", "user_id": user_id},
            headers=self._headers(user_id),
        )
        self.assertEqual(200, response.status_code)
        return response.get_json()["session_id"]

    def _open_terminal(self, user_id, session_id, target_id="term-1"):
        response = self.client.post(
            "/terminal_open",
            json={
                "target_id": target_id,
                "target_type": "software.pty",
                "transport": {"kind": "pty", "ref": "local"},
                "meta": {"label": "Demo Terminal"},
            },
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, response.status_code)
        return response.get_json()["target"]["target_id"]

    def _find_latest_event(self, event_type):
        events = tracing_module.read_trace_events(limit=500, filters={"event_type": event_type})
        if not events:
            return None
        return events[-1]

    def test_terminal_exec_emits_command_events(self):
        user_id = "terminal-user-events"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id)

        response = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo hello-terminal"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, response.status_code)
        body = response.get_json()
        self.assertEqual(0, body["exit_code"])
        self.assertIn("hello-terminal", body["raw_output"])

        applied = self._find_latest_event("terminal_command_applied")
        self.assertIsNotNone(applied)
        self.assertEqual(target_id, (applied.get("payload") or {}).get("target_id"))

        output = self._find_latest_event("terminal_output")
        self.assertIsNotNone(output)
        self.assertEqual(False, (output.get("payload") or {}).get("derived"))
        self.assertTrue(int((output.get("payload") or {}).get("source_buffer_version") or 0) >= 1)

    def test_terminal_transform_emits_transform_trace(self):
        user_id = "terminal-user-transform"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id)

        set_transform = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "italian"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, set_transform.status_code)

        response = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo ciao"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, response.status_code)
        transformed = response.get_json()["transformed_outputs"]
        self.assertTrue(any(item.get("module") == "localize" for item in transformed))

        output_events = tracing_module.read_trace_events(limit=500, filters={"event_type": "terminal_output"})
        derived_for_target = [
            event
            for event in output_events
            if (event.get("payload") or {}).get("target_id") == target_id and (event.get("payload") or {}).get("derived")
        ]
        self.assertTrue(derived_for_target)
        derived_payload = derived_for_target[-1].get("payload") or {}
        self.assertEqual("localize", derived_payload.get("module"))
        self.assertEqual("italian", derived_payload.get("language"))
        self.assertTrue(int(derived_payload.get("source_line_count") or 0) >= 1)
        self.assertTrue(int(derived_payload.get("derived_line_count") or 0) >= 1)
        self.assertTrue(int(derived_payload.get("source_buffer_version") or 0) >= 1)
        self.assertTrue(str(derived_payload.get("quality_status") or "").strip())
        derived_model = derived_for_target[-1].get("model") or {}
        self.assertTrue(str(derived_model.get("provider") or "").strip())
        self.assertTrue(str(derived_model.get("name") or "").strip())

        transform_event = self._find_latest_event("terminal_transform_applied")
        self.assertIsNotNone(transform_event)
        payload = transform_event.get("payload") or {}
        self.assertEqual("localize", payload.get("module"))
        self.assertEqual(target_id, payload.get("target_id"))
        self.assertTrue(payload.get("source_trace_id"))
        self.assertTrue(payload.get("derived_trace_id"))
        self.assertTrue(int(payload.get("source_buffer_version") or 0) >= 1)
        self.assertTrue(str(payload.get("quality_status") or "").strip())
        transform_model = transform_event.get("model") or {}
        self.assertTrue(str(transform_model.get("provider") or "").strip())
        self.assertTrue(str(transform_model.get("name") or "").strip())

        targets_resp = self.client.get("/terminal_targets", headers=self._headers(user_id, session_id))
        self.assertEqual(200, targets_resp.status_code)
        targets = targets_resp.get_json().get("targets", [])
        session_target = next((item for item in targets if item.get("target_id") == target_id), {})
        self.assertTrue(int(session_target.get("buffer_version") or 0) >= 1)
        derived_views = list(session_target.get("derived_views") or [])
        self.assertTrue(any(view.get("module") == "localize" for view in derived_views))

    def test_terminal_open_reuse_preserves_recent_output_lines(self):
        user_id = "terminal-user-reuse"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        first_exec = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo first-line"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, first_exec.status_code)

        second_exec = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo second-line"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, second_exec.status_code)

        list_before = self.client.get("/terminal_targets", headers=self._headers(user_id, session_id))
        self.assertEqual(200, list_before.status_code)
        targets_before = list_before.get_json().get("targets", [])
        existing_before = next((item for item in targets_before if item.get("target_id") == target_id), {})
        lines_before = list(existing_before.get("recent_output_lines") or [])
        self.assertTrue(any("first-line" in line for line in lines_before))
        self.assertTrue(any("second-line" in line for line in lines_before))

        reopen = self.client.post(
            "/terminal_open",
            json={
                "target_id": target_id,
                "target_type": "software.pty",
                "transport": {"kind": "pty", "ref": "local"},
                "meta": {"label": "Session Terminal"},
            },
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, reopen.status_code)
        reopened_target = reopen.get_json().get("target", {})
        reopened_lines = list(reopened_target.get("recent_output_lines") or [])
        self.assertTrue(any("first-line" in line for line in reopened_lines))
        self.assertTrue(any("second-line" in line for line in reopened_lines))

    def test_transform_apply_uses_full_buffer_snapshot(self):
        user_id = "terminal-user-full-buffer"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        for cmd in ("echo alpha-line", "echo beta-line"):
            response = self.client.post(
                "/terminal_exec",
                json={"target_id": target_id, "command": cmd},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, response.status_code)

        set_transform = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "french"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, set_transform.status_code)

        derived_events = tracing_module.read_trace_events(limit=500, filters={"event_type": "terminal_output"})
        derived_events = [event for event in derived_events if (event.get("payload") or {}).get("derived")]
        self.assertTrue(derived_events)
        latest = derived_events[-1]
        payload = latest.get("payload") or {}
        self.assertEqual(target_id, payload.get("target_id"))
        output = str(payload.get("output") or "")
        self.assertIn("alpha-line", output)
        self.assertIn("beta-line", output)

    def test_terminal_clear_empties_backend_buffer(self):
        user_id = "terminal-user-clear"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        response = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo clear-me"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, response.status_code)

        cleared = self.client.post(
            "/terminal_clear",
            json={"target_id": target_id},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, cleared.status_code)
        self.assertTrue(cleared.get_json().get("cleared"))

        list_after = self.client.get("/terminal_targets", headers=self._headers(user_id, session_id))
        self.assertEqual(200, list_after.status_code)
        targets_after = list_after.get_json().get("targets", [])
        existing_after = next((item for item in targets_after if item.get("target_id") == target_id), {})
        self.assertEqual([], list(existing_after.get("recent_output_lines") or []))

    def test_trace_events_route_filters_by_model_and_quality(self):
        user_id = "terminal-user-trace-filters"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        set_transform = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "spanish"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, set_transform.status_code)

        exec_resp = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo hola"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, exec_resp.status_code)

        output_events = tracing_module.read_trace_events(limit=500, filters={"event_type": "terminal_output"})
        derived_for_target = [
            event
            for event in output_events
            if (event.get("payload") or {}).get("target_id") == target_id and (event.get("payload") or {}).get("derived")
        ]
        self.assertTrue(derived_for_target)
        latest = derived_for_target[-1]
        payload = latest.get("payload") or {}
        model = latest.get("model") or {}
        quality_status = str(payload.get("quality_status") or "")
        model_provider = str(model.get("provider") or "")
        model_name = str(model.get("name") or "")
        self.assertTrue(quality_status)
        self.assertTrue(model_provider)
        self.assertTrue(model_name)

        trace_resp = self.client.get(
            "/trace_events",
            query_string={
                "event_type": "terminal_output",
                "model_provider": model_provider,
                "model_name": model_name,
                "quality_status": quality_status,
                "limit": 200,
            },
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, trace_resp.status_code)
        events = list((trace_resp.get_json() or {}).get("events") or [])
        self.assertTrue(events)
        self.assertTrue(any(str(item.get("trace_id") or "") == str(latest.get("trace_id") or "") for item in events))

    def test_model_routing_recommendation_route_returns_artifact(self):
        user_id = "terminal-user-routing"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        set_transform = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "spanish"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, set_transform.status_code)

        for cmd in ("echo uno", "echo dos"):
            exec_resp = self.client.post(
                "/terminal_exec",
                json={"target_id": target_id, "command": cmd},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, exec_resp.status_code)

        route_resp = self.client.get(
            "/model_routing_recommendation",
            query_string={"routes": "localize", "min_samples": 1, "limit": 500},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, route_resp.status_code)
        body = route_resp.get_json() or {}
        recommendations = list(body.get("recommendations") or [])
        self.assertTrue(recommendations)
        entry = recommendations[0]
        self.assertEqual("localize", entry.get("route_class"))
        self.assertTrue(entry.get("winner"))
        self.assertTrue(list(entry.get("candidates") or []))

    def test_model_routing_profile_route_returns_active_profile(self):
        user_id = "terminal-user-routing-profile"
        session_id = self._create_session(user_id)
        profile_resp = self.client.get(
            "/model_routing_profile",
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, profile_resp.status_code)
        payload = profile_resp.get_json() or {}
        self.assertTrue(str(payload.get("default_model") or "").strip())
        routes = dict(payload.get("routes") or {})
        self.assertIn("localize", routes)
        self.assertIn("tutor", routes)
        self.assertIn("hardware_explainer", routes)
        self.assertIn("intent_compile", routes)

    def test_model_routing_profile_apply_supports_review_and_apply(self):
        user_id = "terminal-user-routing-apply"
        session_id = self._create_session(user_id)

        original = config_module.get_ai_route_model_profile()
        try:
            review_resp = self.client.post(
                "/model_routing_profile_apply",
                json={
                    "apply": False,
                    "route_models": {"localize": "review-localize-model"},
                },
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, review_resp.status_code)
            review_body = review_resp.get_json() or {}
            self.assertFalse(bool(review_body.get("applied")))
            self.assertEqual("review-localize-model", ((review_body.get("preview") or {}).get("routes") or {}).get("localize"))
            live_after_review = config_module.get_ai_route_model_profile()
            self.assertEqual((original.get("routes") or {}).get("localize"), (live_after_review.get("routes") or {}).get("localize"))

            apply_resp = self.client.post(
                "/model_routing_profile_apply",
                json={
                    "apply": True,
                    "artifact": {
                        "recommendations": [
                            {"route_class": "localize", "winner": {"provider": "ollama", "name": "winner-localize-model"}}
                        ]
                    },
                },
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, apply_resp.status_code)
            apply_body = apply_resp.get_json() or {}
            self.assertTrue(bool(apply_body.get("applied")))
            after = apply_body.get("after") or {}
            self.assertEqual("winner-localize-model", (after.get("routes") or {}).get("localize"))

            profile_resp = self.client.get("/model_routing_profile", headers=self._headers(user_id, session_id))
            self.assertEqual(200, profile_resp.status_code)
            profile = profile_resp.get_json() or {}
            self.assertEqual("winner-localize-model", (profile.get("routes") or {}).get("localize"))

            reviewed_events = tracing_module.read_trace_events(
                limit=500,
                filters={"event_type": "model_routing_profile_reviewed"},
            )
            applied_events = tracing_module.read_trace_events(
                limit=500,
                filters={"event_type": "model_routing_profile_applied"},
            )
            self.assertTrue(reviewed_events)
            self.assertTrue(applied_events)
        finally:
            config_module.apply_ai_route_model_profile(
                default_model=original.get("default_model"),
                route_models=original.get("routes"),
            )

    def test_language_switch_recomputes_overlay_for_new_language(self):
        user_id = "terminal-user-language-switch"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        exec_resp = self.client.post(
            "/terminal_exec",
            json={"target_id": target_id, "command": "echo alpha"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, exec_resp.status_code)

        italian_resp = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "italian"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, italian_resp.status_code)

        spanish_resp = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "spanish"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, spanish_resp.status_code)

        output_events = tracing_module.read_trace_events(limit=500, filters={"event_type": "terminal_output"})
        derived_for_target = [
            event
            for event in output_events
            if (event.get("payload") or {}).get("target_id") == target_id and (event.get("payload") or {}).get("derived")
        ]
        self.assertTrue(derived_for_target)
        latest = derived_for_target[-1]
        payload = latest.get("payload") or {}
        self.assertEqual("localize", payload.get("module"))
        self.assertEqual("spanish", payload.get("language"))
        self.assertIn("[spanish]", str(payload.get("output") or "").lower())

    def test_transform_merge_avoids_duplicate_reappend_when_delta_overlaps(self):
        user_id = "terminal-user-dedupe"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        set_transform = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "spanish"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, set_transform.status_code)

        original_apply = terminal_service_module._apply_transform
        call_state = {"count": 0}

        def fake_apply_transform(module, raw_output, command, exit_code, config):
            call_state["count"] += 1
            if call_state["count"] == 1:
                return "line-a\nline-b", {"provider": "fake", "name": "fake-model"}, "ok", 1
            return "line-a\nline-b\nline-c", {"provider": "fake", "name": "fake-model"}, "ok", 1

        try:
            terminal_service_module._apply_transform = fake_apply_transform
            first = self.client.post(
                "/terminal_exec",
                json={"target_id": target_id, "command": "echo first"},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, first.status_code)
            second = self.client.post(
                "/terminal_exec",
                json={"target_id": target_id, "command": "echo second"},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, second.status_code)
        finally:
            terminal_service_module._apply_transform = original_apply

        transformed_outputs = list((second.get_json() or {}).get("transformed_outputs") or [])
        localized = next((item for item in transformed_outputs if item.get("module") == "localize"), {})
        localized_lines = str(localized.get("output") or "").splitlines()
        self.assertEqual(["line-a", "line-b", "line-c"], localized_lines[-3:])

    def test_model_routing_artifacts_create_and_fetch(self):
        user_id = "terminal-user-routing-artifacts"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        set_transform = self.client.post(
            "/terminal_transform",
            json={"target_id": target_id, "module": "localize", "config": {"language": "spanish"}},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, set_transform.status_code)
        for cmd in ("echo one", "echo two"):
            exec_resp = self.client.post(
                "/terminal_exec",
                json={"target_id": target_id, "command": cmd},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, exec_resp.status_code)

        create_resp = self.client.post(
            "/model_routing_artifacts",
            json={"routes": ["localize"], "min_samples": 1, "limit": 500},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(201, create_resp.status_code)
        created = create_resp.get_json() or {}
        artifact_id = str(created.get("artifact_id") or "")
        self.assertTrue(artifact_id)
        self.assertEqual("localize", (((created.get("artifact") or {}).get("recommendations") or [{}])[0]).get("route_class"))

        list_resp = self.client.get(
            "/model_routing_artifacts",
            query_string={"limit": 10},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, list_resp.status_code)
        listed = list((list_resp.get_json() or {}).get("artifacts") or [])
        self.assertTrue(any(str(item.get("artifact_id") or "") == artifact_id for item in listed))

        get_resp = self.client.get(
            "/model_routing_artifacts",
            query_string={"artifact_id": artifact_id},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(200, get_resp.status_code)
        fetched = get_resp.get_json() or {}
        self.assertEqual(artifact_id, str(fetched.get("artifact_id") or ""))

    def test_model_routing_profile_apply_supports_artifact_id(self):
        user_id = "terminal-user-routing-apply-artifact-id"
        session_id = self._create_session(user_id)
        target_id = self._open_terminal(user_id, session_id, target_id="session-terminal")

        original = config_module.get_ai_route_model_profile()
        try:
            set_transform = self.client.post(
                "/terminal_transform",
                json={"target_id": target_id, "module": "localize", "config": {"language": "spanish"}},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, set_transform.status_code)
            exec_resp = self.client.post(
                "/terminal_exec",
                json={"target_id": target_id, "command": "echo routed"},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, exec_resp.status_code)

            create_resp = self.client.post(
                "/model_routing_artifacts",
                json={"routes": ["localize"], "min_samples": 1, "limit": 500},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(201, create_resp.status_code)
            artifact_id = str((create_resp.get_json() or {}).get("artifact_id") or "")
            self.assertTrue(artifact_id)

            apply_resp = self.client.post(
                "/model_routing_profile_apply",
                json={"apply": True, "artifact_id": artifact_id},
                headers=self._headers(user_id, session_id),
            )
            self.assertEqual(200, apply_resp.status_code)
            body = apply_resp.get_json() or {}
            self.assertTrue(bool(body.get("applied")))
            after_routes = dict((body.get("after") or {}).get("routes") or {})
            self.assertIn("localize", after_routes)
        finally:
            config_module.apply_ai_route_model_profile(
                default_model=original.get("default_model"),
                route_models=original.get("routes"),
            )

    def test_model_routing_profile_apply_returns_404_for_unknown_artifact_id(self):
        user_id = "terminal-user-routing-apply-artifact-id-404"
        session_id = self._create_session(user_id)
        resp = self.client.post(
            "/model_routing_profile_apply",
            json={"apply": True, "artifact_id": "model-routing-missing"},
            headers=self._headers(user_id, session_id),
        )
        self.assertEqual(404, resp.status_code)
        payload = resp.get_json() or {}
        self.assertEqual("artifact_not_found", payload.get("error"))


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