# Issue #98: Schema migration helper for plugin feature columns.
# Issue #207: Plugin migration registry for observability.
"""ClickHouse schema migration for FeatureProvider plugin columns.

Adds plugin-defined columns to the ``open_deviation_bars`` table using
``ALTER TABLE ADD COLUMN IF NOT EXISTS`` (idempotent), then registers the
migration in ``plugin_migrations`` for observability.

**AI Agent Note**: Plugin columns are NOT in ``schema.sql`` — they are
managed here via the FeatureProvider protocol. See ``plugins/CLAUDE.md``
for the full plugin authoring guide.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .protocol import FeatureProvider

logger = logging.getLogger(__name__)

_REGISTRY_TABLE = "opendeviationbar_cache.plugin_migrations"
_BARS_TABLE = "opendeviationbar_cache.open_deviation_bars"


def _ensure_registry_table(client: object) -> None:
    """Create the plugin_migrations registry table if it doesn't exist."""
    try:
        client.command(f"""
            CREATE TABLE IF NOT EXISTS {_REGISTRY_TABLE} (
                plugin_name LowCardinality(String),
                plugin_version String,
                columns_added Array(String),
                applied_at DateTime64(3, 'UTC') DEFAULT now64(3, 'UTC')
            )
            ENGINE = ReplacingMergeTree(applied_at)
            ORDER BY (plugin_name)
        """)
    except (OSError, RuntimeError) as e:
        logger.debug("Registry table creation: %s", e)


def _register_migration(
    client: object,
    provider: FeatureProvider,
) -> None:
    """Record a plugin migration in the registry table."""
    columns_str = "[" + ",".join(f"'{c}'" for c in provider.columns) + "]"
    try:
        client.command(
            f"INSERT INTO {_REGISTRY_TABLE} "
            "(plugin_name, plugin_version, columns_added) VALUES "
            f"({{name:String}}, {{version:String}}, {columns_str})",
            parameters={
                "name": provider.name,
                "version": getattr(provider, "version", "unknown"),
            },
        )
    except (OSError, RuntimeError) as e:
        logger.debug("Failed to register migration for %s: %s", provider.name, e)


def migrate_plugin_columns(
    client: object,
    providers: list[FeatureProvider],
) -> None:
    """Add plugin feature columns to ClickHouse open_deviation_bars table.

    Uses ``ADD COLUMN IF NOT EXISTS`` for idempotent execution.
    All plugin columns are ``Nullable(Float64)`` — same type as
    inter-bar features. Registers each migration in the
    ``plugin_migrations`` table for observability.

    Parameters
    ----------
    client : clickhouse_connect.driver.Client
        Active ClickHouse client connection.
    providers : list[FeatureProvider]
        Discovered providers whose columns need to exist in the schema.
    """
    _ensure_registry_table(client)

    for provider in providers:
        for col in provider.columns:
            try:
                client.command(
                    f"ALTER TABLE {_BARS_TABLE} "
                    f"ADD COLUMN IF NOT EXISTS `{col}` Nullable(Float64) DEFAULT NULL"
                )
                logger.debug(
                    "Ensured column %s exists (provider: %s)",
                    col,
                    provider.name,
                )
            except (OSError, RuntimeError) as e:
                logger.warning(
                    "Failed to add column %s for provider %s: %s",
                    col,
                    provider.name,
                    e,
                )
        _register_migration(client, provider)
