"""Hierarchy-first application configuration governance helpers.

This module makes hierarchy projects a source-of-truth for application config
and provides publish/pull/audit operations against Snowflake APP_CONFIG tables.
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from .service import HierarchyService


class AppConfigGovernanceService:
    """Service to model app config as hierarchy and sync to Snowflake."""

    DEFAULT_MODULES = ["DATA_SOURCE", "RULES", "UI", "CACHE", "SECURITY"]

    def __init__(self, hierarchy_service: HierarchyService, data_dir: str = "data"):
        self.hierarchy_service = hierarchy_service
        self.data_dir = data_dir

    # ------------------------------------------------------------------
    # Hierarchy modeling helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _normalize_name(value: str) -> str:
        return str(value or "").strip().upper()

    def _children_index(self, project_id: str) -> Dict[Tuple[Optional[str], str], Dict[str, Any]]:
        nodes = self.hierarchy_service.list_hierarchies(project_id)
        out: Dict[Tuple[Optional[str], str], Dict[str, Any]] = {}
        for node in nodes:
            key = (node.get("parent_id"), self._normalize_name(node.get("hierarchy_name", "")))
            out[key] = node
        return out

    def _ensure_node(self, project_id: str, parent_id: Optional[str], name: str) -> Dict[str, Any]:
        wanted = self._normalize_name(name)
        idx = self._children_index(project_id)
        existing = idx.get((parent_id, wanted))
        if existing:
            return existing
        created = self.hierarchy_service.create_hierarchy(
            project_id=project_id,
            hierarchy_name=wanted,
            parent_id=parent_id,
        )
        node = self.hierarchy_service.get_hierarchy_by_id(created.id)
        return node if node else {"id": created.id, "hierarchy_id": created.hierarchy_id, "hierarchy_name": created.hierarchy_name, "parent_id": parent_id}

    def _path_for_node(self, node: Dict[str, Any], by_id: Dict[str, Dict[str, Any]]) -> List[str]:
        names: List[str] = []
        cur = node
        seen = set()
        while cur and cur.get("id") and str(cur["id"]) not in seen:
            seen.add(str(cur["id"]))
            names.append(self._normalize_name(cur.get("hierarchy_name", "")))
            parent_id = cur.get("parent_id")
            cur = by_id.get(str(parent_id)) if parent_id else None
        names.reverse()
        return [n for n in names if n]

    @staticmethod
    def _infer_value_type(value: Any) -> str:
        if isinstance(value, bool):
            return "BOOLEAN"
        if isinstance(value, (int, float)):
            return "NUMBER"
        if isinstance(value, (dict, list)):
            return "JSON"
        return "STRING"

    # ------------------------------------------------------------------
    # Public modeling operations
    # ------------------------------------------------------------------

    def init_config_project(
        self,
        project_name: str,
        description: str = "",
        app_name: str = "NON_OP_EDITOR",
        env: str = "PROD",
        scope: str = "DEFAULT",
        modules: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        proj = self.hierarchy_service.create_project(name=project_name, description=description)
        project_id = proj.id

        root = self._ensure_node(project_id, None, app_name)
        env_node = self._ensure_node(project_id, root.get("id"), env)
        scope_node = self._ensure_node(project_id, env_node.get("id"), scope)

        mod_names = modules or list(self.DEFAULT_MODULES)
        created_modules = []
        for mod in mod_names:
            mod_node = self._ensure_node(project_id, scope_node.get("id"), mod)
            created_modules.append(mod_node.get("hierarchy_name"))

        return {
            "status": "success",
            "project_id": project_id,
            "project_name": proj.name,
            "app_name": self._normalize_name(app_name),
            "env": self._normalize_name(env),
            "scope": self._normalize_name(scope),
            "modules_created": created_modules,
        }

    def create_configuration(
        self,
        project_id: str,
        app_name: str,
        env: str,
        scope: str,
        modules: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        root = self._ensure_node(project_id, None, app_name)
        env_node = self._ensure_node(project_id, root.get("id"), env)
        scope_node = self._ensure_node(project_id, env_node.get("id"), scope)
        created_modules = []
        for mod in (modules or list(self.DEFAULT_MODULES)):
            mod_node = self._ensure_node(project_id, scope_node.get("id"), mod)
            created_modules.append(mod_node.get("hierarchy_name"))
        return {
            "status": "success",
            "project_id": project_id,
            "app_name": self._normalize_name(app_name),
            "env": self._normalize_name(env),
            "scope": self._normalize_name(scope),
            "modules_created": created_modules,
        }

    def list_configurations(
        self,
        project_id: str,
        app_name: str = "",
        env: str = "",
    ) -> Dict[str, Any]:
        nodes = self.hierarchy_service.list_hierarchies(project_id)
        by_id = {str(n["id"]): n for n in nodes if n.get("id") is not None}
        acc: Dict[Tuple[str, str, str], int] = {}
        app_filter = self._normalize_name(app_name) if app_name else ""
        env_filter = self._normalize_name(env) if env else ""
        for node in nodes:
            path = self._path_for_node(node, by_id)
            if len(path) < 5:
                continue
            a, e, s = [self._normalize_name(p) for p in path[:3]]
            if app_filter and a != app_filter:
                continue
            if env_filter and e != env_filter:
                continue
            key = (a, e, s)
            acc[key] = acc.get(key, 0) + 1
        configs = [
            {"app_name": a, "env": e, "scope": s, "setting_count": cnt}
            for (a, e, s), cnt in sorted(acc.items())
        ]
        return {
            "status": "success",
            "project_id": project_id,
            "configurations": configs,
            "total_configurations": len(configs),
        }

    def add_or_update_setting(
        self,
        project_id: str,
        app_name: str,
        env: str,
        scope: str,
        module: str,
        setting_key: str,
        setting_value: Any,
        value_type: Optional[str] = None,
        description: str = "",
        owner: str = "",
        tags: Optional[List[str]] = None,
        is_secret: bool = False,
    ) -> Dict[str, Any]:
        n1 = self._ensure_node(project_id, None, app_name)
        n2 = self._ensure_node(project_id, n1.get("id"), env)
        n3 = self._ensure_node(project_id, n2.get("id"), scope)
        n4 = self._ensure_node(project_id, n3.get("id"), module)
        n5 = self._ensure_node(project_id, n4.get("id"), setting_key)

        vt = self._normalize_name(value_type or self._infer_value_type(setting_value))
        payload = {
            "setting_key": self._normalize_name(setting_key),
            "value_type": vt,
            "setting_value": setting_value,
            "is_secret": bool(is_secret),
            "description": description,
            "owner": owner,
            "tags": tags or [],
            "kind": "app_config_setting",
        }

        updated = self.hierarchy_service.update_hierarchy(
            project_id=project_id,
            hierarchy_id=n5.get("hierarchy_id"),
            updates={
                "description": description or f"{app_name}/{env}/{scope}/{module}/{setting_key}",
                "properties": payload,
            },
        )
        return {
            "status": "success",
            "project_id": project_id,
            "node_id": n5.get("id"),
            "hierarchy_id": n5.get("hierarchy_id"),
            "setting_path": f"{self._normalize_name(app_name)}/{self._normalize_name(env)}/{self._normalize_name(scope)}/{self._normalize_name(module)}/{self._normalize_name(setting_key)}",
            "node": updated or n5,
        }

    def validate_project(self, project_id: str) -> Dict[str, Any]:
        nodes = self.hierarchy_service.list_hierarchies(project_id)
        by_id = {str(n["id"]): n for n in nodes if n.get("id") is not None}
        issues: List[Dict[str, Any]] = []
        records: List[Dict[str, Any]] = []

        for node in nodes:
            path = self._path_for_node(node, by_id)
            props = node.get("properties") or {}
            if not isinstance(props, dict):
                props = {}

            if len(path) >= 5:
                app_name, env, scope, module, setting_key = [self._normalize_name(p) for p in path[:5]]
                value = props.get("setting_value", props.get("value", node.get("description", "")))
                value_type = self._normalize_name(props.get("value_type") or self._infer_value_type(value))
                if value is None or (isinstance(value, str) and not value.strip()):
                    issues.append(
                        {
                            "severity": "error",
                            "node_id": node.get("id"),
                            "path": "/".join(path[:5]),
                            "message": "setting_value is empty",
                        }
                    )
                if value_type not in {"STRING", "NUMBER", "BOOLEAN", "JSON", "DATE"}:
                    issues.append(
                        {
                            "severity": "error",
                            "node_id": node.get("id"),
                            "path": "/".join(path[:5]),
                            "message": f"Unsupported value_type: {value_type}",
                        }
                    )
                records.append(
                    {
                        "app_name": app_name,
                        "env": env,
                        "scope": scope,
                        "module": module,
                        "setting_key": setting_key,
                        "value_type": value_type,
                        "value": value,
                        "node_id": node.get("id"),
                        "hierarchy_id": node.get("hierarchy_id"),
                        "path": "/" + "/".join(path[:5]),
                    }
                )

        return {
            "status": "success",
            "project_id": project_id,
            "total_nodes": len(nodes),
            "publishable_settings": len(records),
            "issues": issues,
            "is_valid": len([i for i in issues if i.get("severity") == "error"]) == 0,
            "records_preview": records[:50],
        }

    def _collect_project_records(self, project_id: str) -> List[Dict[str, Any]]:
        nodes = self.hierarchy_service.list_hierarchies(project_id)
        by_id = {str(n["id"]): n for n in nodes if n.get("id") is not None}
        out: List[Dict[str, Any]] = []
        for node in nodes:
            path = self._path_for_node(node, by_id)
            if len(path) < 5:
                continue
            props = node.get("properties") or {}
            if not isinstance(props, dict):
                props = {}
            app_name, env, scope, module, setting_key = [self._normalize_name(p) for p in path[:5]]
            value = props.get("setting_value", props.get("value", node.get("description", "")))
            value_type = self._normalize_name(props.get("value_type") or self._infer_value_type(value))
            out.append(
                {
                    "app_name": app_name,
                    "env": env,
                    "scope": scope,
                    "module": module,
                    "setting_key": setting_key,
                    "value": value,
                    "value_type": value_type,
                    "node_id": node.get("id"),
                    "node_path": "/" + "/".join(path[:5]),
                }
            )
        return out

    def export_bundle(
        self,
        project_id: str,
        bundle_name: str = "",
        bundle_root: str = "",
        include_sql: bool = True,
    ) -> Dict[str, Any]:
        validation = self.validate_project(project_id)
        if validation.get("status") != "success":
            return {"error": "Unable to validate project before export", "validation": validation}
        if not validation.get("is_valid", False):
            return {"error": "Validation failed", "validation": validation}

        ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
        safe_name = "".join(ch if ch.isalnum() or ch in ("-", "_") else "_" for ch in (bundle_name or project_id))
        root = Path(bundle_root) if bundle_root else Path(self.data_dir)
        bundle_dir = root / f"app_config_bundle_{safe_name}_{ts}"
        bundle_dir.mkdir(parents=True, exist_ok=True)

        records = self._collect_project_records(project_id)

        settings_path = bundle_dir / "settings.json"
        settings_payload = {
            "project_id": project_id,
            "generated_at": ts,
            "record_count": len(records),
            "records": records,
        }
        settings_path.write_text(json.dumps(settings_payload, indent=2), encoding="utf-8")

        files_written = [str(settings_path)]
        publish_sql = None
        refresh_sql = None

        if include_sql:
            bootstrap_lines = [
                "-- Generated by AppConfigGovernanceService.export_bundle",
                "-- Canonical DataBridge APP_CONFIG bootstrap",
                "USE DATABASE TRANSFORMATION;",
                "CREATE SCHEMA IF NOT EXISTS CONFIGURATION;",
                "USE SCHEMA CONFIGURATION;",
                "",
                "CREATE TABLE IF NOT EXISTS APP_CONFIG (",
                "  APP_NAME STRING NOT NULL,",
                "  ENV STRING NOT NULL,",
                "  OPERATOR STRING NOT NULL DEFAULT 'DEFAULT',",
                "  MODULE STRING NOT NULL,",
                "  SETTING_KEY STRING NOT NULL,",
                "  SETTING_VALUE VARIANT,",
                "  VALUE_TYPE STRING,",
                "  IS_ACTIVE BOOLEAN DEFAULT TRUE,",
                "  VALID_FROM TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),",
                "  VALID_TO TIMESTAMP_NTZ,",
                "  UPDATED_BY STRING,",
                "  UPDATED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),",
                "  COMMENT STRING",
                ");",
                "",
                "CREATE TABLE IF NOT EXISTS APP_CONFIG_AUDIT (",
                "  AUDIT_ID NUMBER AUTOINCREMENT,",
                "  CHANGE_TS TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),",
                "  CHANGE_TYPE STRING,",
                "  APP_NAME STRING,",
                "  ENV STRING,",
                "  OPERATOR STRING,",
                "  MODULE STRING,",
                "  SETTING_KEY STRING,",
                "  OLD_SETTING_VALUE VARIANT,",
                "  NEW_SETTING_VALUE VARIANT,",
                "  VALUE_TYPE STRING,",
                "  CHANGED_BY STRING,",
                "  NOTE STRING",
                ");",
                "",
                "CREATE OR REPLACE VIEW V_APP_CONFIG_ACTIVE AS",
                "SELECT",
                "  APP_NAME,",
                "  ENV,",
                "  OPERATOR,",
                "  MODULE,",
                "  SETTING_KEY,",
                "  SETTING_VALUE,",
                "  VALUE_TYPE,",
                "  UPDATED_BY,",
                "  UPDATED_AT",
                "FROM APP_CONFIG",
                "WHERE IS_ACTIVE = TRUE",
                "  AND (VALID_TO IS NULL OR VALID_TO > CURRENT_TIMESTAMP())",
                "QUALIFY ROW_NUMBER() OVER (",
                "  PARTITION BY APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY",
                "  ORDER BY UPDATED_AT DESC, VALID_FROM DESC",
                ") = 1;",
            ]
            bootstrap_sql = bundle_dir / "00_bootstrap_app_config.sql"
            bootstrap_sql.write_text("\n".join(bootstrap_lines) + "\n", encoding="utf-8")
            files_written.append(str(bootstrap_sql))

            publish_lines = [
                "-- Generated by AppConfigGovernanceService.export_bundle",
                "USE DATABASE TRANSFORMATION;",
                "USE SCHEMA CONFIGURATION;",
                "",
            ]
            for r in records:
                value_expr = self._variant_expr(r["value"], r["value_type"])
                publish_lines.extend(
                    [
                        "MERGE INTO APP_CONFIG t",
                        "USING (",
                        "  SELECT",
                        f"    {self._lit(r['app_name'])} AS APP_NAME,",
                        f"    {self._lit(r['env'])} AS ENV,",
                        f"    {self._lit(r['scope'])} AS OPERATOR,",
                        f"    {self._lit(r['module'])} AS MODULE,",
                        f"    {self._lit(r['setting_key'])} AS SETTING_KEY,",
                        f"    {value_expr} AS SETTING_VALUE,",
                        f"    {self._lit(r['value_type'])} AS VALUE_TYPE",
                        ") s",
                        "ON t.APP_NAME = s.APP_NAME",
                        " AND t.ENV = s.ENV",
                        " AND t.OPERATOR = s.OPERATOR",
                        " AND t.MODULE = s.MODULE",
                        " AND t.SETTING_KEY = s.SETTING_KEY",
                        " AND t.IS_ACTIVE = TRUE",
                        "WHEN MATCHED THEN UPDATE SET",
                        "  SETTING_VALUE = s.SETTING_VALUE,",
                        "  VALUE_TYPE = s.VALUE_TYPE,",
                        "  UPDATED_AT = CURRENT_TIMESTAMP()",
                        "WHEN NOT MATCHED THEN INSERT (",
                        "  APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY, SETTING_VALUE, VALUE_TYPE,",
                        "  IS_ACTIVE, VALID_FROM, UPDATED_AT",
                        ") VALUES (",
                        "  s.APP_NAME, s.ENV, s.OPERATOR, s.MODULE, s.SETTING_KEY, s.SETTING_VALUE, s.VALUE_TYPE,",
                        "  TRUE, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()",
                        ");",
                        "",
                    ]
                )
            publish_sql = bundle_dir / "01_publish_app_config.sql"
            publish_sql.write_text("\n".join(publish_lines), encoding="utf-8")
            files_written.append(str(publish_sql))

            refresh_lines = [
                "USE DATABASE TRANSFORMATION;",
                "USE SCHEMA CONFIGURATION;",
                "CALL SP_REFRESH_APP_CONFIG_HIERARCHY_OBJECTS(NULL, NULL);",
            ]
            refresh_sql = bundle_dir / "02_refresh_hierarchy_objects.sql"
            refresh_sql.write_text("\n".join(refresh_lines) + "\n", encoding="utf-8")
            files_written.append(str(refresh_sql))

        runbook = bundle_dir / "RUNBOOK.md"
        runbook.write_text(
            "\n".join(
                [
                    "# APP_CONFIG Bundle Runbook",
                    "",
                    f"- Project ID: `{project_id}`",
                    f"- Generated (UTC): `{ts}`",
                    f"- Settings file: `{settings_path.name}`",
                    "",
                    "## Apply in Snowflake",
                    "1. Run `00_bootstrap_app_config.sql`",
                    "2. Run `01_publish_app_config.sql`",
                    "3. Run `02_refresh_hierarchy_objects.sql`",
                    "",
                    "## Promotion",
                    "Promote this bundle through dev -> test -> master using your standard PR flow.",
                ]
            )
            + "\n",
            encoding="utf-8",
        )
        files_written.append(str(runbook))

        manifest = bundle_dir / "manifest.json"
        manifest_payload = {
            "generated_at": ts,
            "bundle_type": "app_config_hierarchy_bundle",
            "project_id": project_id,
            "bundle_dir": str(bundle_dir),
            "include_sql": bool(include_sql),
            "files": [Path(p).name for p in files_written],
            "record_count": len(records),
        }
        manifest.write_text(json.dumps(manifest_payload, indent=2), encoding="utf-8")
        files_written.append(str(manifest))

        return {
            "status": "success",
            "project_id": project_id,
            "bundle_dir": str(bundle_dir),
            "record_count": len(records),
            "files_written": files_written,
            "manifest_file": str(manifest),
            "runbook_file": str(runbook),
        }

    # ------------------------------------------------------------------
    # Snowflake sync helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _load_toml(path: Path) -> Dict[str, Any]:
        try:
            import tomllib  # type: ignore
            return tomllib.loads(path.read_text(encoding="utf-8"))
        except Exception:
            try:
                import tomli  # type: ignore
                return tomli.loads(path.read_text(encoding="utf-8"))
            except Exception:
                return {}

    def _read_connection(self, connection_name: str, config_file: str) -> Dict[str, Any]:
        candidates = [Path(config_file)]
        candidates.append(Path.home() / ".snowflake" / "config.toml")
        candidates.append(Path.home() / ".snowflake" / "connections.toml")
        candidates.append(Path.home() / "AppData" / "Local" / "snowflake" / "config.toml")
        for p in candidates:
            if not p.exists():
                continue
            cfg = self._load_toml(p)
            conns = cfg.get("connections", {}) if isinstance(cfg, dict) else {}
            if isinstance(conns, dict) and isinstance(conns.get(connection_name), dict):
                return dict(conns[connection_name])
        return {}

    @staticmethod
    def _connect(conn_cfg: Dict[str, Any]):
        import snowflake.connector

        kwargs = {
            "account": conn_cfg.get("account"),
            "user": conn_cfg.get("user"),
            "password": conn_cfg.get("password"),
            "database": conn_cfg.get("database"),
            "schema": conn_cfg.get("schema"),
            "warehouse": conn_cfg.get("warehouse"),
            "role": conn_cfg.get("role"),
            "authenticator": conn_cfg.get("authenticator", "externalbrowser"),
            "client_store_temporary_credential": True,
            "client_request_mfa_token": True,
            "login_timeout": 30,
            "network_timeout": 120,
        }
        clean = {k: v for k, v in kwargs.items() if v not in (None, "")}
        return snowflake.connector.connect(**clean)

    @staticmethod
    def _lit(value: Any) -> str:
        return "'" + str(value).replace("'", "''") + "'"

    def _variant_expr(self, value: Any, value_type: str) -> str:
        vt = self._normalize_name(value_type or self._infer_value_type(value))
        if vt == "BOOLEAN":
            return f"TO_VARIANT({'TRUE' if bool(value) else 'FALSE'})"
        if vt == "NUMBER":
            try:
                n = float(value)
            except Exception:
                n = 0.0
            if n.is_integer():
                return f"TO_VARIANT({int(n)})"
            return f"TO_VARIANT({n})"
        if vt == "JSON":
            payload = value if isinstance(value, (dict, list)) else {"value": value}
            s = json.dumps(payload).replace("'", "''")
            return f"PARSE_JSON('{s}')"
        s = str(value).replace("'", "''")
        return f"TO_VARIANT('{s}')"

    def publish_to_snowflake(
        self,
        project_id: str,
        connection_name: str = "default",
        config_file: str = ".snowflake_cli/config.toml",
        changed_by: str = "hierarchy_publish",
        note: str = "Published from hierarchy",
        refresh_hierarchy_objects: bool = True,
    ) -> Dict[str, Any]:
        validation = self.validate_project(project_id)
        if not validation.get("is_valid", False):
            return {"error": "Validation failed", "validation": validation}
        records = validation.get("records_preview", [])
        # Recompute complete records (preview may be truncated)
        nodes = self.hierarchy_service.list_hierarchies(project_id)
        by_id = {str(n["id"]): n for n in nodes if n.get("id") is not None}
        full_records = []
        for node in nodes:
            path = self._path_for_node(node, by_id)
            if len(path) < 5:
                continue
            props = node.get("properties") or {}
            if not isinstance(props, dict):
                props = {}
            app_name, env, scope, module, setting_key = [self._normalize_name(p) for p in path[:5]]
            value = props.get("setting_value", props.get("value", node.get("description", "")))
            value_type = self._normalize_name(props.get("value_type") or self._infer_value_type(value))
            full_records.append(
                {
                    "app_name": app_name,
                    "env": env,
                    "scope": scope,
                    "module": module,
                    "setting_key": setting_key,
                    "value": value,
                    "value_type": value_type,
                    "node_id": node.get("id"),
                    "node_path": "/" + "/".join(path[:5]),
                }
            )

        conn_cfg = self._read_connection(connection_name=connection_name, config_file=config_file)
        if not conn_cfg:
            return {"error": f"Connection not found: {connection_name}"}

        conn = None
        try:
            conn = self._connect(conn_cfg)
            cur = conn.cursor()
            cur.execute("USE DATABASE TRANSFORMATION")
            cur.execute("USE SCHEMA CONFIGURATION")

            upserts = 0
            for r in full_records:
                value_expr = self._variant_expr(r["value"], r["value_type"])
                merge_sql = f"""
                MERGE INTO APP_CONFIG t
                USING (
                  SELECT
                    {self._lit(r['app_name'])} AS APP_NAME,
                    {self._lit(r['env'])} AS ENV,
                    {self._lit(r['scope'])} AS OPERATOR,
                    {self._lit(r['module'])} AS MODULE,
                    {self._lit(r['setting_key'])} AS SETTING_KEY,
                    {value_expr} AS SETTING_VALUE,
                    {self._lit(r['value_type'])} AS VALUE_TYPE,
                    {self._lit(changed_by)} AS UPDATED_BY,
                    {self._lit(note)} AS COMMENT
                ) s
                ON t.APP_NAME = s.APP_NAME
                 AND t.ENV = s.ENV
                 AND t.OPERATOR = s.OPERATOR
                 AND t.MODULE = s.MODULE
                 AND t.SETTING_KEY = s.SETTING_KEY
                 AND t.IS_ACTIVE = TRUE
                WHEN MATCHED THEN UPDATE SET
                  SETTING_VALUE = s.SETTING_VALUE,
                  VALUE_TYPE = s.VALUE_TYPE,
                  UPDATED_BY = s.UPDATED_BY,
                  UPDATED_AT = CURRENT_TIMESTAMP(),
                  COMMENT = s.COMMENT
                WHEN NOT MATCHED THEN INSERT (
                  APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY, SETTING_VALUE, VALUE_TYPE,
                  IS_ACTIVE, VALID_FROM, UPDATED_BY, UPDATED_AT, COMMENT
                ) VALUES (
                  s.APP_NAME, s.ENV, s.OPERATOR, s.MODULE, s.SETTING_KEY, s.SETTING_VALUE, s.VALUE_TYPE,
                  TRUE, CURRENT_TIMESTAMP(), s.UPDATED_BY, CURRENT_TIMESTAMP(), s.COMMENT
                )
                """
                cur.execute(merge_sql)
                upserts += 1

                bridge_sql = f"""
                MERGE INTO APP_CONFIG_HIERARCHY_BRIDGE t
                USING (
                  SELECT
                    {self._lit(r['app_name'])} AS APP_NAME,
                    {self._lit(r['env'])} AS ENV,
                    {self._lit(r['scope'])} AS OPERATOR,
                    {self._lit(r['module'])} AS MODULE,
                    {self._lit(r['setting_key'])} AS SETTING_KEY,
                    {self._lit(project_id)} AS HIERARCHY_PROJECT_ID,
                    {self._lit(r['node_id'])} AS HIERARCHY_NODE_ID,
                    {self._lit(r['node_path'])} AS HIERARCHY_PATH,
                    'BIDIRECTIONAL' AS SYNC_DIRECTION,
                    {self._lit(changed_by)} AS UPDATED_BY,
                    {self._lit(note)} AS COMMENT
                ) s
                ON t.APP_NAME = s.APP_NAME
                 AND t.ENV = s.ENV
                 AND t.OPERATOR = s.OPERATOR
                 AND t.MODULE = s.MODULE
                 AND t.SETTING_KEY = s.SETTING_KEY
                WHEN MATCHED THEN UPDATE SET
                  HIERARCHY_PROJECT_ID = s.HIERARCHY_PROJECT_ID,
                  HIERARCHY_NODE_ID = s.HIERARCHY_NODE_ID,
                  HIERARCHY_PATH = s.HIERARCHY_PATH,
                  SYNC_DIRECTION = s.SYNC_DIRECTION,
                  LAST_SYNC_TS = CURRENT_TIMESTAMP(),
                  LAST_SYNC_STATUS = 'SUCCESS',
                  LAST_SYNC_NOTE = s.COMMENT,
                  IS_ACTIVE = TRUE,
                  UPDATED_BY = s.UPDATED_BY,
                  UPDATED_AT = CURRENT_TIMESTAMP(),
                  COMMENT = s.COMMENT
                WHEN NOT MATCHED THEN INSERT (
                  APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY,
                  HIERARCHY_PROJECT_ID, HIERARCHY_NODE_ID, HIERARCHY_PATH,
                  SYNC_DIRECTION, LAST_SYNC_TS, LAST_SYNC_STATUS, LAST_SYNC_NOTE,
                  IS_ACTIVE, UPDATED_BY, UPDATED_AT, COMMENT
                ) VALUES (
                  s.APP_NAME, s.ENV, s.OPERATOR, s.MODULE, s.SETTING_KEY,
                  s.HIERARCHY_PROJECT_ID, s.HIERARCHY_NODE_ID, s.HIERARCHY_PATH,
                  s.SYNC_DIRECTION, CURRENT_TIMESTAMP(), 'SUCCESS', s.COMMENT,
                  TRUE, s.UPDATED_BY, CURRENT_TIMESTAMP(), s.COMMENT
                )
                """
                cur.execute(bridge_sql)

                audit_sql = f"""
                INSERT INTO APP_CONFIG_AUDIT (
                  CHANGE_TYPE, APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY,
                  OLD_SETTING_VALUE, NEW_SETTING_VALUE, VALUE_TYPE, CHANGED_BY, NOTE
                )
                SELECT
                  'HIERARCHY_PUBLISH',
                  {self._lit(r['app_name'])},
                  {self._lit(r['env'])},
                  {self._lit(r['scope'])},
                  {self._lit(r['module'])},
                  {self._lit(r['setting_key'])},
                  PARSE_JSON('null'),
                  {value_expr},
                  {self._lit(r['value_type'])},
                  {self._lit(changed_by)},
                  {self._lit(note)}
                """
                cur.execute(audit_sql)

            refreshed = False
            if refresh_hierarchy_objects:
                cur.execute("CALL SP_REFRESH_APP_CONFIG_HIERARCHY_OBJECTS(NULL, NULL)")
                refreshed = True

            return {
                "status": "success",
                "project_id": project_id,
                "settings_published": len(full_records),
                "app_config_upserts": upserts,
                "hierarchy_objects_refreshed": refreshed,
            }
        except Exception as exc:
            return {"error": str(exc)}
        finally:
            try:
                if conn is not None:
                    conn.close()
            except Exception:
                pass

    def pull_from_snowflake(
        self,
        project_id: str,
        app_name: str,
        env: str,
        operator: str = "",
        connection_name: str = "default",
        config_file: str = ".snowflake_cli/config.toml",
    ) -> Dict[str, Any]:
        conn_cfg = self._read_connection(connection_name=connection_name, config_file=config_file)
        if not conn_cfg:
            return {"error": f"Connection not found: {connection_name}"}

        conn = None
        try:
            conn = self._connect(conn_cfg)
            cur = conn.cursor()
            where = [
                f"APP_NAME = {self._lit(self._normalize_name(app_name))}",
                f"ENV = {self._lit(self._normalize_name(env))}",
            ]
            if operator.strip():
                where.append(f"OPERATOR = {self._lit(self._normalize_name(operator))}")
            q = f"""
            SELECT APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY, SETTING_VALUE, VALUE_TYPE
            FROM TRANSFORMATION.CONFIGURATION.V_APP_CONFIG_ACTIVE
            WHERE {' AND '.join(where)}
            ORDER BY APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY
            """
            cur.execute(q)
            rows = cur.fetchall()
            cols = [d[0] for d in cur.description]
            imported = 0
            for row in rows:
                rec = dict(zip(cols, row))
                imported += 1
                self.add_or_update_setting(
                    project_id=project_id,
                    app_name=str(rec["APP_NAME"]),
                    env=str(rec["ENV"]),
                    scope=str(rec["OPERATOR"]),
                    module=str(rec["MODULE"]),
                    setting_key=str(rec["SETTING_KEY"]),
                    setting_value=rec["SETTING_VALUE"],
                    value_type=str(rec["VALUE_TYPE"] or ""),
                    description=f"Pulled from APP_CONFIG {rec['APP_NAME']}/{rec['ENV']}/{rec['OPERATOR']}/{rec['MODULE']}/{rec['SETTING_KEY']}",
                )
            return {"status": "success", "project_id": project_id, "rows_imported": imported}
        except Exception as exc:
            return {"error": str(exc)}
        finally:
            try:
                if conn is not None:
                    conn.close()
            except Exception:
                pass

    def list_audit(
        self,
        app_name: str = "",
        env: str = "",
        operator: str = "",
        limit: int = 200,
        connection_name: str = "default",
        config_file: str = ".snowflake_cli/config.toml",
    ) -> Dict[str, Any]:
        conn_cfg = self._read_connection(connection_name=connection_name, config_file=config_file)
        if not conn_cfg:
            return {"error": f"Connection not found: {connection_name}"}
        conn = None
        try:
            conn = self._connect(conn_cfg)
            cur = conn.cursor()
            where = ["1=1"]
            if app_name.strip():
                where.append(f"APP_NAME = {self._lit(self._normalize_name(app_name))}")
            if env.strip():
                where.append(f"ENV = {self._lit(self._normalize_name(env))}")
            if operator.strip():
                where.append(f"OPERATOR = {self._lit(self._normalize_name(operator))}")
            q = f"""
            SELECT
              AUDIT_ID, CHANGE_TS, CHANGE_TYPE, APP_NAME, ENV, OPERATOR, MODULE, SETTING_KEY,
              OLD_SETTING_VALUE, NEW_SETTING_VALUE, VALUE_TYPE, CHANGED_BY, NOTE
            FROM TRANSFORMATION.CONFIGURATION.APP_CONFIG_AUDIT
            WHERE {' AND '.join(where)}
            ORDER BY CHANGE_TS DESC
            LIMIT {int(limit)}
            """
            cur.execute(q)
            rows = cur.fetchall()
            cols = [d[0] for d in cur.description]
            payload = [dict(zip(cols, row)) for row in rows]
            return {"status": "success", "total": len(payload), "rows": payload}
        except Exception as exc:
            return {"error": str(exc)}
        finally:
            try:
                if conn is not None:
                    conn.close()
            except Exception:
                pass
