# #region TestResourceHubs [C:2] [TYPE Module]
# @RELATION  DEPENDS_ON -> [DashboardsApi]
# @RELATION  DEPENDS_ON -> [DatasetsApi]
# @SEMANTICS: tests, resource-hubs, dashboards, datasets, pagination, api
# @PURPOSE: Contract tests for resource hub dashboards/datasets listing and pagination boundary validation.
# @LAYER Tests
import pytest
from unittest.mock import AsyncMock, MagicMock

from fastapi.testclient import TestClient

from src.app import app
from src.dependencies import (
    get_config_manager,
    get_resource_service,
    get_task_manager,
    has_permission,
)

client = TestClient(app)

# #region test_dashboards_api [C:2] [TYPE Block]
# @RELATION  BINDS_TO -> [TestResourceHubs]
# @PURPOSE: Verify GET /api/dashboards contract compliance
# @TEST_CONTRACT: dashboards_query -> dashboards payload or not_found response
# @TEST_SCENARIO: dashboards_env_found_returns_payload -> HTTP 200 returns normalized dashboards list.
# @TEST_SCENARIO: dashboards_unknown_env_returns_not_found -> HTTP 404 is returned for unknown env_id.
# @TEST_SCENARIO: dashboards_search_filters_results -> Search narrows payload to matching dashboard title.
# @TEST_INVARIANT: dashboards_route_contract_stays_observable -> VERIFIED_BY: [dashboards_env_found_returns_payload, dashboards_unknown_env_returns_not_found, dashboards_search_filters_results]


# #region mock_deps [C:2] [TYPE Function]
# @PURPOSE: Provide dependency override fixture for resource hub route tests.
# @RELATION  BINDS_TO -> [TestResourceHubs]
# @TEST_FIXTURE: resource_hub_overrides -> INLINE_JSON
@pytest.fixture
def mock_deps():
    # @INVARIANT: unconstrained mock — no spec= enforced; attribute typos will silently pass
    config_manager = MagicMock()
    # @INVARIANT: unconstrained mock — no spec= enforced; attribute typos will silently pass
    task_manager = MagicMock()
    # @INVARIANT: unconstrained mock — no spec= enforced; attribute typos will silently pass
    resource_service = MagicMock()

    # Mock environment
    env = MagicMock()
    env.id = "env1"
    config_manager.get_environments.return_value = [env]

    # Mock tasks
    task_manager.get_all_tasks.return_value = []

    # Mock dashboards
    resource_service.get_dashboards_with_status = AsyncMock(
        return_value=[
            {
                "id": 1,
                "title": "Sales",
                "slug": "sales",
                "git_status": {"branch": "main", "sync_status": "OK"},
                "last_task": None,
            },
            {
                "id": 2,
                "title": "Marketing",
                "slug": "mkt",
                "git_status": None,
                "last_task": {"task_id": "t1", "status": "SUCCESS"},
            },
        ]
    )

    app.dependency_overrides[get_config_manager] = lambda: config_manager
    app.dependency_overrides[get_task_manager] = lambda: task_manager
    app.dependency_overrides[get_resource_service] = lambda: resource_service

    # Bypass permission check
    mock_user = MagicMock()
    mock_user.username = "testadmin"
    mock_user.roles = []
    admin_role = MagicMock()
    admin_role.name = "Admin"
    mock_user.roles.append(admin_role)

    # Override both get_current_user and has_permission
    from src.dependencies import get_current_user

    app.dependency_overrides[get_current_user] = lambda: mock_user

    # We need to override the specific instance returned by has_permission
    app.dependency_overrides[has_permission("plugin:migration", "READ")] = (
        lambda: mock_user
    )

    yield {"config": config_manager, "task": task_manager, "resource": resource_service}

    app.dependency_overrides.clear()


# #endregion mock_deps


# #region test_get_dashboards_success [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_dashboards_api]
# @PURPOSE: Verify dashboards endpoint returns 200 with expected dashboard payload fields.
def test_get_dashboards_success(mock_deps):
    response = client.get("/api/dashboards?env_id=env1")
    assert response.status_code == 200
    data = response.json()
    assert "dashboards" in data
    assert len(data["dashboards"]) == 2
    assert data["dashboards"][0]["title"] == "Sales"
    assert data["dashboards"][0]["git_status"]["sync_status"] == "OK"


# #endregion test_get_dashboards_success


# #region test_get_dashboards_not_found [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_dashboards_api]
# @PURPOSE: Verify dashboards endpoint returns 404 for unknown environment identifier.
def test_get_dashboards_not_found(mock_deps):
    response = client.get("/api/dashboards?env_id=invalid")
    assert response.status_code == 404


# #endregion test_get_dashboards_not_found


# #region test_get_dashboards_search [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_dashboards_api]
# @PURPOSE: Verify dashboards endpoint search filter returns matching subset.
def test_get_dashboards_search(mock_deps):
    response = client.get("/api/dashboards?env_id=env1&search=Sales")
    assert response.status_code == 200
    data = response.json()
    assert len(data["dashboards"]) == 1
    assert data["dashboards"][0]["title"] == "Sales"


# #endregion test_get_dashboards_search
# #endregion test_dashboards_api


# #region test_datasets_api [C:2] [TYPE Block]
# @RELATION  BINDS_TO -> [TestResourceHubs]
# @PURPOSE: Verify GET /api/datasets contract compliance
# @TEST_CONTRACT: datasets_query -> datasets payload or error response
# @TEST_SCENARIO: datasets_env_found_returns_payload -> HTTP 200 returns normalized datasets list.
# @TEST_SCENARIO: datasets_unknown_env_returns_not_found -> HTTP 404 is returned for unknown env_id.
# @TEST_SCENARIO: datasets_search_filters_results -> Search narrows payload to matching dataset table.
# @TEST_SCENARIO: datasets_service_failure_returns_503 -> Backend fetch failure surfaces as HTTP 503.
# @TEST_INVARIANT: datasets_route_contract_stays_observable -> VERIFIED_BY: [datasets_env_found_returns_payload, datasets_unknown_env_returns_not_found, datasets_search_filters_results, datasets_service_failure_returns_503]


# #region test_get_datasets_success [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_datasets_api]
# @PURPOSE: Verify datasets endpoint returns 200 with mapped fields payload.
def test_get_datasets_success(mock_deps):
    mock_deps["resource"].get_datasets_with_status = AsyncMock(
        return_value=[
            {
                "id": 1,
                "table_name": "orders",
                "schema": "public",
                "database": "db1",
                "mapped_fields": {"total": 10, "mapped": 5},
                "last_task": None,
            }
        ]
    )

    response = client.get("/api/datasets?env_id=env1")
    assert response.status_code == 200
    data = response.json()
    assert "datasets" in data
    assert len(data["datasets"]) == 1
    assert data["datasets"][0]["table_name"] == "orders"
    assert data["datasets"][0]["mapped_fields"]["mapped"] == 5


# #endregion test_get_datasets_success


# #region test_get_datasets_not_found [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_datasets_api]
# @PURPOSE: Verify datasets endpoint returns 404 for unknown environment identifier.
def test_get_datasets_not_found(mock_deps):
    response = client.get("/api/datasets?env_id=invalid")
    assert response.status_code == 404


# #endregion test_get_datasets_not_found


# #region test_get_datasets_search [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_datasets_api]
# @PURPOSE: Verify datasets endpoint search filter returns matching dataset subset.
def test_get_datasets_search(mock_deps):
    mock_deps["resource"].get_datasets_with_status = AsyncMock(
        return_value=[
            {
                "id": 1,
                "table_name": "orders",
                "schema": "public",
                "database": "db1",
                "mapped_fields": {"total": 10, "mapped": 5},
                "last_task": None,
            },
            {
                "id": 2,
                "table_name": "users",
                "schema": "public",
                "database": "db1",
                "mapped_fields": {"total": 5, "mapped": 5},
                "last_task": None,
            },
        ]
    )

    response = client.get("/api/datasets?env_id=env1&search=orders")
    assert response.status_code == 200
    data = response.json()
    assert len(data["datasets"]) == 1
    assert data["datasets"][0]["table_name"] == "orders"


# #endregion test_get_datasets_search


# #region test_get_datasets_service_failure [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_datasets_api]
# @PURPOSE: Verify datasets endpoint surfaces backend fetch failure as HTTP 503.
def test_get_datasets_service_failure(mock_deps):
    mock_deps["resource"].get_datasets_with_status = AsyncMock(
        side_effect=Exception("Superset down")
    )

    response = client.get("/api/datasets?env_id=env1")
    assert response.status_code == 503
    assert "Failed to fetch datasets" in response.json()["detail"]


# #endregion test_get_datasets_service_failure
# #endregion test_datasets_api


# #region test_pagination_boundaries [C:2] [TYPE Block]
# @RELATION  BINDS_TO -> [TestResourceHubs]
# @PURPOSE: Verify pagination validation for GET endpoints
# @TEST_CONTRACT: pagination_query -> validation error response
# @TEST_SCENARIO: dashboards_zero_page_rejected -> page=0 returns HTTP 400.
# @TEST_SCENARIO: dashboards_oversize_page_rejected -> page_size=101 returns HTTP 400.
# @TEST_SCENARIO: datasets_zero_page_rejected -> page=0 returns HTTP 400.
# @TEST_SCENARIO: datasets_oversize_page_rejected -> page_size=101 returns HTTP 400.
# @TEST_EDGE: missing_field -> Missing env_id prevents route contract completion.
# @TEST_EDGE: invalid_type -> Invalid pagination values are rejected at route validation layer.
# @TEST_EDGE: external_fail -> Validation failure returns HTTP 400 without partial payload.
# @TEST_INVARIANT: pagination_limits_apply_to_both_routes -> VERIFIED_BY: [dashboards_zero_page_rejected, dashboards_oversize_page_rejected, datasets_zero_page_rejected, datasets_oversize_page_rejected]


# #region test_get_dashboards_pagination_zero_page [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_pagination_boundaries]
# @PURPOSE: Verify dashboards endpoint rejects page=0 with HTTP 400 validation error.
def test_get_dashboards_pagination_zero_page(mock_deps):
    # @TEST_EDGE: pagination_zero_page -> {page: 0, status: 400}
    response = client.get("/api/dashboards?env_id=env1&page=0")
    assert response.status_code == 400
    assert "Page must be >= 1" in response.json()["detail"]


# #endregion test_get_dashboards_pagination_zero_page


# #region test_get_dashboards_pagination_oversize [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_pagination_boundaries]
# @PURPOSE: Verify dashboards endpoint rejects oversized page_size with HTTP 400.
def test_get_dashboards_pagination_oversize(mock_deps):
    # @TEST_EDGE: pagination_oversize -> {page_size: 101, status: 400}
    response = client.get("/api/dashboards?env_id=env1&page_size=101")
    assert response.status_code == 400
    assert "Page size must be between 1 and 100" in response.json()["detail"]


# #endregion test_get_dashboards_pagination_oversize


# #region test_get_datasets_pagination_zero_page [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_pagination_boundaries]
# @PURPOSE: Verify datasets endpoint rejects page=0 with HTTP 400.
def test_get_datasets_pagination_zero_page(mock_deps):
    # @TEST_EDGE: pagination_zero_page_datasets -> {page: 0, status: 400}
    response = client.get("/api/datasets?env_id=env1&page=0")
    assert response.status_code == 400


# #endregion test_get_datasets_pagination_zero_page


# #region test_get_datasets_pagination_oversize [C:2] [TYPE Function]
# @RELATION  BINDS_TO -> [test_pagination_boundaries]
# @PURPOSE: Verify datasets endpoint rejects oversized page_size with HTTP 400.
def test_get_datasets_pagination_oversize(mock_deps):
    # @TEST_EDGE: pagination_oversize_datasets -> {page_size: 101, status: 400}
    response = client.get("/api/datasets?env_id=env1&page_size=101")
    assert response.status_code == 400


# #endregion test_get_datasets_pagination_oversize
# #endregion test_pagination_boundaries
# #endregion TestResourceHubs
