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

import ast
import re
from pathlib import Path

from app.models.foundation import (
    Campaign,
    LoyaltyAuditLog,
    LoyaltyProgram,
    Member,
    MembershipTier,
    PointAccount,
    Reward,
)


FORBIDDEN_IMPORT_PREFIXES = (
    "app.services.accounting",
    "app.models.accounting",
    "backend.services.accounting",
    "backend.services.crm",
    "backend.services.automation",
    "backend.services.notification",
    "backend.services.file_storage",
    "backend.services.identity_access",
    "backend.core_service",
)


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():
    aggregates = {
        LoyaltyProgram,
        MembershipTier,
        Member,
        PointAccount,
        Reward,
        Campaign,
        LoyaltyAuditLog,
    }
    assert len(aggregates) == 7
    for model in aggregates:
        assert hasattr(model, "tenant_id")
        assert hasattr(model, "id")
        assert not model.__mapper__.relationships


def test_point_account_has_no_mutable_balance_column():
    forbidden = {"balance", "points_balance", "available_balance", "ledger_balance"}
    columns = set(PointAccount.__table__.columns.keys())
    assert forbidden.isdisjoint(columns)


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

    assert "loyalty.view" in ALL_PERMISSIONS
    assert "loyalty.programs.create" in ALL_PERMISSIONS
    assert "loyalty.tiers.view" in ALL_PERMISSIONS
    assert "loyalty.members.enroll" in ALL_PERMISSIONS
    assert "loyalty.members.activate" in ALL_PERMISSIONS
    assert "loyalty.members.transfer" in ALL_PERMISSIONS
    assert "loyalty.members.lifecycle.view" in ALL_PERMISSIONS
    assert "loyalty.point_accounts.manage" in ALL_PERMISSIONS
    assert "loyalty.rewards.create" in ALL_PERMISSIONS
    assert "loyalty.campaigns.manage" in ALL_PERMISSIONS
    assert "loyalty.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 LoyaltyEventType

    assert LoyaltyEventType.PROGRAM_CREATED.value == "loyalty.program.created"
    assert LoyaltyEventType.PROGRAM_DELETED.value == "loyalty.program.deleted"
    assert LoyaltyEventType.MEMBER_ENROLLED.value == "loyalty.member.enrolled"
    assert LoyaltyEventType.MEMBER_ACTIVATED.value == "loyalty.member.activated"
    assert LoyaltyEventType.MEMBER_FROZEN.value == "loyalty.member.frozen"
    assert LoyaltyEventType.MEMBER_TRANSFERRED.value == "loyalty.member.transferred"
    assert LoyaltyEventType.MEMBER_DELETED.value == "loyalty.member.deleted"
    assert (
        LoyaltyEventType.POINT_ACCOUNT_OPENED.value == "loyalty.point_account.opened"
    )
    assert LoyaltyEventType.REWARD_CREATED.value == "loyalty.reward.created"
    assert LoyaltyEventType.CAMPAIGN_UPDATED.value == "loyalty.campaign.updated"
    assert LoyaltyEventType.TIER_DELETED.value == "loyalty.tier.deleted"


def test_platform_provider_contracts_exist():
    from app.providers import (
        AIProvider,
        AnalyticsProvider,
        CRMProvider,
        CommunicationProvider,
        Customer360Provider,
        FileStorageProvider,
        ModuleIntegrationProvider,
        NotificationProvider,
    )

    assert NotificationProvider is not None
    assert AnalyticsProvider is not None
    assert Customer360Provider is not None
    assert AIProvider is not None
    assert ModuleIntegrationProvider is not None
    assert CRMProvider is not None
    assert CommunicationProvider is not None
    assert FileStorageProvider is not None


def test_outbox_model_is_tenant_aware():
    from app.models.outbox import OutboxEvent

    assert hasattr(OutboxEvent, "tenant_id")
    assert "outbox_events" in OutboxEvent.__table__.name or OutboxEvent.__tablename__ == "outbox_events"


def test_no_forbidden_service_imports():
    root = Path(__file__).resolve().parents[1]
    violations: list[str] = []
    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 forbidden in FORBIDDEN_IMPORT_PREFIXES:
                        if alias.name.startswith(forbidden) or forbidden in alias.name:
                            violations.append(f"{path}: import {alias.name}")
            elif isinstance(node, ast.ImportFrom) and node.module:
                for forbidden in FORBIDDEN_IMPORT_PREFIXES:
                    if node.module.startswith(forbidden) or forbidden in node.module:
                        violations.append(f"{path}: from {node.module}")
    assert violations == []


def test_api_ownership_is_loyalty_only():
    from app.api.v1 import api_router

    prefixes = {getattr(r, "path", "") for r in api_router.routes}
    joined = " ".join(sorted(prefixes))
    assert any("/programs" in p for p in prefixes) or "/programs" in joined
    assert any("/members" in p for p in prefixes) or "/members" in joined
    forbidden = (
        "automation",
        "customer360",
        "notification",
        "helpdesk",
        "analytics",
        "crm",
        "accounting",
    )
    for name in forbidden:
        assert not re.search(rf"(^|/){re.escape(name)}(/|$|\s)", joined), name


def test_folder_structure():
    root = Path(__file__).resolve().parents[2]
    required = [
        "app/models",
        "app/repositories",
        "app/services",
        "app/validators",
        "app/schemas",
        "app/events",
        "app/permissions",
        "app/providers",
        "app/api/v1",
        "app/tests",
        "alembic/versions",
        "README.md",
    ]
    for rel in required:
        assert (root / rel).exists(), f"missing {rel}"
