# #region service [C:5] [TYPE Module] [SEMANTICS fastapi, notification, dispatch, policy, routing]
#
# @BRIEF Orchestrates notification routing based on user preferences and policy context.
# @LAYER     Domain
# @RELATION DEPENDS_ON -> [NotificationProvider]
# @RELATION DEPENDS_ON -> [SMTPProvider]
# @RELATION DEPENDS_ON -> [TelegramProvider]
# @RELATION DEPENDS_ON -> [SlackProvider]
# @RELATION DEPENDS_ON -> [ValidationRecord]
# @RELATION DEPENDS_ON -> [ValidationPolicy]
# @RELATION DEPENDS_ON -> [UserDashboardPreference]
#
# @INVARIANT  NotificationService maintains singleton pattern for per-channel notifications
# @DATA_CONTRACT  NotificationChannelConfig -> NotificationRecipient
# @PRE  channel_config is loaded
# @POST  Notification dispatched via configured providers
# @SIDE_EFFECT  Sends notifications via configured providers

from fastapi import BackgroundTasks
from sqlalchemy.orm import Session

from ...core.config_manager import ConfigManager
from ...core.logger import belief_scope, logger
from ...models.llm import ValidationPolicy, ValidationRecord
from ...models.profile import UserDashboardPreference
from .providers import (
    NotificationProvider,
    SlackProvider,
    SMTPProvider,
    TelegramProvider,
)


# #region NotificationService [C:4] [TYPE Class]
# @BRIEF Routes validation reports to appropriate users and channels.
# @RELATION DEPENDS_ON -> [NotificationProvider]
# @RELATION DEPENDS_ON -> [ValidationRecord]
# @RELATION DEPENDS_ON -> [ValidationPolicy]
# @RELATION DEPENDS_ON -> [UserDashboardPreference]
# @PRE  Service receives a live DB session and configuration manager with notification payload settings.
# @POST  Service can resolve targets and dispatch provider sends without mutating validation records.
# @SIDE_EFFECT  Reads notification configuration, queries user preferences, and dispatches provider I/O.
# @DATA_CONTRACT  Input[ValidationRecord, Optional[ValidationPolicy], Optional[BackgroundTasks]] -> Output[None]
class NotificationService:
    # region NotificationService_init [TYPE Function]
    # @PURPOSE: Bind DB and configuration collaborators used for provider initialization and routing.
    # @RELATION BINDS_TO ->[NotificationService]
    # @RELATION DEPENDS_ON ->[ValidationPolicy]
    def __init__(self, db: Session, config_manager: ConfigManager):
        self.db = db
        self.config_manager = config_manager
        self._providers: dict[str, NotificationProvider] = {}
        self._initialized = False

    # endregion NotificationService_init

    # region _initialize_providers [TYPE Function]
    # @PURPOSE: Materialize configured notification channel adapters once per service lifetime.
    # @RELATION DEPENDS_ON ->[SMTPProvider]
    # @RELATION DEPENDS_ON ->[TelegramProvider]
    # @RELATION DEPENDS_ON ->[SlackProvider]
    def _initialize_providers(self):
        if self._initialized:
            return

        # In a real implementation, we would fetch these from NotificationConfig model
        # For now, we'll use a placeholder initialization logic
        # T033 will implement the UI/API for this.
        configs = self.config_manager.get_payload().get("notifications", {})

        if "smtp" in configs:
            self._providers["SMTP"] = SMTPProvider(configs["smtp"])
        if "telegram" in configs:
            self._providers["TELEGRAM"] = TelegramProvider(configs["telegram"])
        if "slack" in configs:
            self._providers["SLACK"] = SlackProvider(configs["slack"])

        self._initialized = True

    # endregion _initialize_providers

    # region dispatch_report [TYPE Function]
    # @PURPOSE: Route one validation record to resolved owners and configured custom channels.
    # @RELATION CALLS ->[EXT:method:_initialize_providers]
    # @RELATION CALLS ->[EXT:method:_should_notify]
    # @RELATION CALLS ->[EXT:method:_resolve_targets]
    # @RELATION CALLS ->[EXT:method:_build_body]
    # @PRE  record is persisted and providers can be initialized from configuration payload.
    # @POST  Eligible notification sends are scheduled in background or awaited inline.
    # @SIDE_EFFECT  Schedules or performs outbound provider sends and emits notification logs.
    # @DATA_CONTRACT  Input[ValidationRecord, Optional[ValidationPolicy], Optional[BackgroundTasks]] -> Output[None]
    async def dispatch_report(
        self,
        record: ValidationRecord,
        policy: ValidationPolicy | None = None,
        background_tasks: BackgroundTasks | None = None,
    ):
        """
        Route a validation record to owners and custom channels.
        @PRE: record is persisted.
        @POST: Dispatches async tasks for each resolved target.
        """
        with belief_scope(
            "NotificationService.dispatch_report", f"record_id={record.id}"
        ):
            self._initialize_providers()

            # 1. Determine if we should notify based on status and policy
            should_notify = self._should_notify(record, policy)
            if not should_notify:
                logger.reason(
                    f"Notification skipped for record {record.id} (status={record.status})",
                    extra={"src": "NotificationService"},
                )
                return

            # 2. Resolve targets (Owners + Custom Channels)
            targets = self._resolve_targets(record, policy)

            # 3. Dispatch
            subject = f"Dashboard Health Alert: {record.status}"
            body = self._build_body(record)

            for channel_type, recipient in targets:
                provider = self._providers.get(channel_type)
                if not provider:
                    logger.explore(
                        f"Unsupported or unconfigured channel: {channel_type}",
                        extra={"src": "NotificationService"},
                    )
                    continue

                if background_tasks:
                    background_tasks.add_task(provider.send, recipient, subject, body)
                else:
                    # Fallback to sync for tests or if no background_tasks provided
                    await provider.send(recipient, subject, body)

    # endregion dispatch_report

    # region _should_notify [TYPE Function]
    # @PURPOSE: Evaluate record status against effective alert policy.
    # @RELATION DEPENDS_ON ->[ValidationRecord]
    # @RELATION DEPENDS_ON ->[ValidationPolicy]
    def _should_notify(
        self, record: ValidationRecord, policy: ValidationPolicy | None
    ) -> bool:
        condition = policy.alert_condition if policy else "FAIL_ONLY"

        if condition == "ALWAYS":
            return True
        if condition == "WARN_AND_FAIL":
            return record.status in ("WARN", "FAIL")
        return record.status == "FAIL"

    # endregion _should_notify

    # region _resolve_targets [TYPE Function]
    # @PURPOSE: Resolve owner and policy-defined delivery targets for one validation record.
    # @RELATION CALLS ->[EXT:method:_find_dashboard_owners]
    # @RELATION DEPENDS_ON ->[ValidationRecord]
    # @RELATION DEPENDS_ON ->[ValidationPolicy]
    def _resolve_targets(
        self, record: ValidationRecord, policy: ValidationPolicy | None
    ) -> list[tuple]:
        targets = []

        # Owner routing
        if not policy or policy.notify_owners:
            owners = self._find_dashboard_owners(record)
            for owner_pref in owners:
                if not owner_pref.notify_on_fail:
                    continue

                if owner_pref.telegram_id:
                    targets.append(("TELEGRAM", owner_pref.telegram_id))

                email = owner_pref.email_address or getattr(
                    owner_pref.user, "email", None
                )
                if email:
                    targets.append(("SMTP", email))

        # Custom channels from policy
        if policy and policy.custom_channels:
            for channel in policy.custom_channels:
                # channel format: {"type": "SLACK", "target": "#alerts"}
                targets.append((channel.get("type"), channel.get("target")))

        return targets

    # endregion _resolve_targets

    # region _find_dashboard_owners [TYPE Function]
    # @PURPOSE: Load candidate dashboard owners from persisted profile preferences.
    # @RELATION DEPENDS_ON ->[ValidationRecord]
    # @RELATION DEPENDS_ON ->[UserDashboardPreference]
    def _find_dashboard_owners(
        self, record: ValidationRecord
    ) -> list[UserDashboardPreference]:
        # This is a simplified owner lookup.
        # In a real scenario, we'd query Superset for owners, then match them to our UserDashboardPreference.
        # For now, we'll return all users who have bound this dashboard's environment and have a username.

        # Placeholder: return all preferences that have a superset_username
        # (In production, we'd filter by actual ownership from Superset metadata)
        return (
            self.db.query(UserDashboardPreference)
            .filter(UserDashboardPreference.superset_username != None)
            .all()
        )

    # endregion _find_dashboard_owners

    # region _build_body [TYPE Function]
    # @PURPOSE: Format one validation record into provider-ready body text.
    # @RELATION DEPENDS_ON ->[ValidationRecord]
    def _build_body(self, record: ValidationRecord) -> str:
        return (
            f"Dashboard ID: {record.dashboard_id}\n"
            f"Environment: {record.environment_id}\n"
            f"Status: {record.status}\n\n"
            f"Summary: {record.summary}\n\n"
            f"Issues found: {len(record.issues)}"
        )

    # endregion _build_body


# #endregion NotificationService

# #endregion service
