# [DEF:backend.src.api.routes.__tests__.test_profile_api:Module]
# @TIER: STANDARD
# @SEMANTICS: tests, profile, api, preferences, lookup, contract
# @PURPOSE: Verifies profile API route contracts for preference read/update and Superset account lookup.
# @LAYER: API
# @RELATION: TESTS -> backend.src.api.routes.profile

# [SECTION: IMPORTS]
from datetime import datetime, timezone
from unittest.mock import MagicMock, patch

from fastapi.testclient import TestClient

from src.app import app
from src.core.database import get_db
from src.dependencies import get_config_manager, get_current_user
from src.schemas.profile import (
    ProfilePermissionState,
    ProfilePreference,
    ProfilePreferenceResponse,
    ProfileSecuritySummary,
    SupersetAccountCandidate,
    SupersetAccountLookupResponse,
)
from src.services.profile_service import (
    EnvironmentNotFoundError,
    ProfileAuthorizationError,
    ProfileValidationError,
)
# [/SECTION]


client = TestClient(app)


# [DEF:mock_profile_route_dependencies:Function]
# @PURPOSE: Provides deterministic dependency overrides for profile route tests.
# @PRE: App instance is initialized.
# @POST: Dependencies are overridden for current test and restored afterward.
def mock_profile_route_dependencies():
    mock_user = MagicMock()
    mock_user.id = "u-1"
    mock_user.username = "test-user"

    mock_db = MagicMock()
    mock_config_manager = MagicMock()

    app.dependency_overrides[get_current_user] = lambda: mock_user
    app.dependency_overrides[get_db] = lambda: mock_db
    app.dependency_overrides[get_config_manager] = lambda: mock_config_manager

    return mock_user, mock_db, mock_config_manager
# [/DEF:mock_profile_route_dependencies:Function]


# [DEF:profile_route_deps_fixture:Function]
# @PURPOSE: Pytest fixture wrapper for profile route dependency overrides.
# @PRE: None.
# @POST: Yields overridden dependencies and clears overrides after test.
import pytest


@pytest.fixture(autouse=True)
def profile_route_deps_fixture():
    yielded = mock_profile_route_dependencies()
    yield yielded
    app.dependency_overrides.clear()
# [/DEF:profile_route_deps_fixture:Function]


# [DEF:_build_preference_response:Function]
# @PURPOSE: Builds stable profile preference response payload for route tests.
# @PRE: user_id is provided.
# @POST: Returns ProfilePreferenceResponse object with deterministic timestamps.
def _build_preference_response(user_id: str = "u-1") -> ProfilePreferenceResponse:
    now = datetime.now(timezone.utc)
    return ProfilePreferenceResponse(
        status="success",
        message="Preference loaded",
        preference=ProfilePreference(
            user_id=user_id,
            superset_username="John_Doe",
            superset_username_normalized="john_doe",
            show_only_my_dashboards=True,
            show_only_slug_dashboards=True,
            git_username="ivan.ivanov",
            git_email="ivan@company.local",
            has_git_personal_access_token=True,
            git_personal_access_token_masked="iv***al",
            start_page="reports",
            auto_open_task_drawer=False,
            dashboards_table_density="compact",
            created_at=now,
            updated_at=now,
        ),
        security=ProfileSecuritySummary(
            read_only=True,
            auth_source="adfs",
            current_role="Data Engineer",
            role_source="adfs",
            roles=["Data Engineer"],
            permissions=[
                ProfilePermissionState(key="migration:run", allowed=True),
                ProfilePermissionState(key="admin:users", allowed=False),
            ],
        ),
    )
# [/DEF:_build_preference_response:Function]


# [DEF:test_get_profile_preferences_returns_self_payload:Function]
# @PURPOSE: Verifies GET /api/profile/preferences returns stable self-scoped payload.
# @PRE: Authenticated user context is available.
# @POST: Response status is 200 and payload contains current user preference.
def test_get_profile_preferences_returns_self_payload(profile_route_deps_fixture):
    mock_user, _, _ = profile_route_deps_fixture
    service = MagicMock()
    service.get_my_preference.return_value = _build_preference_response(user_id=mock_user.id)

    with patch("src.api.routes.profile._get_profile_service", return_value=service):
        response = client.get("/api/profile/preferences")

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "success"
    assert payload["preference"]["user_id"] == mock_user.id
    assert payload["preference"]["superset_username_normalized"] == "john_doe"
    assert payload["preference"]["git_username"] == "ivan.ivanov"
    assert payload["preference"]["git_email"] == "ivan@company.local"
    assert payload["preference"]["show_only_slug_dashboards"] is True
    assert payload["preference"]["has_git_personal_access_token"] is True
    assert payload["preference"]["git_personal_access_token_masked"] == "iv***al"
    assert payload["preference"]["start_page"] == "reports"
    assert payload["preference"]["auto_open_task_drawer"] is False
    assert payload["preference"]["dashboards_table_density"] == "compact"
    assert payload["security"]["read_only"] is True
    assert payload["security"]["current_role"] == "Data Engineer"
    assert payload["security"]["permissions"][0]["key"] == "migration:run"
    service.get_my_preference.assert_called_once_with(mock_user)
# [/DEF:test_get_profile_preferences_returns_self_payload:Function]


# [DEF:test_patch_profile_preferences_success:Function]
# @PURPOSE: Verifies PATCH /api/profile/preferences persists valid payload through route mapping.
# @PRE: Valid request payload and authenticated user.
# @POST: Response status is 200 with saved preference payload.
def test_patch_profile_preferences_success(profile_route_deps_fixture):
    mock_user, _, _ = profile_route_deps_fixture
    service = MagicMock()
    service.update_my_preference.return_value = _build_preference_response(user_id=mock_user.id)

    with patch("src.api.routes.profile._get_profile_service", return_value=service):
        response = client.patch(
            "/api/profile/preferences",
            json={
                "superset_username": "John_Doe",
                "show_only_my_dashboards": True,
                "show_only_slug_dashboards": True,
                "git_username": "ivan.ivanov",
                "git_email": "ivan@company.local",
                "git_personal_access_token": "ghp_1234567890",
                "start_page": "reports-logs",
                "auto_open_task_drawer": False,
                "dashboards_table_density": "free",
            },
        )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "success"
    assert payload["preference"]["superset_username"] == "John_Doe"
    assert payload["preference"]["show_only_my_dashboards"] is True
    assert payload["preference"]["show_only_slug_dashboards"] is True
    assert payload["preference"]["git_username"] == "ivan.ivanov"
    assert payload["preference"]["git_email"] == "ivan@company.local"
    assert payload["preference"]["start_page"] == "reports"
    assert payload["preference"]["auto_open_task_drawer"] is False
    assert payload["preference"]["dashboards_table_density"] == "compact"
    service.update_my_preference.assert_called_once()

    called_kwargs = service.update_my_preference.call_args.kwargs
    assert called_kwargs["current_user"] == mock_user
    assert called_kwargs["payload"].git_username == "ivan.ivanov"
    assert called_kwargs["payload"].git_email == "ivan@company.local"
    assert called_kwargs["payload"].git_personal_access_token == "ghp_1234567890"
    assert called_kwargs["payload"].show_only_slug_dashboards is True
    assert called_kwargs["payload"].start_page == "reports-logs"
    assert called_kwargs["payload"].auto_open_task_drawer is False
    assert called_kwargs["payload"].dashboards_table_density == "free"
# [/DEF:test_patch_profile_preferences_success:Function]


# [DEF:test_patch_profile_preferences_validation_error:Function]
# @PURPOSE: Verifies route maps domain validation failure to HTTP 422 with actionable details.
# @PRE: Service raises ProfileValidationError.
# @POST: Response status is 422 and includes validation messages.
def test_patch_profile_preferences_validation_error(profile_route_deps_fixture):
    service = MagicMock()
    service.update_my_preference.side_effect = ProfileValidationError(
        ["Superset username is required when default filter is enabled."]
    )

    with patch("src.api.routes.profile._get_profile_service", return_value=service):
        response = client.patch(
            "/api/profile/preferences",
            json={
                "superset_username": "",
                "show_only_my_dashboards": True,
            },
        )

    assert response.status_code == 422
    payload = response.json()
    assert "detail" in payload
    assert "Superset username is required when default filter is enabled." in payload["detail"]
# [/DEF:test_patch_profile_preferences_validation_error:Function]


# [DEF:test_patch_profile_preferences_cross_user_denied:Function]
# @PURPOSE: Verifies route maps domain authorization guard failure to HTTP 403.
# @PRE: Service raises ProfileAuthorizationError.
# @POST: Response status is 403 with denial message.
def test_patch_profile_preferences_cross_user_denied(profile_route_deps_fixture):
    service = MagicMock()
    service.update_my_preference.side_effect = ProfileAuthorizationError(
        "Cross-user preference mutation is forbidden"
    )

    with patch("src.api.routes.profile._get_profile_service", return_value=service):
        response = client.patch(
            "/api/profile/preferences",
            json={
                "superset_username": "john_doe",
                "show_only_my_dashboards": True,
            },
        )

    assert response.status_code == 403
    payload = response.json()
    assert payload["detail"] == "Cross-user preference mutation is forbidden"
# [/DEF:test_patch_profile_preferences_cross_user_denied:Function]


# [DEF:test_lookup_superset_accounts_success:Function]
# @PURPOSE: Verifies lookup route returns success payload with normalized candidates.
# @PRE: Valid environment_id and service success response.
# @POST: Response status is 200 and items list is returned.
def test_lookup_superset_accounts_success(profile_route_deps_fixture):
    service = MagicMock()
    service.lookup_superset_accounts.return_value = SupersetAccountLookupResponse(
        status="success",
        environment_id="dev",
        page_index=0,
        page_size=20,
        total=1,
        warning=None,
        items=[
            SupersetAccountCandidate(
                environment_id="dev",
                username="john_doe",
                display_name="John Doe",
                email="john@example.local",
                is_active=True,
            )
        ],
    )

    with patch("src.api.routes.profile._get_profile_service", return_value=service):
        response = client.get("/api/profile/superset-accounts?environment_id=dev")

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "success"
    assert payload["environment_id"] == "dev"
    assert payload["total"] == 1
    assert payload["items"][0]["username"] == "john_doe"
# [/DEF:test_lookup_superset_accounts_success:Function]


# [DEF:test_lookup_superset_accounts_env_not_found:Function]
# @PURPOSE: Verifies lookup route maps missing environment to HTTP 404.
# @PRE: Service raises EnvironmentNotFoundError.
# @POST: Response status is 404 with explicit message.
def test_lookup_superset_accounts_env_not_found(profile_route_deps_fixture):
    service = MagicMock()
    service.lookup_superset_accounts.side_effect = EnvironmentNotFoundError(
        "Environment 'missing-env' not found"
    )

    with patch("src.api.routes.profile._get_profile_service", return_value=service):
        response = client.get("/api/profile/superset-accounts?environment_id=missing-env")

    assert response.status_code == 404
    payload = response.json()
    assert payload["detail"] == "Environment 'missing-env' not found"
# [/DEF:test_lookup_superset_accounts_env_not_found:Function]

# [/DEF:backend.src.api.routes.__tests__.test_profile_api:Module]
