"""
Story 8.06 — Test Suite
========================
Royal Dashboard FastAPI Static Mount

File under test: api/royal_dashboard.py  (GET / route + __main__ block)
Dependency:      api/templates/royal_dashboard.html  (Story 8.05, must exist)

BB Tests (3):
  BB1: GET / returns HTTP 200 with Content-Type text/html
  BB2: Response body contains the "Royal Chamber" title string
  BB3: GET / returns different (HTML) content from GET /status (which is JSON)

WB Tests (3):
  WB1: serve_dashboard() returns a FileResponse instance (not HTMLResponse)
  WB2: TEMPLATE_PATH is constructed relative to __file__ using os.path.dirname
  WB3: Port in __main__ block is 8766 (not 8765)

Additional coverage tests (3):
  COV1: GET / with the real HTML file on disk → full end-to-end smoke test
  COV2: TEMPLATE_PATH points to the correct absolute location on E: drive
  COV3: serve_dashboard function signature matches the route (no dependencies)

All tests use httpx via FastAPI TestClient.
No real uvicorn server is started.
"""
from __future__ import annotations

import ast
import inspect
import os
import sys
from pathlib import Path
from unittest.mock import patch

import pytest
from fastapi.responses import FileResponse
from fastapi.testclient import TestClient

sys.path.insert(0, "/mnt/e/genesis-system")

import api.royal_dashboard as rd
from api.royal_dashboard import dashboard, serve_dashboard

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

_MODULE_FILE = Path("/mnt/e/genesis-system/api/royal_dashboard.py")
_HTML_FILE = Path("/mnt/e/genesis-system/api/templates/royal_dashboard.html")

# ---------------------------------------------------------------------------
# BB1 — GET / returns HTTP 200, Content-Type text/html
# ---------------------------------------------------------------------------


class TestBB1_DashboardReturns200HTML:
    """BB1: GET / must return 200 with Content-Type: text/html."""

    def test_get_root_returns_200(self):
        """BB1: GET / → HTTP 200 status code."""
        client = TestClient(dashboard)
        resp = client.get("/")
        assert resp.status_code == 200, (
            f"Expected HTTP 200 from GET /, got {resp.status_code}"
        )

    def test_get_root_content_type_is_html(self):
        """BB1: GET / → Content-Type header includes 'text/html'."""
        client = TestClient(dashboard)
        resp = client.get("/")
        content_type = resp.headers.get("content-type", "")
        assert "text/html" in content_type, (
            f"Expected 'text/html' in Content-Type, got '{content_type}'"
        )

    def test_get_root_body_is_not_empty(self):
        """BB1: GET / → response body is non-empty."""
        client = TestClient(dashboard)
        resp = client.get("/")
        assert len(resp.content) > 0, "Expected non-empty body from GET /"


# ---------------------------------------------------------------------------
# BB2 — Response body contains "Royal Chamber"
# ---------------------------------------------------------------------------


class TestBB2_BodyContainsRoyalChamber:
    """BB2: The HTML served at GET / must contain 'Royal Chamber'."""

    def test_body_contains_royal_chamber(self):
        """BB2: GET / body must contain the string 'Royal Chamber'."""
        client = TestClient(dashboard)
        resp = client.get("/")
        assert resp.status_code == 200
        body = resp.text
        assert "Royal Chamber" in body, (
            "Expected 'Royal Chamber' in the response body from GET /. "
            f"Got first 200 chars: {body[:200]!r}"
        )

    def test_body_is_html_document(self):
        """BB2: GET / body must look like an HTML document (contains <!DOCTYPE or <html>)."""
        client = TestClient(dashboard)
        resp = client.get("/")
        body = resp.text.lower()
        # The file may start with an HTML comment (e.g., VERIFICATION_STAMP) before
        # the DOCTYPE declaration — check for presence anywhere in the document.
        assert "<!doctype" in body or "<html" in body, (
            f"Expected HTML document containing <!DOCTYPE or <html>, got: {body[:200]!r}"
        )


# ---------------------------------------------------------------------------
# BB3 — GET / returns different content from GET /status
# ---------------------------------------------------------------------------


class TestBB3_RootDifferentFromStatus:
    """BB3: GET / must return HTML (not the JSON dict that /status returns)."""

    def test_root_is_not_json_like_status(self):
        """BB3: GET / must not return JSON with active_calls/swarm_workers/queue_depth."""
        client = TestClient(dashboard)
        resp = client.get("/")
        # /status returns JSON: {"active_calls": ..., "swarm_workers": ..., "queue_depth": ...}
        # GET / must return HTML, which should NOT be parseable as the status dict
        try:
            data = resp.json()
            # If it IS valid JSON, it must not be the status dict
            assert "active_calls" not in data, (
                "GET / should not return the /status JSON payload"
            )
        except Exception:
            # Non-JSON (HTML) is exactly what we want
            pass

    def test_root_content_differs_from_status_content(self):
        """BB3: Raw content of GET / must differ from GET /status."""
        client = TestClient(dashboard)
        root_resp = client.get("/")
        status_resp = client.get("/status")
        assert root_resp.text != status_resp.text, (
            "GET / and GET /status returned identical content — expected different responses"
        )

    def test_status_still_returns_json(self):
        """BB3 (regression guard): GET /status still returns valid JSON after adding GET /."""
        client = TestClient(dashboard)
        resp = client.get("/status")
        assert resp.status_code == 200
        data = resp.json()
        for key in ("active_calls", "swarm_workers", "queue_depth"):
            assert key in data, f"Key '{key}' missing from /status after adding GET / route"


# ---------------------------------------------------------------------------
# WB1 — FileResponse is used (not HTMLResponse with hardcoded string)
# ---------------------------------------------------------------------------


class TestWB1_FileResponseUsed:
    """WB1: The route must use FileResponse, not HTMLResponse."""

    def test_serve_dashboard_returns_file_response(self):
        """WB1: serve_dashboard() must return a FileResponse instance."""
        # We check the return annotation and the actual response object
        # by inspecting the source / return type
        source = inspect.getsource(serve_dashboard)
        assert "FileResponse" in source, (
            "serve_dashboard must use FileResponse (not HTMLResponse with hardcoded string). "
            f"Source: {source!r}"
        )

    def test_file_response_not_hardcoded_html_string(self):
        """WB1: Route must NOT use HTMLResponse with a hardcoded HTML string."""
        source = inspect.getsource(serve_dashboard)
        # HTMLResponse(content="<html>...") pattern is forbidden
        assert "HTMLResponse" not in source, (
            "serve_dashboard must NOT use HTMLResponse — must use FileResponse."
        )

    def test_return_annotation_is_file_response(self):
        """WB1: Function return annotation must be FileResponse (by name or type)."""
        hints = serve_dashboard.__annotations__
        ret = hints.get("return")
        # Under 'from __future__ import annotations', annotations are stored as strings.
        # Compare by string name OR by actual class reference.
        ret_name = ret if isinstance(ret, str) else getattr(ret, "__name__", str(ret))
        assert ret_name == "FileResponse" or ret is FileResponse, (
            f"Expected return annotation 'FileResponse', got {ret!r}"
        )


# ---------------------------------------------------------------------------
# WB2 — TEMPLATE_PATH uses os.path.dirname(__file__)
# ---------------------------------------------------------------------------


class TestWB2_TemplatePathRelativeToModule:
    """WB2: TEMPLATE_PATH must be constructed relative to __file__, not hardcoded absolute."""

    def test_template_path_constant_exists(self):
        """WB2: rd.TEMPLATE_PATH must be defined in the module."""
        assert hasattr(rd, "TEMPLATE_PATH"), (
            "Expected TEMPLATE_PATH constant in api.royal_dashboard module"
        )

    def test_template_path_ends_with_html_filename(self):
        """WB2: TEMPLATE_PATH must end with 'royal_dashboard.html'."""
        assert rd.TEMPLATE_PATH.endswith("royal_dashboard.html"), (
            f"TEMPLATE_PATH must end with 'royal_dashboard.html', got: {rd.TEMPLATE_PATH!r}"
        )

    def test_template_path_contains_templates_dir(self):
        """WB2: TEMPLATE_PATH must include 'templates' directory segment."""
        norm = rd.TEMPLATE_PATH.replace("\\", "/")
        assert "templates" in norm, (
            f"TEMPLATE_PATH must include 'templates' directory, got: {rd.TEMPLATE_PATH!r}"
        )

    def test_template_path_source_uses_os_path_dirname(self):
        """WB2: Source code must derive TEMPLATE_PATH using os.path.dirname(__file__)."""
        source = _MODULE_FILE.read_text(encoding="utf-8")
        assert "os.path.dirname(__file__)" in source or "os.path.dirname" in source, (
            "TEMPLATE_PATH must use os.path.dirname(__file__) for relative resolution. "
            "Hardcoded absolute paths are forbidden."
        )

    def test_template_path_points_to_e_drive(self):
        """WB2: TEMPLATE_PATH must resolve to E: drive (not C: drive)."""
        norm = rd.TEMPLATE_PATH.replace("\\", "/").lower()
        # On WSL, E: maps to /mnt/e/
        assert "/mnt/e/" in norm or norm.startswith("e:/"), (
            f"TEMPLATE_PATH must be on E: drive, got: {rd.TEMPLATE_PATH!r}"
        )


# ---------------------------------------------------------------------------
# WB3 — Port in __main__ block is 8766
# ---------------------------------------------------------------------------


class TestWB3_PortIs8766:
    """WB3: The uvicorn.run() call in __main__ must use port 8766."""

    def test_source_contains_port_8766(self):
        """WB3: Source code must contain port=8766 in the __main__ block."""
        source = _MODULE_FILE.read_text(encoding="utf-8")
        assert "port=8766" in source, (
            "Expected 'port=8766' in __main__ uvicorn.run() call. "
            "Dashboard uses port 8766 (Memory API is on 8765)."
        )

    def test_source_does_not_use_port_8765_in_main(self):
        """WB3: The __main__ uvicorn.run() must NOT use port 8765 (that's the Memory API)."""
        source = _MODULE_FILE.read_text(encoding="utf-8")
        # Find the __main__ block specifically
        main_idx = source.find('if __name__ == "__main__"')
        assert main_idx != -1, "Expected __main__ block in source"
        main_block = source[main_idx:]
        assert "port=8765" not in main_block, (
            "Port 8765 is reserved for the Memory API. Dashboard must use 8766."
        )

    def test_source_has_main_block(self):
        """WB3: Source code must contain a __main__ block with uvicorn.run."""
        source = _MODULE_FILE.read_text(encoding="utf-8")
        assert 'if __name__ == "__main__"' in source, (
            "Expected '__main__' entrypoint block in royal_dashboard.py"
        )
        main_idx = source.find('if __name__ == "__main__"')
        main_block = source[main_idx:]
        assert "uvicorn" in main_block, (
            "Expected 'uvicorn.run' inside __main__ block"
        )


# ---------------------------------------------------------------------------
# COV1 — Full end-to-end smoke test using real HTML file on disk
# ---------------------------------------------------------------------------


class TestCOV1_EndToEndSmoke:
    """COV1: Real HTML file served at GET / passes end-to-end smoke test."""

    def test_html_file_exists_on_disk(self):
        """COV1: The template HTML file must exist on disk."""
        assert _HTML_FILE.exists(), (
            f"Template HTML not found at {_HTML_FILE}. "
            "Story 8.05 must be complete before 8.06 can be tested."
        )

    def test_get_root_serves_actual_html_file_content(self):
        """COV1: GET / must serve the same content as the HTML file on disk."""
        if not _HTML_FILE.exists():
            pytest.skip("HTML template not found — skipping end-to-end test")
        expected = _HTML_FILE.read_text(encoding="utf-8")
        client = TestClient(dashboard)
        resp = client.get("/")
        assert resp.status_code == 200
        # Body should match file content (TestClient may compress, so check substring)
        assert "Royal Chamber" in resp.text, (
            "GET / body must contain HTML file content including 'Royal Chamber'"
        )

    def test_full_round_trip_200_html_royal_chamber(self):
        """COV1: Combined assertion — 200, text/html, Royal Chamber in body."""
        client = TestClient(dashboard)
        resp = client.get("/")
        assert resp.status_code == 200, f"Status: {resp.status_code}"
        assert "text/html" in resp.headers.get("content-type", ""), (
            f"Content-Type: {resp.headers.get('content-type')}"
        )
        assert "Royal Chamber" in resp.text, "Body missing 'Royal Chamber'"


# ---------------------------------------------------------------------------
# COV2 — TEMPLATE_PATH points to correct location on E: drive
# ---------------------------------------------------------------------------


class TestCOV2_TemplatePathCorrect:
    """COV2: TEMPLATE_PATH must resolve to the correct absolute path."""

    def test_template_path_is_absolute(self):
        """COV2: TEMPLATE_PATH must be an absolute path (not relative)."""
        assert os.path.isabs(rd.TEMPLATE_PATH), (
            f"TEMPLATE_PATH must be absolute, got: {rd.TEMPLATE_PATH!r}"
        )

    def test_template_path_file_exists(self):
        """COV2: TEMPLATE_PATH must point to a file that actually exists on disk."""
        assert os.path.isfile(rd.TEMPLATE_PATH), (
            f"TEMPLATE_PATH does not point to an existing file: {rd.TEMPLATE_PATH!r}"
        )

    def test_template_path_is_in_api_templates_subdir(self):
        """COV2: TEMPLATE_PATH must be inside the api/templates/ subdirectory."""
        norm = rd.TEMPLATE_PATH.replace("\\", "/")
        assert "api/templates/" in norm, (
            f"Expected TEMPLATE_PATH inside 'api/templates/', got: {rd.TEMPLATE_PATH!r}"
        )


# ---------------------------------------------------------------------------
# COV3 — serve_dashboard function signature: no injected dependencies
# ---------------------------------------------------------------------------


class TestCOV3_ServeDashboardSignature:
    """COV3: serve_dashboard must be a simple async function with no dependencies."""

    def test_serve_dashboard_is_async(self):
        """COV3: serve_dashboard must be an async function."""
        import asyncio
        assert asyncio.iscoroutinefunction(serve_dashboard), (
            "serve_dashboard must be defined as 'async def serve_dashboard()'"
        )

    def test_serve_dashboard_has_no_required_parameters(self):
        """COV3: serve_dashboard must take no parameters (no Depends injection needed)."""
        sig = inspect.signature(serve_dashboard)
        params = sig.parameters
        # No parameters needed — static file serving needs no DB/Redis
        assert len(params) == 0, (
            f"serve_dashboard should take 0 parameters, got: {list(params.keys())}"
        )

    def test_route_is_registered_on_get_slash(self):
        """COV3: GET / route must be registered in the dashboard FastAPI app."""
        routes = {route.path: route for route in dashboard.routes}  # type: ignore[attr-defined]
        assert "/" in routes, (
            f"GET / route not found in dashboard app. Registered routes: {list(routes.keys())}"
        )


# ---------------------------------------------------------------------------
# Run summary
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    result = pytest.main([__file__, "-v", "--tb=short"])
    sys.exit(result)
