"""Architecture tests — Experience module boundary enforcement."""
from __future__ import annotations

import ast
from pathlib import Path

from app.models import foundation as models
from app.models import sites as site_models
from app.models import components as component_models
from app.models import themes as theme_models
from app.models import templates as template_models
from app.models import localization as localization_models
from app.models import forms as form_models
from app.models import publishing as publishing_models
from app.models import bundles as bundle_models
from app.models import integrations as integration_models
from app.models import analytics as analytics_models
from app.models import ai_hooks as ai_hooks_models


FORBIDDEN_IMPORT_PREFIXES = (
    "backend.services.accounting",
    "backend.services.crm",
    "backend.services.loyalty",
    "backend.services.communication",
    "backend.services.sports_center",
    "backend.services.hospitality",
    "backend.services.delivery",
    "backend.services.automation",
    "backend.services.notification",
    "backend.services.file_storage",
    "backend.services.identity_access",
    "backend.core_service",
)

FOUNDATION_MODELS = [
    models.ExperienceWorkspace,
    models.ExperienceLocaleProfile,
    models.ExperienceRole,
    models.ExperiencePermission,
    models.ExternalProviderConfig,
    models.RenderEngineRegistration,
    models.ExperienceConfiguration,
    models.ExperienceSetting,
    models.ExperienceAuditLog,
]

SITE_MODELS = [
    site_models.ExperienceSite,
    site_models.ExperiencePage,
    site_models.ExperienceSiteDomain,
]

COMPONENT_MODELS = [
    component_models.ExperienceComponent,
    component_models.ExperienceComponentVersion,
    component_models.PageComponentPlacement,
]

THEME_MODELS = [
    theme_models.ExperienceTheme,
    theme_models.ExperienceThemeVersion,
    theme_models.ExperienceLayout,
    theme_models.ExperienceLayoutVersion,
    theme_models.SiteThemeBinding,
    theme_models.PageLayoutBinding,
]

TEMPLATE_MODELS = [
    template_models.ExperienceTemplate,
    template_models.ExperienceTemplateVersion,
    template_models.TemplateInstantiation,
]

LOCALIZATION_MODELS = [
    localization_models.SiteLocaleBinding,
    localization_models.ExperienceLocalization,
    localization_models.ExperienceMediaRef,
]

FORM_MODELS = [
    form_models.ExperienceForm,
    form_models.FormSubmission,
    form_models.ExperienceSurvey,
    form_models.SurveyResponse,
    form_models.ExperienceAppointmentPage,
    form_models.AppointmentBookingRef,
]
PUBLISHING_MODELS = [
    publishing_models.PublishRelease,
    publishing_models.DomainEdgeBinding,
    publishing_models.SeoMetadata,
    publishing_models.SitemapShell,
    publishing_models.RobotsShell,
    publishing_models.PwaManifest,
]
BUNDLE_MODELS = [
    bundle_models.ExperienceBundleDefinition,
    bundle_models.ExperienceTenantBundle,
    bundle_models.ExperienceLicenseBinding,
    bundle_models.ExperienceFeatureToggle,
]
INTEGRATION_MODELS = [
    integration_models.ExperienceConsumerConnectorRegistration,
    integration_models.ExperienceConsumerConnectorDispatch,
    integration_models.ExperienceWidgetDefinition,
    integration_models.ExperienceWidgetInstance,
    integration_models.ExperienceNotifyDispatch,
]

ANALYTICS_MODELS = [
    analytics_models.ExperienceAnalyticsReportDefinition,
    analytics_models.ExperienceAnalyticsSnapshot,
]

AI_HOOKS_MODELS = [
    ai_hooks_models.ExperienceAiContentHookRegistration,
    ai_hooks_models.ExperienceAiContentDispatch,
]


def test_all_models_have_tenant_id():
    from app.core.database import Base
    import app.models  # noqa: F401

    skip = {"alembic_version"}
    for table in Base.metadata.tables.values():
        if table.name in skip:
            continue
        assert "tenant_id" in table.columns, f"{table.name} missing tenant_id"


def test_foundation_aggregates_are_independent():
    assert len(FOUNDATION_MODELS) == 9
    for model in FOUNDATION_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_site_aggregates_are_independent():
    assert len(SITE_MODELS) == 3
    for model in SITE_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_component_aggregates_are_independent():
    assert len(COMPONENT_MODELS) == 3
    for model in COMPONENT_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_theme_aggregates_are_independent():
    assert len(THEME_MODELS) == 6
    for model in THEME_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_template_aggregates_are_independent():
    assert len(TEMPLATE_MODELS) == 3
    for model in TEMPLATE_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_localization_aggregates_are_independent():
    assert len(LOCALIZATION_MODELS) == 3
    for model in LOCALIZATION_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_form_aggregates_are_independent():
    assert len(FORM_MODELS) == 6
    for model in FORM_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_publishing_aggregates_are_independent():
    assert len(PUBLISHING_MODELS) == 6
    for model in PUBLISHING_MODELS:
        assert hasattr(model, "tenant_id")
        assert not model.__mapper__.relationships


def test_bundle_aggregates_are_independent():
    assert len(BUNDLE_MODELS) == 4
    for model in BUNDLE_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "version")
        assert hasattr(model, "is_deleted")
        assert not model.__mapper__.relationships


def test_integration_aggregates_are_independent():
    assert len(INTEGRATION_MODELS) == 5
    for model in INTEGRATION_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "version")
        assert hasattr(model, "is_deleted")
        assert not model.__mapper__.relationships


def test_analytics_aggregates_are_independent():
    assert len(ANALYTICS_MODELS) == 2
    for model in ANALYTICS_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "version")
        assert hasattr(model, "is_deleted")
        assert not model.__mapper__.relationships


def test_ai_hooks_aggregates_are_independent():
    assert len(AI_HOOKS_MODELS) == 2
    for model in AI_HOOKS_MODELS:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "version")
        assert hasattr(model, "is_deleted")
        assert not model.__mapper__.relationships


def test_no_page_builder_engine_columns():
    forbidden = {
        "page_builder",
        "theme_engine",
        "template_engine",
        "cms_blob",
        "section_tree",
        "layout_engine",
        "forms_engine",
        "survey_engine",
        "booking_engine",
        "calendar_engine",
        "schedule_engine",
    }
    for model in (
        FOUNDATION_MODELS
        + SITE_MODELS
        + COMPONENT_MODELS
        + THEME_MODELS
        + TEMPLATE_MODELS
        + LOCALIZATION_MODELS
        + FORM_MODELS
        + PUBLISHING_MODELS
        + BUNDLE_MODELS
        + INTEGRATION_MODELS
        + ANALYTICS_MODELS
        + AI_HOOKS_MODELS
    ):
        columns = set(model.__table__.columns.keys())
        assert forbidden.isdisjoint(columns), model.__tablename__


def test_media_ref_has_no_binary_columns():
    columns = set(localization_models.ExperienceMediaRef.__table__.columns.keys())
    forbidden = {
        "binary",
        "blob",
        "bytes",
        "content_bytes",
        "file_bytes",
        "base64",
        "data_uri",
        "raw_content",
    }
    assert forbidden.isdisjoint(columns)
    assert "storage_file_ref" in columns


def test_permissions_defined():
    from app.permissions.definitions import ALL_PERMISSIONS, PERMISSION_PREFIXES

    assert "experience.view" in ALL_PERMISSIONS
    assert "experience.workspaces.create" in ALL_PERMISSIONS
    assert "experience.locale_profiles.manage" in ALL_PERMISSIONS
    assert "experience.render_engines.manage" in ALL_PERMISSIONS
    assert "experience.sites.create" in ALL_PERMISSIONS
    assert "experience.pages.manage" in ALL_PERMISSIONS
    assert "experience.domains.view" in ALL_PERMISSIONS
    assert "experience.sites.view" in ALL_PERMISSIONS
    assert "experience.components.create" in ALL_PERMISSIONS
    assert "experience.components.manage" in ALL_PERMISSIONS
    assert "experience.themes.create" in ALL_PERMISSIONS
    assert "experience.layouts.manage" in ALL_PERMISSIONS
    assert "experience.templates.create" in ALL_PERMISSIONS
    assert "experience.templates.manage" in ALL_PERMISSIONS
    assert "experience.locales.create" in ALL_PERMISSIONS
    assert "experience.locales.manage" in ALL_PERMISSIONS
    assert "experience.media_refs.create" in ALL_PERMISSIONS
    assert "experience.media_refs.manage" in ALL_PERMISSIONS
    assert "experience.forms.create" in ALL_PERMISSIONS
    assert "experience.forms.manage" in ALL_PERMISSIONS
    assert "experience.surveys.create" in ALL_PERMISSIONS
    assert "experience.appointments.manage" in ALL_PERMISSIONS
    assert "experience.publishing.manage" in ALL_PERMISSIONS
    assert "experience.seo.manage" in ALL_PERMISSIONS
    assert "experience.pwa.manage" in ALL_PERMISSIONS
    assert "experience.bundles.manage" in ALL_PERMISSIONS
    assert "experience.feature_toggles.manage" in ALL_PERMISSIONS
    assert "experience.consumer_connectors.manage" in ALL_PERMISSIONS
    assert "experience.widgets.manage" in ALL_PERMISSIONS
    assert "experience.notify.manage" in ALL_PERMISSIONS
    assert "experience.analytics.manage" in ALL_PERMISSIONS
    assert "experience.ai_hooks.manage" in ALL_PERMISSIONS
    assert "experience.audit.view" in ALL_PERMISSIONS
    for prefix in PERMISSION_PREFIXES:
        assert any(p.startswith(prefix.rstrip(".")) or p.startswith(prefix) for p in ALL_PERMISSIONS)


def test_events_defined():
    from app.events.types import ExperienceEventType

    assert ExperienceEventType.WORKSPACE_CREATED.value == "experience.workspace.created"
    assert (
        ExperienceEventType.LOCALE_PROFILE_CREATED.value
        == "experience.locale_profile.created"
    )
    assert (
        ExperienceEventType.RENDER_ENGINE_REGISTERED.value
        == "experience.render_engine.registered"
    )
    assert ExperienceEventType.SITE_CREATED.value == "experience.site.created"
    assert ExperienceEventType.PAGE_PUBLISHED.value == "experience.page.published"
    assert ExperienceEventType.SITE_DOMAIN_BOUND.value == "experience.site_domain.bound"
    assert ExperienceEventType.COMPONENT_CREATED.value == "experience.component.created"
    assert (
        ExperienceEventType.COMPONENT_VERSION_PUBLISHED.value
        == "experience.component_version.published"
    )
    assert (
        ExperienceEventType.PAGE_COMPONENT_PLACED.value
        == "experience.page_component.placed"
    )
    assert ExperienceEventType.THEME_CREATED.value == "experience.theme.created"
    assert ExperienceEventType.LAYOUT_ACTIVATED.value == "experience.layout.activated"
    assert (
        ExperienceEventType.SITE_THEME_ASSIGNED.value
        == "experience.site_theme.assigned"
    )
    assert (
        ExperienceEventType.PAGE_LAYOUT_ASSIGNED.value
        == "experience.page_layout.assigned"
    )
    assert ExperienceEventType.TEMPLATE_CREATED.value == "experience.template.created"
    assert (
        ExperienceEventType.TEMPLATE_VERSION_PUBLISHED.value
        == "experience.template_version.published"
    )
    assert (
        ExperienceEventType.TEMPLATE_INSTANTIATED.value
        == "experience.template.instantiated"
    )
    assert (
        ExperienceEventType.SITE_LOCALE_ASSIGNED.value
        == "experience.site_locale.assigned"
    )
    assert (
        ExperienceEventType.LOCALIZATION_UPSERTED.value
        == "experience.localization.upserted"
    )
    assert ExperienceEventType.MEDIA_REF_CREATED.value == "experience.media_ref.created"
    assert ExperienceEventType.FORM_CREATED.value == "experience.form.created"
    assert (
        ExperienceEventType.FORM_SUBMISSION_RECEIVED.value
        == "experience.form_submission.received"
    )
    assert ExperienceEventType.SURVEY_CREATED.value == "experience.survey.created"
    assert (
        ExperienceEventType.APPOINTMENT_BOOKING_REF_CREATED.value
        == "experience.appointment_booking_ref.created"
    )
    assert ExperienceEventType.PUBLISH_RELEASE_COMPLETED.value == "experience.publish_release.completed"
    assert ExperienceEventType.DOMAIN_EDGE_BOUND.value == "experience.domain_edge.bound"
    assert ExperienceEventType.SEO_METADATA_UPSERTED.value == "experience.seo_metadata.upserted"
    assert ExperienceEventType.PWA_MANIFEST_UPSERTED.value == "experience.pwa_manifest.upserted"
    assert ExperienceEventType.BUNDLE_DEFINITION_CREATED.value == "experience.bundle_definition.created"
    assert ExperienceEventType.FEATURE_TOGGLE_CREATED.value == "experience.feature_toggle.created"
    assert (
        ExperienceEventType.CONSUMER_CONNECTOR_REGISTRATION_CREATED.value
        == "experience.consumer_connector_registration.created"
    )
    assert (
        ExperienceEventType.WIDGET_DEFINITION_CREATED.value
        == "experience.widget_definition.created"
    )
    assert ExperienceEventType.NOTIFY_DISPATCH_SENT.value == "experience.notify_dispatch.sent"
    assert (
        ExperienceEventType.ANALYTICS_SNAPSHOT_REFRESHED.value
        == "experience.analytics_snapshot.refreshed"
    )
    assert (
        ExperienceEventType.AI_CONTENT_DISPATCH_GENERATED.value
        == "experience.ai_content_dispatch.generated"
    )


def test_no_duplicate_event_values():
    from app.events.types import ExperienceEventType

    values = [e.value for e in ExperienceEventType]
    assert len(values) == len(set(values))


def test_platform_provider_contracts_exist():
    from app.providers import (
        AIProvider,
        AccountingProvider,
        AutomationProvider,
        CRMProvider,
        CommunicationProvider,
        DeliveryProvider,
        HospitalityProvider,
        IdentityProvider,
        LoyaltyProvider,
        MarketplaceProvider,
        NotificationProvider,
        RenderEngineProvider,
        SportsCenterProvider,
        StorageProvider,
        ThemeMarketplaceProvider,
    )

    assert AccountingProvider is not None
    assert CRMProvider is not None
    assert StorageProvider is not None
    assert HospitalityProvider is not None
    assert MarketplaceProvider is not None
    assert RenderEngineProvider is not None
    assert ThemeMarketplaceProvider is not None
    assert AutomationProvider is not None
    assert DeliveryProvider is not None
    assert SportsCenterProvider is not None
    assert AIProvider is not None
    assert CommunicationProvider is not None
    assert LoyaltyProvider is not None
    assert IdentityProvider is not None
    assert NotificationProvider is not None


def test_no_foreign_service_imports():
    root = Path(__file__).resolve().parents[1]
    for path in root.rglob("*.py"):
        if "tests" in path.parts:
            continue
        tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    for prefix in FORBIDDEN_IMPORT_PREFIXES:
                        assert not alias.name.startswith(prefix), path
            elif isinstance(node, ast.ImportFrom) and node.module:
                for prefix in FORBIDDEN_IMPORT_PREFIXES:
                    assert not node.module.startswith(prefix), path
