# #region MaintenanceBannerRenderer [C:3] [TYPE Module] [SEMANTICS maintenance, banner, renderer, text]
# @BRIEF Banner text rendering and rebuild helpers. Build aggregated markdown from
#   active maintenance events, format with template, apply HTML escaping.
# @LAYER Service
# @RELATION DEPENDS_ON -> [MaintenanceModels]
# @RELATION DEPENDS_ON -> [SupersetDashboardsWriteMixin]

from datetime import datetime
import html
import re as _re
from typing import Any

from sqlalchemy.orm import Session

from ...core.logger import belief_scope, logger as app_logger
from ...core.superset_client import SupersetClient
from ...models.maintenance import (
    MaintenanceDashboardBanner,
    MaintenanceDashboardBannerStatus,
    MaintenanceDashboardState,
    MaintenanceDashboardStateStatus,
    MaintenanceEvent,
    MaintenanceEventStatus,
    MaintenanceSettings,
)


# #region build_banner_text [C:3] [TYPE Function] [SEMANTICS maintenance, banner, text, template, rendering]
# @BRIEF Build aggregated banner markdown text from events using settings template.
#   Single event: substitute {start_time}, {end_time}, {message} directly.
#   Multiple events: render each event as sub-block, combine as {message}.
#   Missing variables → empty string. Message is HTML-escaped.
# @PRE events is a non-empty list of dicts with start_time, end_time, message keys.
#   template is a non-empty string.
# @POST Returns str with substituted template or empty string on failure.
def build_banner_text(
    events: list[dict[str, Any]],
    template: str,
    display_timezone: str = "UTC",
) -> str:
    with belief_scope(
        "build_banner_text",
        f"events={len(events)}",
    ):
        if not events:
            app_logger.reflect("No events, returning empty string")
            return ""

        def _format_dt(dt_str: str | None) -> str:
            """Format datetime string for display."""
            if not dt_str:
                return "уточняется"
            try:
                dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
                return dt.strftime("%Y-%m-%d %H:%M")
            except (ValueError, TypeError):
                return dt_str or "уточняется"

        def _event_substitutions(event: dict[str, Any]) -> dict[str, str]:
            """Build substitution dict for a single event."""
            msg = event.get("message") or ""
            escaped_msg = html.escape(msg) if msg else ""
            return {
                "start_time": _format_dt(event.get("start_time")),
                "end_time": _format_dt(event.get("end_time")),
                "message": escaped_msg,
            }

        if len(events) == 1:
            subs = _event_substitutions(events[0])
            result = template
            for key, value in subs.items():
                result = result.replace(f"{{{key}}}", value)
            # Remove any remaining unreplaced variables
            result = _re.sub(r"\{[a-zA-Z_]+\}", "", result)
            app_logger.reflect("Banner text built (single event)")
            return result

        # Multiple events: build aggregated message, substitute as {message}
        event_blocks: list[str] = []
        for i, event in enumerate(events):
            subs = _event_substitutions(event)
            block = template
            for key, value in subs.items():
                block = block.replace(f"{{{key}}}", value)
            block = _re.sub(r"\{[a-zA-Z_]+\}", "", block)
            event_blocks.append(block)

        combined_message = "\n\n---\n\n".join(event_blocks)

        # Build aggregator template (use first event for start/end if uniform)
        result = template
        result = result.replace("{message}", combined_message)
        if len(events) == 1:
            subs = _event_substitutions(events[0])
            for key in ("start_time", "end_time"):
                result = result.replace(f"{{{key}}}", subs.get(key, ""))
        else:
            result = result.replace("{start_time}", "")
            result = result.replace("{end_time}", "")
        result = _re.sub(r"\{[a-zA-Z_]+\}", "", result)

        app_logger.reflect("Banner text built (multi-event)")
        return result
# #endregion build_banner_text


# #region _build_banner_text_for_dashboard [C:2] [TYPE Function]
# @BRIEF Build aggregated banner text for a banner based on all active events linked to it.
def _build_banner_text_for_dashboard(
    banner_id: str,
    db_session: Session,
    template: str,
    timezone_str: str,
) -> str:
    try:
        # Find all active dashboard states linked to this banner
        active_states = (
            db_session.query(MaintenanceDashboardState)
            .filter(
                MaintenanceDashboardState.banner_id == banner_id,
                MaintenanceDashboardState.status == MaintenanceDashboardStateStatus.ACTIVE,
            )
            .all()
        )

        if not active_states:
            return ""

        # Load events for each state
        events_data: list[dict[str, Any]] = []
        for state in active_states:
            ev = (
                db_session.query(MaintenanceEvent)
                .filter(MaintenanceEvent.id == state.event_id)
                .first()
            )
            if ev and ev.status in (
                MaintenanceEventStatus.ACTIVE,
                MaintenanceEventStatus.PARTIAL,
                MaintenanceEventStatus.APPLYING,
            ):
                events_data.append(
                    {
                        "start_time": ev.start_time.isoformat() if ev.start_time else None,
                        "end_time": ev.end_time.isoformat() if ev.end_time else None,
                        "message": ev.message or "",
                    }
                )

        if not events_data:
            return ""

        return build_banner_text(events_data, template, timezone_str)
    except Exception as e:
        app_logger.explore(
            "Failed to build banner text",
            extra={"banner_id": banner_id},
            error=str(e),
        )
        return ""
# #endregion _build_banner_text_for_dashboard


# #region rebuild_banner [C:3] [TYPE Function] [SEMANTICS maintenance, banner, rebuild, text, update]
# @BRIEF Rebuild aggregated banner text for a banner and update the Superset chart.
#   Query all active MaintenanceDashboardState records linked to banner,
#   build aggregated text via build_banner_text, update markdown chart in Superset.
# @PRE banner_id points to an active MaintenanceDashboardBanner with a valid chart_id.
# @POST Banner chart in Superset updated with current aggregated text. DB banner_text updated.
# @SIDE_EFFECT Modifies Superset chart. Writes DB.
# @RELATION DEPENDS_ON -> [build_banner_text]
# @RELATION DEPENDS_ON -> [SupersetDashboardsWriteMixin]
def rebuild_banner(
    banner_id: str,
    db_session: Session,
    superset_client: SupersetClient,
) -> bool:
    with belief_scope("rebuild_banner", f"banner_id={banner_id}"):
        app_logger.reason(
            "Rebuilding banner text",
            extra={"banner_id": banner_id},
        )

        banner = db_session.query(MaintenanceDashboardBanner).filter(
            MaintenanceDashboardBanner.id == banner_id
        ).first()
        if not banner:
            app_logger.explore(
                "Banner not found",
                extra={"banner_id": banner_id},
                error="BannerNotFound",
            )
            return False

        if banner.status != MaintenanceDashboardBannerStatus.ACTIVE:
            app_logger.reflect(
                "Banner not active, skipping",
                extra={"banner_id": banner_id, "status": banner.status.value},
            )
            return False

        # Load settings for template
        settings = db_session.query(MaintenanceSettings).filter(
            MaintenanceSettings.id == "default"
        ).first()
        template = settings.banner_template if settings else ""
        timezone_str = settings.display_timezone if settings else "UTC"

        # Build aggregated text
        banner_text = _build_banner_text_for_dashboard(
            banner_id, db_session, template, timezone_str
        )

        if not banner_text and banner.chart_id:
            # No active events — should be handled by end_maintenance, but clean up just in case
            app_logger.reflect(
                "No active events for banner, marking as REMOVED",
                extra={"banner_id": banner_id},
            )
            banner.status = MaintenanceDashboardBannerStatus.REMOVED
            db_session.flush()
            return True

        # Update banner on dashboard (native MARKDOWN in layout)
        if banner.chart_id:
            try:
                superset_client.update_banner_on_dashboard(
                    banner.dashboard_id, banner.chart_id, banner_text
                )
            except Exception as e:
                app_logger.explore(
                    "Failed to update banner on dashboard during rebuild",
                    extra={
                        "chart_id": banner.chart_id,
                        "banner_id": banner_id,
                        "dashboard_id": banner.dashboard_id,
                    },
                    error=str(e),
                )
                return False

        # Update DB
        banner.banner_text = banner_text
        db_session.flush()

        app_logger.reflect(
            "Banner rebuilt successfully",
            extra={"banner_id": banner_id},
        )
        return True
# #endregion rebuild_banner

# #endregion MaintenanceBannerRenderer
