import fastavro
import json
import requests
import snowflake.connector
import typer
import os
import csv
import re

from collections import defaultdict
from cruxctl.command_groups.auth.connectors.snowflake_conn import (
    get_snowflake_connection_instance,
)
from cruxctl.command_groups.auth.connectors.snowflake_conn import (
    get_snowflake_eu_connection_instance,
)
from cruxctl.command_groups.auth.connectors.snowflake_conn import (
    get_snowflake_vps_connection_instance,
)
from cruxctl.command_groups.bigsnow.orgs.base_orgs import BaseOrgs
from cruxctl.command_groups.profile.profile import get_current_profile, console
from cruxctl.common.utils.api_utils import set_api_token
from datetime import datetime, date, timedelta
from requests.exceptions import HTTPError
from snowflake.connector import DictCursor
from cruxctl.common.api.crux.v2 import CruxApiV2Ops
from cruxctl.common.api.crux.v4 import CruxApiV4Ops


class SnowflakeOrgs(BaseOrgs):
    def __init__(self):
        params = {"url": os.getenv("API_HOST"), "token": os.getenv("API_KEY")}
        self.crux_v2_api = CruxApiV2Ops(**params)
        self.crux_v4_api = CruxApiV4Ops(**params)

    def list_all(self, unique=False):
        print("\n" + "=" * 60)
        if unique:
            print("Listing all unique organizations")
        else:
            print("Listing all organizations")
        print("=" * 60 + "\n")

        print("Step 1: Connecting to Snowflake...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 2: Querying all organizations...")

        if unique:
            sql_query = (
                "SELECT DISTINCT "
                + ' "ORG", "NAME" '
                + 'FROM "CX_DATA_LAKE".PRIVATE.ORG_ACCOUNTS '
                + 'ORDER BY "NAME" ASC'
            )
        else:
            sql_query = (
                "SELECT DISTINCT "
                + ' "ORG", "NAME", "ACCOUNT" '
                + 'FROM "CX_DATA_LAKE".PRIVATE.ORG_ACCOUNTS '
                + 'ORDER BY "NAME", "ACCOUNT" ASC'
            )

        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            results = cur.fetchall()

            if not results:
                print("✗ No organizations found\n")
            else:
                print("✓ Found {} organization(s)\n".format(len(results)))
                print("=" * 60)
                if unique:
                    print("UNIQUE ORGANIZATIONS")
                else:
                    print("ALL ORGANIZATIONS")
                print("=" * 60 + "\n")

                for idx, data in enumerate(results, 1):
                    print("Org #{}:".format(idx))
                    print("  Organization: {}".format(data.get("ORG")))
                    print("  Name:         {}".format(data.get("NAME")))
                    if not unique:
                        print("  Account:      {}".format(data.get("ACCOUNT")))
                    print()
        finally:
            cur.close()
            conn.close()

        print("=" * 60)
        print("List all completed")
        print("=" * 60 + "\n")

    def list(self, org_id, org_name=None):
        """List organization details from Snowflake."""
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        try:
            # Query Snowflake directly - no API calls needed
            sql_query = (
                "SELECT DISTINCT "
                + '"ORG", "NAME", "ACCOUNT" '
                + 'FROM "CX_DATA_LAKE".PRIVATE.ORG_ACCOUNTS '
                + f"WHERE \"ORG\" = '{org_id}' "
                + 'ORDER BY "NAME", "ACCOUNT" ASC'
            )

            cur.execute(sql_query)
            results = cur.fetchall()

            if not results:
                print(f"No organization found for org_id={org_id}")
            else:
                for row in results:
                    print(row)

        except Exception as e:
            print(f"Error querying Snowflake: {e}")
        finally:
            cur.close()
            conn.close()

    def add(self, org_id):
        print(f"\n{'=' * 60}")
        print(f"Adding organization: {org_id}")
        print(f"{'=' * 60}\n")

        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        select_sql = (
            "SELECT ORG, KEY FROM CX_DATA_LAKE.PRIVATE.ORG_ENV_CONFIG "
            + f" WHERE ORG = '{org_id}' LIMIT 1"
        )

        print("Step 1: Checking if organization already exists...")
        print(f"Executing SQL:\n{select_sql}\n")

        try:
            cur.execute(select_sql)
            result = cur.fetchone()
            print(f"Query result: {result}\n")
        except Exception as e:
            print(f"✗ Error running query: {e}\n")
            cur.close()
            conn.close()
            return

        if result:
            print(f"✓ Org id {org_id} already exists. Skipping insert.\n")
        else:
            insert_sql = (
                'INSERT INTO "CX_DATA_LAKE".PRIVATE.ORG_ENV_CONFIG '
                '("ORG","KEY","VALUE","ENVIRONMENT") VALUES '
                + f"('{org_id}','DATABASE', 'CX_DATA_LAKE', 'SNOWFLAKE')"
            )

            print("Step 2: Inserting new organization...")
            print(f"Executing SQL:\n{insert_sql}\n")

            try:
                cur.execute(insert_sql)
                print(f"✓ Org id {org_id} inserted successfully\n")
            except Exception as e:
                print(f"✗ Error inserting org: {e}\n")

        print(f"{'=' * 60}")
        print(f"Organization add completed for {org_id}")
        print(f"{'=' * 60}\n")

        cur.close()
        conn.close()
        return

    def delete(self, org_id):
        print(f"\n{'=' * 60}")
        print(f"Deleting organization: {org_id}")
        print(f"{'=' * 60}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print(f"User roles: {user_roles}\n")

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type DELETE to confirm")
                if confirmation != "DELETE":
                    typer.echo("Aborted.")
                    print(f"✗ {org_id} You are not Authorized for Delete operation\n")
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
                print("Deleting entitlements...")
            else:
                typer.echo("✗ You are not authorized for Delete")
                raise typer.Exit(code=1)
        else:
            print(f"✗ {org_id} Not Authorized\n")
            raise typer.Exit(code=1)

        print("\nStep 2: Connecting to Snowflake...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 3: Deleting organization from ORG_ENV_CONFIG...")
        delete_sql = (
            "DELETE FROM CX_DATA_LAKE.PRIVATE.ORG_ENV_CONFIG WHERE "
            + f" ORG = '{org_id}' and KEY = 'DATABASE'"
            f" and value = 'CX_DATA_LAKE' and environment = 'SNOWFLAKE'"
        )
        print(f"Executing SQL:\n{delete_sql}\n")

        try:
            cur.execute(delete_sql)
            print(f"✓ Deleted ORG account for org_id: {org_id}\n")
        except Exception as err:
            print(f"✗ Error deleting records for org_id {org_id}: {err}\n")

        print(f"{'=' * 60}")
        print(f"Organization delete completed for {org_id}")
        print(f"{'=' * 60}\n")

        cur.close()
        conn.close()

        return

    def accounts_list(self, org_id):
        print("\n" + "=" * 60)
        print("Listing accounts for organization: {}".format(org_id))
        print("=" * 60 + "\n")

        print("Step 1: Connecting to Snowflake...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 2: Querying organization accounts...")
        sql_query = (
            "SELECT ORG, NAME, ACCOUNT, ORG_ACCT_NAME,"
            " SOURCE_ACCOUNT, TYPE, CLIENT_SCHEMA "
            + "FROM CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS"
            + " WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            results = cur.fetchall()

            if not results:
                print("✗ No accounts found for org {}\n".format(org_id))
            else:
                print("✓ Found {} account(s)\n".format(len(results)))
                print("=" * 60)
                print("ACCOUNTS FOR ORGANIZATION: {}".format(org_id))
                print("=" * 60 + "\n")

                for idx, row in enumerate(results, 1):
                    print("Account #{}:".format(idx))
                    print("  Organization:     {}".format(row.get("ORG")))
                    print("  Name:             {}".format(row.get("NAME")))
                    print("  Account:          {}".format(row.get("ACCOUNT")))
                    print("  Org Account Name: {}".format(row.get("ORG_ACCT_NAME")))
                    print("  Source Account:   {}".format(row.get("SOURCE_ACCOUNT")))
                    print("  Type:             {}".format(row.get("TYPE")))
                    print(
                        "  Client Schema:    {}".format(
                            row.get("CLIENT_SCHEMA") or "None"
                        )
                    )
                    print()

        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            return
        finally:
            cur.close()
            conn.close()

        print("=" * 60)
        print("Accounts list completed for {}".format(org_id))
        print("=" * 60 + "\n")

        return

    def accounts_add(self, org_id, locator, acct_id, client_schema):
        print(f"\n{'=' * 60}")
        print(f"Adding account for organization: {org_id}")
        print(
            f"Locator: {locator}, Account ID: {acct_id}, Client Schema: {client_schema}"
        )
        print(f"{'=' * 60}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        source = "ts91674"

        # Get organization details
        print("Step 1: Fetching organization details from API...")
        url = (
            f"https://api.cruxinformatics.com/v2/ops"
            f"/orgs?filter=externalId.eq.{org_id}&offset=0&limit=100"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
            return
        except Exception as err:
            print(f"✗ Other error occurred: {err}")
            return

        response_json = response.json()
        name = response_json[0]["name"]
        print(f"✓ Organization name: {name}\n")

        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        # Get max ID
        print("Step 2: Getting next available ID...")
        sql_query = "SELECT MAX(ID) FROM CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS "
        print(f"Executing SQL:\n{sql_query}\n")

        max_id = 0
        share_name = ""
        schema_name = ""
        try:
            cur.execute(sql_query)
            for data in cur:
                max_id = data["MAX(ID)"]
            print(f"✓ Max ID: {max_id}, Next ID will be: {max_id + 1}\n")
        except Exception as err:
            print(f"✗ Error occurred: {err}\n")
            return

        # Handle client schema
        if client_schema != "N/A":
            print(f"Step 3: Creating client schema: {client_schema}...")
            name = "_".join([org_id, client_schema])

            client_sql = f"show schemas like '%{name}%'"
            print(f"Executing SQL:\n{client_sql}\n")

            cur.execute(client_sql)
            for data in cur:
                schema_name = data["name"]

            if schema_name == name:
                print(f"✓ Schema already exists: {schema_name}\n")
            else:
                schema_name = "_".join([org_id, client_schema])
                print(f"Creating new client schema: {schema_name}...")
                share_name, errors = SnowflakeOrgs.create_client_schema(
                    cur, name, org_id, locator
                )
                if errors:
                    print(f"✗ Errors: {errors}\n")
                    return
        else:
            print("Step 3: Creating client share (no custom schema)...")
            share_name, errors = SnowflakeOrgs.create_client_share(
                cur, name, org_id, locator
            )
            if errors:
                print(f"✗ Errors: {errors}\n")
                return

        # Insert into ORG_ACCOUNTS
        print("Step 4: Inserting account into ORG_ACCOUNTS...")
        sql_query = (
            "INSERT INTO CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS "
            + '("ID", "ORG", "TYPE", "ACCOUNT", "NAME",'
            + ' "SOURCE_ACCOUNT", "ORG_ACCT_NAME", "CLIENT_SCHEMA") VALUES'
            + f" ({max_id + 1}, '{org_id}', 'OLR', "
            + f"'{locator}', '{name.upper()}', '{source}', "
            + f"'{acct_id}', '{schema_name}' )"
        )
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            print("✓ Account inserted successfully\n")
        except Exception as err:
            print(f"✗ Error occurred: {err}\n")
            return

        # Get or create listing
        print("Step 5: Managing listing...")
        target_accts = []
        listing_found = False
        listing_name = f"CRUX_OLR_LISTING_FOR_{org_id}"

        sql_query = f"SHOW LISTINGS LIKE '%{org_id}%' "
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            result = cur.fetchone()
            if result:
                existing_targets = result.get("target_accounts", "")
                target_accts = existing_targets.split(",") if existing_targets else []
                listing_found = True
                print(f"✓ Found existing listing: {listing_name}")
                print(f"  Existing target accounts: {target_accts}\n")
        except Exception as err:
            print(f"✗ Error occurred: {err}\n")

        target_accts = [acct.strip() for acct in target_accts if acct.strip()]
        target_accts.append(acct_id)
        unique_targets = list(set(target_accts))
        t_accts_json = json.dumps(unique_targets)

        if listing_found:
            print(f"Updating existing listing with new account: {acct_id}...")
            listing_ddl = (
                f'ALTER LISTING  "{listing_name}" AS \n'
                f"$$ \n"
                f' title: "{listing_name}"\n'
                f' description: "{org_id} listing" \n'
                f" targets: \n"
                f"     accounts: {t_accts_json} \n"
                f" listing_terms: \n"
                f'     type: "OFFLINE" \n'
                f" auto_fulfillment: \n"
                f"     refresh_schedule: 15 MINUTE \n"
                f"     refresh_type: SUB_DATABASE \n"
                f"$$ \n"
            )
        else:
            print(f"Creating new listing: {listing_name}...")
            listing_ddl = (
                f'CREATE EXTERNAL LISTING "{listing_name}" \n'
                f'SHARE "{share_name}" AS \n '
                f"$$ \n"
                f' title: "{listing_name}"\n'
                f' description: "{org_id} listing" \n'
                f' profile : "10208F74_1A60_41A5_A377_658400AD9D90"\n'
                f" listing_terms: \n"
                f'     type: "OFFLINE" \n'
                f" targets: \n"
                f'     accounts: ["{acct_id}"] \n'
                f" auto_fulfillment: \n"
                f"     refresh_schedule: 15 MINUTE \n"
                f"     refresh_type: SUB_DATABASE \n"
                f"$$ \n"
            )

        print(f"Executing DDL:\n{listing_ddl}\n")

        try:
            cur.execute(listing_ddl)
            print(
                f"✓ Listing {'updated' if listing_found else 'created'}: {listing_name}\n"
            )
        except snowflake.connector.errors.ProgrammingError as e:
            print(f"✗ Listing failed: {e.errno}\n")

        # Fetch active subscriptions and create service restrictions
        print("Step 6: Fetching active CDU subscriptions...")
        url = (
            f"https://api.cruxinformatics.com/v2/ops/"
            f"orgs?filter=externalId.eq.{org_id}&offset=0&limit=100"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        orgId = response_json[0]["id"]
        print(f"✓ Organization ID: {orgId}\n")

        url = (
            f"https://api.cruxinformatics.com/v2/"
            f"ops/cduSubscriptions?order=cduId&"
            f"filter=orgId.eq.{orgId},status.eq"
            f".Active&limit=1000&offset=0"
        )
        print(f"Fetching subscriptions from: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()

        now = datetime.now()
        now_str = now.strftime("%Y-%m-%dT00:00:00.000+0000T")

        if response_json:
            print(
                f"Step 7: Creating service restrictions for {len(response_json)} CDU(s)..."
            )
            for i in response_json:
                cduid = i["cduId"].strip("Cx")
                print(f"\n  Processing CDU: {cduid}")

                sql_query = (
                    "SELECT CDU_ID FROM "
                    "CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS  "
                    + f"WHERE ORG = '{org_id}'"
                    + f" and CDU_ID = '{cduid}' LIMIT 1"
                )
                print(f"  Checking existing restriction:\n  {sql_query}")

                try:
                    cur.execute(sql_query)
                    result = cur.fetchone()
                    if not result:
                        sql_query = (
                            "INSERT INTO "
                            "CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
                            + '("ORG", "SVC_ACCT", "CDU_ID", "SVC_ST_DT", '
                            + '"SVC_ET_DT", "R1_SDT","R1_EDT", '
                            + '"R2_SDT", "R2_EDT", "SVC_BLOCK_DT", "STATUS",'
                            + ' "LICENSE", "SF", "FTP", "S3", '
                            '"API") VALUES \n'
                            + f"( '{org_id}', '{locator}', "
                            + f"'{cduid}','{now_str}',"
                            f" '2050-12-31T00:00:00.000+0000',"
                            f"'1970-12-31T00:00:00.000+0000', "
                            f"'2030-12-31T00:00:00.000+0000',"
                            f"'2050-12-31T00:00:00.000+0000', "
                            f"'2050-12-31T00:00:00.000+0000', "
                            f"'2050-12-31T00:00:00.000+0000', 'ACTIVE',"
                            + "'FULL', 'TRUE', 'FALSE', 'FALSE', 'FALSE')"
                        )
                        print(f"  Inserting restriction:\n  {sql_query}")

                        try:
                            cur.execute(sql_query)
                            print(f"  ✓ Service restriction created for CDU {cduid}")
                        except Exception as err:
                            print(f"  ✗ Error occurred: {err}")
                            return
                    else:
                        print(f"  ✓ Service restriction already exists for CDU {cduid}")

                except Exception as err:
                    print(f"  ✗ Error occurred: {err}")

        print(f"\n{'=' * 60}")
        print("✓ OLR ACCOUNT has been created successfully")
        print(f"{'=' * 60}\n")

        cur.close()
        conn.close()
        return

    def accounts_delete(self, org_id, locator, acct_id):
        print(f"\n{'=' * 60}")
        print(f"Deleting account for organization: {org_id}")
        print(f"Locator: {locator}")
        print(f"Account ID: {acct_id}")
        print(f"{'=' * 60}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print(f"User roles: {user_roles}\n")

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type DELETE to confirm")
                if confirmation != "DELETE":
                    typer.echo("Aborted.")
                    print(f"✗ {org_id} You are not Authorized for Delete operation\n")
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
                print("Authorized for Delete")
            else:
                typer.echo("✗ You are not authorized for Delete")
                raise typer.Exit(code=1)
        else:
            print(f"✗ {org_id} Not Authorized\n")
            raise typer.Exit(code=1)

        print("\nStep 2: Connecting to Snowflake...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 3: Finding share for organization...")
        share_name = ""
        select_sql = f"SHOW SHARES LIKE '%{org_id}%' "
        print(f"Executing SQL:\n{select_sql}\n")

        try:
            cur.execute(select_sql)
            for data in cur:
                if "SHARE" in data["name"]:
                    share_name = data["name"]
                    print(f"✓ Found share: {share_name}")
        except Exception as e:
            print(f"✗ Error running query: {e}\n")

        if share_name:
            print()
        else:
            print("⚠ No share found\n")

        print("Step 4: Checking listing for organization...")
        listing_found = False
        target_accts = []
        listing_name = ""
        sql_query = f"SHOW LISTINGS LIKE '%{org_id}%' "
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            result = cur.fetchone()
            if result:
                existing_targets = result.get("target_accounts", "")
                target_accts = existing_targets.split(",") if existing_targets else []
                listing_found = True
                listing_name = result.get("name", "")
                print(f"✓ Found listing: {listing_name}")
                print(f"  Current target accounts: {target_accts}\n")
        except Exception as err:
            print(f"✗ Other error occurred: {err}\n")

        if listing_found and acct_id in target_accts:
            print("Step 5: Updating listing to remove account...")
            target_accts = [acct for acct in target_accts if acct != acct_id]

            if target_accts:
                targets_json = json.dumps(target_accts)
                print(f"  New target accounts: {target_accts}")

                listing_sql = f"""
                    ALTER LISTING "{listing_name}" AS
                    $$
                      title: "{listing_name}"
                      description: "{org_id} listing"
                      listing_terms:
                        type: "OFFLINE"
                      targets:
                        accounts: {targets_json}
                      auto_fulfillment:
                        refresh_schedule: 15 MINUTE
                        refresh_type: SUB_DATABASE
                    $$
                    """
                print(f"Executing DDL:\n{listing_sql}\n")

                try:
                    cur.execute(listing_sql)
                    print(f"✓ Removed account {acct_id} from listing {listing_name}\n")
                except snowflake.connector.errors.ProgrammingError as e:
                    print(f"✗ Failed to update listing: {e.msg} [Error {e.errno}]\n")
            else:
                print("  No accounts left in listing. Dropping listing...")

                unpublish_sql = f'ALTER LISTING "{listing_name}" UNPUBLISH'
                print(f"Executing SQL:\n{unpublish_sql}")

                drop_sql = f'DROP LISTING IF EXISTS "{listing_name}"'
                print(f"Executing SQL:\n{drop_sql}")

                unset_sql = f'ALTER SHARE "{share_name}" UNSET LISTING'
                print(f"Executing SQL:\n{unset_sql}\n")

                try:
                    cur.execute(unpublish_sql)
                    cur.execute(drop_sql)
                    cur.execute(unset_sql)
                    print(
                        f"✓ Listing {listing_name} dropped and share {share_name} unset.\n"
                    )
                except Exception as e:
                    print(f"✗ Error cleaning up listing and share: {e}\n")
        else:
            if listing_found:
                print(
                    f"⚠ Account {acct_id} not found in listing {listing_name} — no changes made.\n"
                )
            else:
                print(f"⚠ No listing found for org_id {org_id}.\n")

        source = "ts91674"

        print("Step 6: Deleting account from ORG_ACCOUNTS...")
        sql_query = (
            f"DELETE FROM CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS"
            f" where ORG = '{org_id}' and ACCOUNT='{locator}'"
            f" and TYPE='OLR' and SOURCE_ACCOUNT = '{source}' "
            + f"AND ORG_ACCT_NAME = '{acct_id}' "
        )
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            print("✓ Account deleted from ORG_ACCOUNTS\n")
        except Exception as err:
            print(f"✗ Other error occurred: {err}\n")
            return

        print("Step 7: Deleting service restrictions...")
        sql_query = (
            f"DELETE FROM "
            f"CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
            f"where ORG = '{org_id}' and SVC_ACCT ='{locator}'"
        )
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            print("✓ Service restrictions deleted\n")
        except Exception as err:
            print(f"✗ Other error occurred: {err}\n")
            return

        print(f"{'=' * 60}")
        print(f"✓ {locator} ACCOUNT has been deleted.")
        print(f"{'=' * 60}\n")

        cur.close()
        conn.close()
        return

    def entitlements_list(self, org_id):
        print("\n" + "=" * 60)
        print("Listing entitlements for organization: {}".format(org_id))
        print("=" * 60 + "\n")

        print("Step 1: Connecting to Snowflake...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        # Get all accounts for this org
        print("Step 2: Fetching organization accounts...")
        accounts_sql = (
            "SELECT DISTINCT ACCOUNT FROM"
            " CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(accounts_sql + "\n")

        accounts = set()
        try:
            cur.execute(accounts_sql)
            for row in cur:
                accounts.add(row["ACCOUNT"])
            print(
                "✓ Found {} account(s): {}\n".format(len(accounts), ", ".join(accounts))
            )
        except Exception as err:
            print("✗ Error fetching accounts: {}\n".format(err))
            cur.close()
            conn.close()
            return

        if not accounts:
            print("✗ No accounts found for org {}\n".format(org_id))
            cur.close()
            conn.close()
            return

        # Get all entitled CDUs
        print("Step 3: Fetching entitled CDUs...")
        cdus_sql = (
            "SELECT DISTINCT CDU_ID FROM CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS "
            "WHERE ORG_ID = '{}' AND ENT_TYPE = 'CATALOG'".format(org_id)
        )
        print("Executing SQL:")
        print(cdus_sql + "\n")

        cdus = set()
        try:
            cur.execute(cdus_sql)
            for row in cur:
                cdus.add(row["CDU_ID"])
            print("✓ Found {} entitled CDU(s): {}\n".format(len(cdus), ", ".join(cdus)))
        except Exception as err:
            print("✗ Error fetching CDUs: {}\n".format(err))
            cur.close()
            conn.close()
            return

        if not cdus:
            print("✗ No entitled CDUs for org {}\n".format(org_id))
            cur.close()
            conn.close()
            return

        # Get existing service restrictions (account-CDU combinations)
        print("Step 4: Checking existing service restrictions...")
        restrictions_sql = (
            "SELECT DISTINCT SVC_ACCT, CDU_ID FROM CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
            "WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(restrictions_sql + "\n")

        existing = set()
        try:
            cur.execute(restrictions_sql)
            for row in cur:
                existing.add((row["SVC_ACCT"], row["CDU_ID"]))
            print("✓ Found {} existing service restriction(s)\n".format(len(existing)))
        except Exception as err:
            print("✗ Error fetching restrictions: {}\n".format(err))
            cur.close()
            conn.close()
            return

        # Get all expected account-CDU combinations
        print("Step 5: Calculating expected service restrictions...")
        expected = set()
        for acct in accounts:
            for cdu in cdus:
                expected.add((acct, cdu))
        print("  Expected combinations: {}\n".format(len(expected)))

        # Find missing combinations
        missing = expected - existing

        if missing:
            print("=" * 60)
            print("MISSING SERVICE RESTRICTIONS FOR ORG: {}".format(org_id))
            print("=" * 60 + "\n")
            for idx, (acct, cdu) in enumerate(sorted(missing), 1):
                print("  {}. Account: {}, CDU: {}".format(idx, acct, cdu))
            print()
        else:
            print("✓ All service restrictions exist for org {}\n".format(org_id))

        # Show existing entitlements
        print("Step 6: Fetching detailed entitlement information...")
        details_sql = (
            "SELECT DISTINCT T.CDU_ID, E.CRUX_DELIVERY_UNIT, E.SUPPLIER_ACRONYM, "
            "T.STATUS, T.SVC_ACCT FROM CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS T "
            "LEFT JOIN CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS E "
            "ON T.ORG = E.ORG_ID AND T.CDU_ID = E.CDU_ID "
            "WHERE T.ORG = '{}' ORDER BY T.CDU_ID, T.SVC_ACCT".format(org_id)
        )
        print("Executing SQL:")
        print(details_sql + "\n")

        try:
            cur.execute(details_sql)
            results = cur.fetchall()

            if results:
                print("=" * 60)
                print("EXISTING ENTITLEMENTS FOR ORG: {}".format(org_id))
                print("=" * 60 + "\n")

                for idx, row in enumerate(results, 1):
                    print("Entitlement #{}:".format(idx))
                    print("  CDU ID:               {}".format(row.get("CDU_ID")))
                    print(
                        "  Delivery Unit:        {}".format(
                            row.get("CRUX_DELIVERY_UNIT")
                        )
                    )
                    print(
                        "  Supplier:             {}".format(row.get("SUPPLIER_ACRONYM"))
                    )
                    print("  Status:               {}".format(row.get("STATUS")))
                    print("  Service Account:      {}".format(row.get("SVC_ACCT")))
                    print()
        except Exception as err:
            print("✗ Error: {}\n".format(err))

        print("=" * 60)
        print("Entitlements list completed for {}".format(org_id))
        print("=" * 60 + "\n")

        cur.close()
        conn.close()

    def entitlements_add(self, org_id, cdu_id):
        # Strip whitespace from inputs
        org_id = org_id.strip()
        cdu_id = cdu_id.strip()

        print("\n" + "=" * 60)
        print("Adding entitlement: CDU {} to Organization {}".format(cdu_id, org_id))
        print("=" * 60 + "\n")

        accts = []
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Fetching organization details...")
        url = (
            "https://api.cruxinformatics.com/v2/ops"
            "/orgs?filter=externalId.eq.{}&offset=0&limit=100".format(org_id)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
            raise typer.Exit(1)
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            raise typer.Exit(1)

        response_json = response.json()

        if not response_json:
            print("✗ Error: Organization {} not found\n".format(org_id))
            raise typer.Exit(1)

        orgId = response_json[0]["id"]
        print("✓ Organization ID: {}\n".format(orgId))

        print("Step 2: Checking CDU subscription status...")
        url = (
            "https://api.cruxinformatics.com/v2/ops/"
            "cduSubscriptions?order=cduId&filter=orgId.eq.{},"
            "cduId.eq.{},status.eq.Active&limit=1000&offset=0".format(orgId, cdu_id)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
            raise typer.Exit(1)
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            raise typer.Exit(1)

        resp = response.json()

        # Check if we got a valid subscription response
        if isinstance(resp, list) and len(resp) > 0:
            svc_start_date = resp[0]["serviceStartDate"]
            svc_end_date = resp[0]["serviceEndDate"]
            print("✓ Active subscription found")
            print("  Service Start Date: {}".format(svc_start_date))
            print("  Service End Date: {}\n".format(svc_end_date))
        else:
            # Handle error response or no subscription found
            if isinstance(resp, dict) and "message" in resp:
                print("✗ {} is NOT entitled for {}".format(cdu_id, org_id))
                print("   Error: {}".format(resp.get("message", "Unknown error")))
            else:
                print("✗ {} is NOT entitled for {}".format(cdu_id, org_id))
                print("   No active subscription found")
            return

        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        print("Step 3: Checking existing service restrictions...")
        existing_restrictions = set()
        sql_query = (
            "SELECT SVC_ACCT FROM CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS  "
            "WHERE ORG = '{}' and CDU_ID "
            "= '{}'".format(org_id, cdu_id.strip("Cx"))
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for row in cur:
                existing_restrictions.add(row["SVC_ACCT"])
            print(
                "Query result: Found {} existing restriction(s)\n".format(
                    len(existing_restrictions)
                )
            )
        except Exception as err:
            print("✗ Other error occurred: {}\n".format(err))

        print("Step 4: Fetching organization accounts...")
        sql_query = (
            "select distinct(ACCOUNT) from "
            "CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for data in cur:
                accts.append(data["ACCOUNT"])

            # CRITICAL: Always include TS91674 (Crux source account)
            if "TS91674" not in accts:
                accts.append("TS91674")
                print("  Added TS91674 (Crux source account) to accounts list")

            print("✓ Found {} account(s): {}\n".format(len(accts), ", ".join(accts)))
        except Exception as err:
            print("✗ Other error occurred: {}\n".format(err))

        # Filter out accounts that already have restrictions
        accounts_to_insert = [
            acct for acct in accts if acct not in existing_restrictions
        ]

        if not accounts_to_insert:
            print(
                "✓ All accounts for Org {} and CDU {} "
                "already have CRUX_SERVICE_RESTRICTIONS. Skipping insert.\n".format(
                    org_id, cdu_id
                )
            )
        else:
            print(
                "Step 5: Creating service restrictions for {} account(s)...".format(
                    len(accounts_to_insert)
                )
            )
            # Execute INSERT for EACH account that doesn't have a restriction yet
            for acct in accounts_to_insert:
                sql_query = (
                    "INSERT INTO "
                    "CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
                    '("ORG", "SVC_ACCT", "CDU_ID", "SVC_ST_DT", '
                    '"SVC_ET_DT", '
                    '"R1_SDT","R1_EDT", '
                    '"R2_SDT", "R2_EDT", "SVC_BLOCK_DT",'
                    ' "STATUS", "LICENSE", '
                    '"SF", "FTP", "S3", "API") VALUES \n'
                    "( '{}', '{}', '{}', "
                    "'{}', '{}', '1970-12-31T00:00:00.000+0000', '2030-12-31T00:00:00.000+0000', "
                    "'2050-12-31T00:00:00.000+0000', '2050-12-31T00:00:00.000+0000', '{}', "
                    "'ACTIVE', 'FULL', 'TRUE', 'FALSE', 'FALSE', 'FALSE')".format(
                        org_id,
                        acct,
                        cdu_id.strip("Cx"),
                        svc_start_date,
                        svc_end_date,
                        svc_end_date,
                    )
                )

                print("\n  Account: {}".format(acct))
                print("  Executing SQL:")
                print("  " + sql_query + "\n")

                try:
                    cur.execute(sql_query)
                    print(
                        "  ✓ Inserted SERVICE_RESTRICTION for account {}".format(acct)
                    )
                except Exception as err:
                    print(
                        "  ✗ Error inserting SERVICE_RESTRICTION for account {}: {}".format(
                            acct, err
                        )
                    )

        print("\nStep 6: Fetching data product details...")
        url = (
            "https://api.cruxinformatics.com/v4/ops/"
            "data-products?withDeleted=false&filters=cduId.eq."
            "{},subscriptionType.in."
            "('SUPPLIER','EDP')&limit=1000&offset=0".format(cdu_id)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        response_json = response.json()

        name = None
        orgcode = None
        for i in response_json["items"]:
            name = i["name"]
            orgcode = i["ownerOrgCode"]

        if not name or not orgcode:
            print(
                "⚠ Warning: Could not find data product details for {}".format(cdu_id)
            )
            print("  Skipping ORG_CDU_ENTITLEMENTS insert")
            cur.close()
            conn.close()
            return

        print("✓ Data product name: {}".format(name))
        print("✓ Owner org code: {}\n".format(orgcode))

        print("Step 7: Checking existing CDU entitlements...")
        existing_entitlement_types = set()
        sql_query = (
            "select ENT_TYPE from "
            "CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS  "
            "WHERE ORG_ID = '{}' and CDU_ID = '{}' ".format(org_id, cdu_id.strip("Cx"))
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for data in cur:
                existing_entitlement_types.add(data["ENT_TYPE"])

            if existing_entitlement_types:
                print(
                    "✓ Found {} existing entitlement(s): {}\n".format(
                        len(existing_entitlement_types),
                        ", ".join(sorted(existing_entitlement_types)),
                    )
                )
        except Exception as err:
            print("✗ Other error occurred: {}\n".format(err))

        required_ent_types = ["CATALOG", "ENTITLE", "LOAD"]
        missing_ent_types = [
            ent for ent in required_ent_types if ent not in existing_entitlement_types
        ]

        if not missing_ent_types:
            print(
                "✓ All entitlements already exist for CDU {} and Org {}.\n".format(
                    cdu_id, org_id
                )
            )
            print("\n" + "=" * 60)
            print("✓ CDU {} has been added to Org {}.".format(cdu_id, org_id))
            print("=" * 60 + "\n")
            cur.close()
            conn.close()
            return

        print(
            "Step 8: Inserting CDU entitlements ({})...".format(
                ", ".join(missing_ent_types)
            )
        )

        for ent in missing_ent_types:
            sql_query = (
                "INSERT INTO CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS "
                '("ORG_ID", "CDU_ID", "CRUX_DELIVERY_UNIT", '
                '"ENT_TYPE", "SUPPLIER_ACRONYM") VALUES \n '
                "( '{}', '{}',"
                "'{}', '{}', '{}')".format(
                    org_id, cdu_id.strip("Cx"), name, ent, orgcode
                )
            )

            print("\n  Entitlement Type: {}".format(ent))
            print("  Executing SQL:")
            print("  " + sql_query + "\n")

            try:
                cur.execute(sql_query)
                print("  ✓ Inserted {} entitlement".format(ent))
            except Exception as err:
                print("  ✗ Error occurred: {}".format(err))

        print("\nStep 9: Sending notification subscription...")
        notify_url = (
            "https://api.cruxinformatics.com/ops"
            "/v4/notification-subscriptions/shared/"
            "SNOWFLAKE_SHARE/orgs/{}/"
            "subscriptions/AQZdPSILiBsLHQXKUJF4kGc3KA".format(orgId)
        )
        print("Notification URL: {}".format(notify_url))

        cdu = cdu_id.replace("Cx", "cx").split()
        print("CDU payload: {}\n".format(cdu))

        try:
            response = requests.post(
                notify_url,
                data=json.dumps(cdu),
                headers={
                    "Authorization": "Bearer {}".format(token),
                    "Content-type": "application/json",
                },
            )
            response.raise_for_status()
            print("✓ Notification sent successfully")
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        print("\n" + "=" * 60)
        print("✓ CDU {} has been added to Org {}.".format(cdu_id, org_id))
        print("=" * 60 + "\n")

        cur.close()
        conn.close()

    def entitlements_update(self, org_id, input_file):
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)
        url = (
            f"https://api.cruxinformatics.com/v2/ops"
            f"/orgs?filter=externalId.eq.{org_id}&offset=0&limit=100"
        )
        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
        except HTTPError as http_err:
            print("HTTP error occurred: {0}".format(http_err))
            print("HTTP ERROR RESPONSE: {0}".format(response.json()))
        except Exception as err:
            print("Other error occurred: {0}".format(err))
        response_json = response.json()

        orgId = response_json[0]["id"]

        if not orgId:
            print(f"Error: {org_id} does not exist")
            raise typer.Exit(1)

        # Validate input file exists
        if not os.path.exists(input_file):
            print(f"Error: Input file '{input_file}' does not exist")
            raise typer.Exit(1)

        entitlements_to_update = []
        with open(input_file, "r") as csvfile:
            csv_reader = csv.DictReader(csvfile)
            for row_num, row in enumerate(csv_reader, 1):
                # Validate required columns
                required_columns = ["cdu_id", "action"]
                for col in required_columns:
                    if col not in row or not row[col]:
                        print(f"Error: Row {row_num} missing required column '{col}'")
                        raise typer.Exit(1)

                if row["action"] not in ["add", "delete"]:
                    print(
                        f"Error: Row {row_num} invalid action '{row['action']}'."
                        f" Must be 'add' or 'delete'"
                    )
                    raise typer.Exit(1)

                entitlements_to_update.append(row)
        print(
            f"Found {len(entitlements_to_update)} entitlements to "
            f"process for organization {org_id}"
        )
        print("Preview of changes:")

        if not entitlements_to_update:
            print("No valid entitlements found in CSV file")
            raise typer.Exit(1)

        confirmation = typer.prompt("Type 'UPDATE' to proceed with these changes")
        if confirmation != "UPDATE":
            print("Update operation cancelled")
            raise typer.Exit(1)

        for entitlements in entitlements_to_update:
            cdu_id = entitlements["cdu_id"]
            action = entitlements["action"]

            if action == "add":
                status = self.entitlements_add(org_id, cdu_id)
            elif action == "delete":
                status = self.entitlements_delete(org_id, cdu_id)
        return status

    def entitlements_delete(self, org_id, cdu_id):
        print(f"\n{'=' * 60}")
        print(f"Deleting entitlement: CDU {cdu_id} from Organization {org_id}")
        print(f"{'=' * 60}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print(f"User roles: {user_roles}\n")

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type DELETE to confirm")
                if confirmation != "DELETE":
                    typer.echo("Aborted.")
                    print(f"✗ {org_id} You are not Authorized for Delete operation\n")
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
                print("Deleting entitlements...")
            else:
                typer.echo("✗ You are not authorized for Delete")
                raise typer.Exit(code=1)
        else:
            print(f"✗ {org_id} Not Authorized\n")
            raise typer.Exit(code=1)

        print("\nStep 2: Connecting to Snowflake...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 3: Deleting service restrictions...")
        sql_query = (
            "DELETE FROM CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS  "
            + f"WHERE ORG = '{org_id}' and CDU_ID = '{cdu_id.strip('Cx')}'"
        )
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            print("✓ Service restrictions deleted\n")
        except Exception as err:
            print(f"✗ Other error occurred: {err}\n")

        print("Step 4: Deleting CDU entitlements...")
        sql_query = (
            "DELETE FROM CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS  "
            + f"WHERE ORG_ID = '{org_id}' and CDU_ID = '{cdu_id.strip('Cx')}'"
        )
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            print("✓ CDU entitlements deleted\n")
        except Exception as err:
            print(f"✗ Other error occurred: {err}\n")

        print("Step 5: Fetching organization ID for notifications...")
        url = (
            f"https://api.cruxinformatics.com/v2/ops/"
            f"orgs?filter=externalId.eq.{org_id}&offset=0&limit=100"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        orgId = response_json[0]["id"]
        print(f"✓ Organization ID: {orgId}\n")

        print("Step 6: Sending delete notification...")
        notify_url = (
            f"https://api.cruxinformatics.com/ops"
            f"/v4/notification-subscriptions/shared/SNOWFLAKE_SHARE"
            f"/orgs/{orgId}/subscriptions"
            f"/AQZdPSILiBsLHQXKUJF4kGc3KA"
        )
        print(f"Notification URL: {notify_url}")

        cdu = cdu_id.replace("Cx", "cx").split()
        print(f"CDU payload: {cdu}\n")

        try:
            response = requests.delete(
                notify_url,
                data=json.dumps(cdu),
                headers={
                    "Authorization": f"Bearer {token}",
                    "Content-type": "application/json",
                },
            )
            response.raise_for_status()
            print("✓ Delete notification sent successfully\n")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        print(f"{'=' * 60}")
        print(f"✓ CDU {cdu_id} has been deleted from Org {org_id}.")
        print(f"{'=' * 60}\n")

        cur.close()
        conn.close()

    def entitlements_sync(self, org_id, dry_run=False):
        """
        Sync all entitlements for an organization to SERVICE_RESTRICTIONS table.

        This method:
        1. Fetches all active CDU subscriptions for the org
        2. Ensures all org accounts have SERVICE_RESTRICTIONS entries for each CDU
        3. Reports missing/added entries

        Args:
            org_id: Organization ID (e.g., 'CXO01010')
            dry_run: If True, only report what would be done without making changes
        """
        # Strip whitespace from inputs
        org_id = org_id.strip()

        print("\n" + "=" * 60)
        print(
            "{}Syncing entitlements for {}".format(
                "[DRY RUN] " if dry_run else "", org_id
            )
        )
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Fetching organization details...")
        # Get organization ID
        url = (
            "https://api.cruxinformatics.com/v2/ops"
            "/orgs?filter=externalId.eq.{}&offset=0&limit=100".format(org_id)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            response.raise_for_status()
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            return
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            return

        response_json = response.json()
        if not response_json:
            print("✗ Organization {} not found\n".format(org_id))
            return

        orgId = response_json[0]["id"]
        print("✓ Organization ID: {}\n".format(orgId))

        # Get all active CDU subscriptions
        print("Step 2: Fetching active CDU subscriptions...")
        url = (
            "https://api.cruxinformatics.com/v2/ops/cduSubscriptions"
            "?order=cduId&filter=orgId.eq.{},status.eq.Active"
            "&limit=5000&offset=0".format(orgId)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            response.raise_for_status()
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            return
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            return

        subscriptions = response.json()
        if not subscriptions:
            print("✗ No active subscriptions found for {}\n".format(org_id))
            return

        print("✓ Found {} active CDU subscription(s)\n".format(len(subscriptions)))

        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        # Get all accounts for this org
        print("Step 3: Fetching organization accounts...")
        accounts = []
        sql_query = (
            "SELECT DISTINCT ACCOUNT FROM "
            "CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for data in cur:
                accounts.append(data["ACCOUNT"])

            print(
                "✓ Found {} account(s): {}\n".format(len(accounts), ", ".join(accounts))
            )
        except Exception as err:
            print("✗ Error fetching accounts: {}\n".format(err))
            cur.close()
            conn.close()
            return

        if not accounts:
            print("✗ No accounts found for {}\n".format(org_id))
            cur.close()
            conn.close()
            return

        # Get existing SERVICE_RESTRICTIONS entries
        print("Step 4: Fetching existing service restrictions...")
        existing_restrictions = set()
        sql_query = (
            "SELECT DISTINCT SVC_ACCT, CDU_ID "
            "FROM CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
            "WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for row in cur:
                existing_restrictions.add((row["SVC_ACCT"], row["CDU_ID"]))
        except Exception as err:
            print("✗ Error fetching existing restrictions: {}\n".format(err))
            cur.close()
            conn.close()
            return

        print(
            "✓ Found {} existing SERVICE_RESTRICTION(s)\n".format(
                len(existing_restrictions)
            )
        )

        # Track what needs to be added
        total_added = 0
        total_skipped = 0
        errors = []

        print("Step 5: Processing CDU subscriptions...")
        # Process each subscription
        for subscription in subscriptions:
            cdu_id = subscription["cduId"]
            cdu_numeric = cdu_id.replace("Cx", "").replace("CX", "")
            svc_start_date = subscription["serviceStartDate"]
            svc_end_date = subscription["serviceEndDate"]

            print("\nProcessing CDU: {}".format(cdu_id))
            print("  CDU Numeric: {}".format(cdu_numeric))
            print("  Service dates: {} to {}".format(svc_start_date, svc_end_date))

            # Check each account for this CDU
            for acct in accounts:
                # Check if restriction already exists
                if (acct, cdu_numeric) in existing_restrictions:
                    print("  ✓ {} - already exists".format(acct))
                    total_skipped += 1
                    continue

                # Need to add this restriction
                if dry_run:
                    print("  + {} - WOULD ADD".format(acct))
                    total_added += 1
                    continue

                # Actually insert the restriction
                sql_query = (
                    "INSERT INTO CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
                    '("ORG", "SVC_ACCT", "CDU_ID", "SVC_ST_DT", "SVC_ET_DT", '
                    '"R1_SDT", "R1_EDT", "R2_SDT", "R2_EDT", "SVC_BLOCK_DT", '
                    '"STATUS", "LICENSE", "SF", "FTP", "S3", "API") VALUES '
                    "('{}', '{}', '{}', "
                    "'{}', '{}', "
                    "'1970-12-31T00:00:00.000+0000', '2030-12-31T00:00:00.000+0000', "
                    "'2050-12-31T00:00:00.000+0000', '2050-12-31T00:00:00.000+0000', "
                    "'{}', 'ACTIVE', 'FULL', 'TRUE', 'FALSE', 'FALSE', 'FALSE')".format(
                        org_id,
                        acct,
                        cdu_numeric,
                        svc_start_date,
                        svc_end_date,
                        svc_end_date,
                    )
                )

                try:
                    cur.execute(sql_query)
                    print("  ✓ {} - ADDED".format(acct))
                    total_added += 1
                except Exception as err:
                    error_msg = "  ✗ {} - ERROR: {}".format(acct, err)
                    print(error_msg)
                    errors.append(error_msg)

        cur.close()
        conn.close()

        # Print summary
        print("\n" + "=" * 60)
        print("SYNC SUMMARY for {}".format(org_id))
        print("=" * 60)
        print("CDUs processed:      {}".format(len(subscriptions)))
        print("Accounts processed:  {}".format(len(accounts)))
        print("Already existed:     {}".format(total_skipped))
        print(
            "{}:           {}".format("Would add" if dry_run else "Added", total_added)
        )
        if errors:
            print("Errors:              {}".format(len(errors)))
            print("\nErrors encountered:")
            for error in errors:
                print(error)
        print("=" * 60)

        if dry_run:
            print(
                "\n[DRY RUN] No changes were made. Run without --dry_run to apply changes.\n"
            )
        elif total_added > 0:
            print(
                "\n✓ Successfully synced {} entitlement(s) for {}\n".format(
                    total_added, org_id
                )
            )
        else:
            print("\n✓ All entitlements already synced for {}\n".format(org_id))

    def provision_add(self, org_id, view_name, region):
        """Create a provisioned view for an organization.

        Args:
            org_id: Organization ID (e.g., 'CXO82491')
            view_name: View name in format 'CX12345--TABLE_NAME--DATASET_ID'
            region: Region (US/EU/VPS)
        """
        print(f"\n{'=' * 60}")
        print(f"Provisioning view for organization: {org_id}")
        print(f"View name: {view_name}")
        print(f"Region: {region}")
        print(f"{'=' * 60}\n")

        parts = view_name.split("--", 1)
        cdu_id = parts[0].upper()  # Ensure uppercase: Cx13336 → CX13336
        table_name = parts[1]

        # Strip the CX prefix for use in CRUX_SERVICE_RESTRICTIONS queries
        cdu_numeric = cdu_id.replace("CX", "").replace("Cx", "")

        print(f"CDU ID: {cdu_id}")
        print(f"CDU Numeric (for restrictions): {cdu_numeric}")
        print(f"Table name: {table_name}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print(f"User roles: {user_roles}\n")

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type ADD to confirm")
                if confirmation != "ADD":
                    typer.echo("Aborted.")
                    print(f"{org_id} You are not Authorized for ADD operation")
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
            else:
                typer.echo("✗ You are not authorized for ADD")
                raise typer.Exit(code=1)
        else:
            print(f"✗ {org_id} Not Authorized ")
            raise typer.Exit(code=1)

        print("Step 2: Fetching organization details...")
        url = (
            f"https://api.cruxinformatics.com/v2/ops/"
            f"orgs?filter=externalId.eq.{org_id}&offset=0&limit=100"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        orgId = response_json[0]["id"]
        print(f"✓ Organization ID: {orgId}\n")

        print("Step 3: Checking CDU subscription...")
        url = (
            f"https://api.cruxinformatics.com/v2/ops"
            f"/cduSubscriptions?order=cduId&filter=orgId.eq.{orgId},"
            f"cduId.eq.{cdu_id},status.eq.Active&limit=1000&offset=0"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        resp = response.json()
        if not resp:
            print(f"✗ {cdu_id} is NOT entitled for {org_id}")
            return

        print("✓ CDU subscription is active\n")

        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print(f"✗ Unsupported region: {region}")
            raise typer.Exit(1)

        print(f"Step 4: Connecting to Snowflake ({region})...")
        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        # Check if table is curated BEFORE proceeding
        print(f"Step 5: Fetching schema information for table: {table_name}")
        crux_schemas = SnowflakeOrgs.get_crux_schemas(table_name, cur)
        if not crux_schemas:
            print(f"✗ Table has NOT been CURATED: {table_name}")
            print("  Make sure the table exists in CX_DATA_LAKE.PRIVATE.CRUX_SCHEMA\n")
            cur.close()
            conn.close()
            return

        print("✓ Schema found for table\n")
        cs = crux_schemas.get(table_name)

        # REMOVED Step 6 - view existence check
        # Now go straight to building the DDL

        print("Step 6: Building view DDL...")
        ide = {"field_name": "crux_ingestion_dt", "data_type": "DATETIME"}
        sde = {"field_name": "crux_supplier_implied_dt", "data_type": "DATETIME"}
        fields = []

        for pos in sorted(cs["fields"].keys()):
            ce = cs["fields"][pos]
            if ce["field_name"] == sde["field_name"]:
                ce = sde
            elif ce["field_name"] == ide["field_name"]:
                ce = ide
            elif cs and cs["fields"].get(pos):
                ce = cs["fields"][pos]
            field = f'"{ce["field_name"]}"'
            if len(ce["field_name"]) > 255:
                print(f"✗ Curated field name too long: {ce['field_name']}")
                cur.close()
                conn.close()
                return
            if ce["data_type"] == "DATE":
                field = f" try_to_date(cast(\"{ce['field_name']}\" as varchar))"
            else:
                field += f"::{ce['data_type']}"

            field += f' as "{ce["field_name"]}"'
            fields.append(field)

        print(f"  Total fields: {len(fields)}\n")

        # IMPORTANT: Construct the final view name with uppercase CDU
        vname = "--".join([cdu_id, table_name])

        entitlement_date = "crux_supplier_implied_dt"
        # Use CREATE OR REPLACE to handle both new views and updates
        create = f'CREATE OR REPLACE SECURE VIEW CX_DATA_LAKE.CRUX_CURATED."{vname}" AS'
        select = f'SELECT {", ".join(fields)} FROM CX_DATA_LAKE.PRIVATE."{table_name}"'
        view_parts = [create, select]

        predicate = (
            f'WHERE "{entitlement_date}" < '
            + '(select distinct "SVC_BLOCK_DT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account()'
            + f" and \"CDU_ID\"= '{cdu_numeric}' and \"STATUS\"='ACTIVE') "
            + f'AND (("{entitlement_date}" >= '
            + '(select distinct "R1_SDT" from '
            + 'CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + f"and \"CDU_ID\"= '{cdu_numeric}' and \"STATUS\"='ACTIVE') "
            + f'AND "{entitlement_date}" <='
            + '(select distinct "R1_EDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + f"and \"CDU_ID\"= '{cdu_numeric}' and \"STATUS\"='ACTIVE')) "
            + f'OR ("{entitlement_date}" >= '
            + '(select distinct "R2_SDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + f"and \"CDU_ID\"= '{cdu_numeric}' and \"STATUS\"='ACTIVE') "
            + f'AND "{entitlement_date}" <= '
            + '(select distinct "R2_EDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + f"and \"CDU_ID\"= '{cdu_numeric}' and \"STATUS\"='ACTIVE')))"
        )
        view_parts.append(predicate)
        view_ddl = " ".join(view_parts) + ";"

        print(f"Step 7: Creating view: CX_DATA_LAKE.CRUX_CURATED.{vname}")
        print(f"Executing DDL:\n{view_ddl[:500]}...\n")  # Print first 500 chars

        try:
            cur.execute(view_ddl)
            print(f"✓ Successfully created view: CX_DATA_LAKE.CRUX_CURATED.{vname}\n")
        except Exception as err:
            print(f"✗ Error creating view: {err}\n")

        cur.close()
        conn.close()

    def cdu_provision_delete(self, view_name, region):
        # Strip whitespace from inputs
        view_name = view_name.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Deleting CDU provisioned view")
        print("View name: {}".format(view_name))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
            raise typer.Exit(1)
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            raise typer.Exit(1)

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print("User roles: {}\n".format(user_roles))

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type DELETE to confirm")
                if confirmation != "DELETE":
                    typer.echo("Aborted.")
                    print("✗ You are not Authorized for DELETE operation\n")
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
            else:
                typer.echo("✗ You are not authorized for DELETE")
                raise typer.Exit(code=1)
        else:
            print("✗ Not Authorized for DELETE operation\n")
            raise typer.Exit(code=1)

        print("Step 2: Parsing view name...")
        parts = view_name.split("--", 1)
        cdu_id = (
            parts[0]
            .upper()
            .replace("CX_DATA_LAKE", "")
            .replace("CRUX_CURATED", "")
            .lstrip(".")
        )
        table_name = parts[1]
        vname = "--".join([cdu_id, table_name])

        print("  CDU ID: {}".format(cdu_id))
        print("  Table name: {}".format(table_name))
        print("  Full view name: {}\n".format(vname))

        print("Step 3: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 4: Dropping view from CRUX_CURATED schema...")
        quoted_view_name = '"{}"'.format(vname)
        drop_view_name = ".".join(["CX_DATA_LAKE", "CRUX_CURATED", quoted_view_name])
        drop_sql = "DROP VIEW IF EXISTS {}".format(drop_view_name)

        print("Executing SQL:")
        print(drop_sql + "\n")

        try:
            cur.execute(drop_sql)
            print("✓ Successfully dropped view: {}\n".format(drop_view_name))
        except Exception as err:
            print("✗ Error dropping view {}: {}\n".format(drop_view_name, err))
        finally:
            cur.close()
            conn.close()

        print("=" * 60)
        print("CDU provision delete completed for {}".format(vname))
        print("=" * 60 + "\n")

    def share_list(self, org_id, region):
        # Strip whitespace from inputs
        org_id = org_id.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Listing share grants for organization: {}".format(org_id))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Fetching organization details...")
        url = (
            "https://api.cruxinformatics.com/v2/ops/"
            "orgs?filter=externalId.eq.{}&offset=0&limit=100".format(org_id)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        response_json = response.json()

        if response_json:
            orgId = response_json[0]["id"]
            print("✓ Organization ID: {}\n".format(orgId))
        else:
            print("✗ Not a valid Org\n")
            return

        print("Step 2: Fetching active CDU subscriptions...")
        selected_cdus = []

        url = (
            "https://api.cruxinformatics.com/v2/ops/cduSubscriptions"
            "?order=cduId&filter=orgId.eq.{},status.eq"
            ".Active&limit=1000&offset=0".format(orgId)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        response_json = response.json()
        if response_json:
            for i in response_json:
                selected_cdus.append(i["cduId"])
            print(
                "✓ Found {} active CDU subscription(s): {}\n".format(
                    len(selected_cdus), ", ".join(selected_cdus)
                )
            )
        else:
            print("✗ {} doesn't have any Active CDU entitlements\n".format(org_id))
            return

        print("Step 3: Connecting to Snowflake (US) for account lookup...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 4: Checking for client schema...")
        client_schema = ""

        sql_query = (
            "SELECT ORG, NAME, ACCOUNT, ORG_ACCT_NAME,"
            + " SOURCE_ACCOUNT, TYPE, CLIENT_SCHEMA "
            + "FROM CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS"
            + " WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            results = cur.fetchall()

            if not results:
                print("✗ No accounts found for org {}\n".format(org_id))
            else:
                print("✓ Found {} account(s)".format(len(results)))
                for data in results:
                    client_schema = data.get("CLIENT_SCHEMA")
                    if client_schema:
                        print("  Client schema: {}\n".format(client_schema))
                        break
                if not client_schema:
                    print("  No client schema configured\n")
        except Exception as err:
            print("✗ Other error occurred: {}\n".format(err))
            return

        print("Step 5: Fetching catalog data...")
        catalog = SnowflakeOrgs.get_catalog(selected_cdus, token, client_schema)
        expected_grants = set()
        existing_grants = set()

        print("Step 6: Building expected grants list...")
        for i, items in catalog.items():
            for frame in items["frames"]:
                if client_schema:
                    expected_grants.add(frame["client_schema_view_name"])
                else:
                    expected_grants.add(frame["view_name"].replace("Cx", "CX"))

        # Add default shares
        default_share = ["CRUX_CATALOG", "CRUX_NOTIFICATIONS", "CRUX_SCHEMAS"]
        default_olr_share = [
            "CX00000--CRUX_CATALOG",
            "CX00000--CRUX_NOTIFICATIONS",
            "CX00000--CRUX_SCHEMAS",
        ]

        print("Step 7: Finding shares...")
        share_list = []
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        if region:
            print("Connecting to Snowflake ({})...".format(region))
            regional_conn = connection_func()
            regional_cur = regional_conn.cursor(DictCursor)
            print("✓ Connected to Snowflake\n")

        select_sql = "SHOW SHARES LIKE '%{}%'".format(org_id)
        print("Executing SQL:")
        print(select_sql + "\n")

        if region:
            try:
                regional_cur.execute(select_sql)
                for data in regional_cur:
                    share_list.append(data["name"])
            except Exception as e:
                print("✗ Error running query: {}\n".format(e))
        else:
            try:
                cur.execute(select_sql)
                for data in cur:
                    share_list.append(data["name"])
            except Exception as e:
                print("✗ Error running query: {}\n".format(e))

        print(
            "✓ Found {} share(s): {}\n".format(len(share_list), ", ".join(share_list))
        )

        # Add default shares to expected grants
        for share in share_list:
            if "OLR" in share:
                for data in default_olr_share:
                    expected_grants.add("--".join([data, org_id]))
                break
            else:
                for data in default_share:
                    if region:
                        view = "--".join(["CX00000", data, org_id])
                        expected_grants.add(view)
                    else:
                        expected_grants.add(data)

        print("  Total expected grants: {}\n".format(len(expected_grants)))

        print("Step 8: Checking existing grants...")
        for idx, share in enumerate(share_list, 1):
            print("\nShare {}/{}: {}".format(idx, len(share_list), share))

            select_sql = 'show grants to share "{}"'.format(share)
            print("Executing SQL:")
            print(select_sql)

            if region:
                try:
                    regional_cur.execute(select_sql)
                    grant_count = 0
                    for data in regional_cur:
                        if data["granted_on"] == "VIEW":
                            parts = data["name"].split(".", 2)
                            name = parts[2]
                            existing_grants.add(name.strip('"'))
                            grant_count += 1
                    print("  Found {} view grant(s)\n".format(grant_count))
                except Exception as e:
                    print("  ✗ Error running query: {}\n".format(e))
            else:
                try:
                    cur.execute(select_sql)
                    grant_count = 0
                    for data in cur:
                        if data["granted_on"] == "VIEW":
                            parts = data["name"].split(".", 2)
                            name = parts[2]
                            existing_grants.add(name.strip('"'))
                            grant_count += 1
                    print("  Found {} view grant(s)\n".format(grant_count))
                except Exception as e:
                    print("  ✗ Error running query: {}\n".format(e))

        missing_grants = sorted(expected_grants - existing_grants)
        deprecated_grants = sorted(existing_grants - expected_grants)

        # Print results
        print("=" * 60)
        print("SHARE GRANT SUMMARY FOR ORG: {}".format(org_id))
        print("=" * 60 + "\n")

        print("Expected grants:    {}".format(len(expected_grants)))
        print("Existing grants:    {}".format(len(existing_grants)))
        print("Missing grants:     {}".format(len(missing_grants)))
        print("Deprecated grants:  {}\n".format(len(deprecated_grants)))

        if missing_grants:
            print("=" * 60)
            print("MISSING VIEW GRANTS ({})".format(len(missing_grants)))
            print("=" * 60)
            for idx, grant in enumerate(missing_grants, 1):
                print("  {}. {}".format(idx, grant))
            print()
        else:
            print("✓ No MISSING VIEW GRANTS for Org {}\n".format(org_id))

        if deprecated_grants:
            print("=" * 60)
            print("DEPRECATED VIEW GRANTS ({})".format(len(deprecated_grants)))
            print("=" * 60)
            for idx, grant in enumerate(deprecated_grants, 1):
                print("  {}. {}".format(idx, grant))
            print()
        else:
            print("✓ No DEPRECATED VIEW GRANTS for Org {}\n".format(org_id))

        print("=" * 60)
        print("Share list completed for {}".format(org_id))
        print("=" * 60 + "\n")

        cur.close()
        conn.close()
        if region:
            regional_cur.close()
            regional_conn.close()
        return

    def share_add(self, org_id, view_name, region):
        """Add view to share for organization

        Args:
            org_id: Organization ID
            view_name: View name to add
            region: REQUIRED - Region (US/EU/VPS) where the share exists
        """
        print(f"\n{'=' * 60}")
        print("Adding view to share")
        print(f"Organization: {org_id}")
        print(f"View: {view_name}")
        print(f"Region: {region}")
        print(f"{'=' * 60}\n")

        # CRITICAL: Region is mandatory for share operations
        if not region:
            print("✗ ERROR: --region parameter is REQUIRED for share-add operations")
            print("Valid regions: US, EU, VPS")
            print(
                "Example: share-add --org_id CXO01010 --view_name <view> --region US\n"
            )
            raise typer.Exit(1)

        # Validate region value
        valid_regions = ["US", "EU", "VPS"]
        if region.upper() not in valid_regions:
            print(
                f"✗ ERROR: Invalid region '{region}'. Must be one of: {', '.join(valid_regions)}\n"
            )
            raise typer.Exit(1)

        region = region.upper()  # Normalize to uppercase
        print(f"✓ Region validated: {region}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Connecting to Snowflake (US) for account lookup...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        client_schema = ""
        sql_query = (
            "SELECT ORG, NAME, ACCOUNT, ORG_ACCT_NAME,"
            + " SOURCE_ACCOUNT, TYPE, CLIENT_SCHEMA "
            + "FROM CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS"
            + f" WHERE ORG = '{org_id}'"
        )
        print(f"Executing SQL:\n{sql_query}\n")

        try:
            cur.execute(sql_query)
            results = cur.fetchall()
            if not results:
                print(f"✗ No accounts found for org {org_id}\n")
            else:
                print(f"✓ Found {len(results)} account(s)")
                for data in results:
                    client_schema = data.get("CLIENT_SCHEMA")
                    print(f"  Client schema: {client_schema or 'None'}\n")
        except Exception as err:
            print(f"✗ Other error occurred: {err}\n")
            return

        if client_schema:
            parts = view_name.split("_", 1)
        else:
            parts = view_name.split("--", 1)

        cdu_id = parts[0].capitalize()
        print(f"CDU ID: {cdu_id}\n")

        print("Step 2: Fetching organization details...")
        url = (
            f"https://api.cruxinformatics.com/v2/ops"
            f"/orgs?filter=externalId.eq.{org_id}&offset=0&limit=100"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        orgId = response_json[0]["id"]
        print(f"✓ Organization ID: {orgId}\n")

        print("Step 3: Checking CDU subscription...")
        selected_cdus = []
        url = (
            f"https://api.cruxinformatics.com/v2/ops"
            f"/cduSubscriptions?order=cduId&filter=orgId.eq.{orgId},"
            f"cduId.eq.{cdu_id},status.eq.Active&limit=1000&offset=0"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        if response_json:
            for i in response_json:
                selected_cdus.append(i["cduId"])
            print(f"✓ Found {len(selected_cdus)} active CDU subscription(s)\n")
        else:
            print(f"✗ {org_id} does not have this CDU {cdu_id} set to Active\n")
            return

        if cdu_id not in selected_cdus:
            print(f"✗ {org_id} is NOT entitled for this cdu {cdu_id}\n")
            return

        print("Step 4: Fetching data product type...")
        type = ""
        url = (
            f"https://api.cruxinformatics.com/v4/ops"
            f"/data-products?withDeleted=false&filters=cduId.eq."
            f"{cdu_id},subscriptionType.in.('SUPPLIER','EDP')&pageSize=50"
        )
        print(f"API URL: {url}\n")

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": f"Bearer {token}",
                },
            )
            print(f"API Response Status: {response.status_code}")
        except HTTPError as http_err:
            print(f"✗ HTTP error occurred: {http_err}")
            print(f"✗ HTTP ERROR RESPONSE: {response.json()}")
        except Exception as err:
            print(f"✗ Other error occurred: {err}")

        response_json = response.json()
        if response_json:
            for i in response_json["items"]:
                type = i["type"]
            print(f"✓ Data product type: {type}\n")

        print(f"Step 5: Connecting to Snowflake ({region}) for share operations...")
        share_list = []
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region)

        if not connection_func:
            print(f"✗ Unsupported region: {region}\n")
            raise typer.Exit(1)

        # Region is now mandatory, always create regional connection
        regional_conn = connection_func()
        regional_cur = regional_conn.cursor(DictCursor)
        print(f"✓ Connected to Snowflake ({region})\n")

        print("Step 6: Finding shares for organization...")
        select_sql = f"SHOW SHARES LIKE '%{org_id}%' "
        print(f"Executing SQL:\n{select_sql}\n")

        try:
            regional_cur.execute(select_sql)
            for data in regional_cur:
                share_list.append(data["name"])
            print(f"✓ Found {len(share_list)} share(s): {share_list}\n")
        except Exception as e:
            print(f"✗ Error running query: {e}\n")

        print("Step 7: Adding view to shares...")
        for share in share_list:
            if "SHARE" in share or "ACCESS" in share:
                print(f"\nProcessing share: {share}")

                name = f'"{view_name}"'
                if type == "Wrangle":
                    if client_schema:
                        catalog = SnowflakeOrgs.get_catalog(
                            selected_cdus, token, client_schema
                        )
                        for i, items in catalog.items():
                            for frame in items["frames"]:
                                if view_name == frame["client_schema_view_name"]:
                                    wrangle_view_name = frame["view_name"]
                        print("  Inserting wrangle client schema view...")
                        SnowflakeOrgs.insert_wrangle_client_schema(
                            cur, client_schema, view_name, wrangle_view_name
                        )
                        view = ".".join(["CX_DATA_LAKE", client_schema, name])
                    else:
                        view = ".".join(["CX_DATA_LAKE", "CRUX_WRANGLE", name])
                elif type == "Milestone":
                    parts = name.split("--", -1)
                    first_part = parts[0].replace("CX", "Cx")
                    second_part = parts[1]
                    rest = parts[2]
                    name = "--".join([first_part, second_part, rest])
                    if client_schema:
                        print("  Inserting client schema view...")
                        SnowflakeOrgs.insert_client_schema(
                            cur, client_schema, cdu_id, view_name
                        )
                        view = ".".join(["CX_DATA_LAKE", client_schema, name])
                    else:
                        view = ".".join(["CX_DATA_LAKE", "CRUX_MILESTONE", name])
                else:
                    if client_schema:
                        print("  Inserting client schema view...")
                        SnowflakeOrgs.insert_client_schema(
                            cur, client_schema, cdu_id, view_name
                        )
                        view = ".".join(["CX_DATA_LAKE", client_schema, name])
                    else:
                        view = ".".join(["CX_DATA_LAKE", "CRUX_CURATED", name])

                grant_sql = f'GRANT SELECT ON VIEW {view} TO SHARE "{share}"'
                print(f"  Executing SQL:\n  {grant_sql}\n")

                try:
                    regional_cur.execute(grant_sql)
                    print(f"  ✓ View {view} has been added to share {share}")
                except snowflake.connector.errors.ProgrammingError as e:
                    print(f"  ✗ Grant failed: [{e.errno}]")
                except Exception as e:
                    print(f"  ✗ Error running query: {e}")

        print(f"\n{'=' * 60}")
        print("Share add completed")
        print(f"{'=' * 60}\n")

        cur.close()
        conn.close()
        regional_cur.close()
        regional_conn.close()

        return

    def share_delete(self, org_id, view_name, region):
        # Strip whitespace from inputs
        org_id = org_id.strip()
        view_name = view_name.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Deleting view from share")
        print("Organization: {}".format(org_id))
        print("View: {}".format(view_name))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print("User roles: {}\n".format(user_roles))

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type DELETE to confirm")
                if confirmation != "DELETE":
                    typer.echo("Aborted.")
                    print(
                        "✗ {} You are not Authorized for Delete operation\n".format(
                            org_id
                        )
                    )
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
                print("Authorized for Delete")
            else:
                typer.echo("✗ You are not authorized for Delete")
                raise typer.Exit(code=1)
        else:
            print("✗ {} Not Authorized\n".format(org_id))
            raise typer.Exit(code=1)

        print("\nStep 2: Connecting to Snowflake (US) for account lookup...")
        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 3: Checking for client schema...")
        client_schema = ""
        sql_query = (
            "SELECT ORG, NAME, ACCOUNT, ORG_ACCT_NAME,"
            " SOURCE_ACCOUNT, TYPE, CLIENT_SCHEMA "
            + "FROM CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS"
            + " WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            results = cur.fetchall()
            if not results:
                print("✗ No accounts found for org {}\n".format(org_id))
            else:
                print("✓ Found {} account(s)".format(len(results)))
                for data in results:
                    client_schema = data.get("CLIENT_SCHEMA")
                    if client_schema:
                        print("  Client schema: {}\n".format(client_schema))
                        break
                if not client_schema:
                    print("  No client schema configured\n")
        except Exception as err:
            print("✗ Other error occurred: {}\n".format(err))
            return

        parts = view_name.split("_", 1) if client_schema else view_name.split("--", 1)
        cdu_id = parts[0].capitalize()
        print("CDU ID: {}\n".format(cdu_id))

        print("Step 4: Fetching data product type...")
        url = (
            "https://api.cruxinformatics.com/v4/ops/data-products"
            "?withDeleted=false"
            "&filters=cduId.eq.{},"
            "subscriptionType.in.('SUPPLIER','EDP')"
            "&pageSize=50".format(cdu_id)
        )
        print("API URL: {}\n".format(url))

        dp_type = ""
        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        response_json = response.json()
        if response_json and "items" in response_json:
            for item in response_json["items"]:
                dp_type = item.get("type", "")
            print("✓ Data product type: {}\n".format(dp_type))
        else:
            print("⚠ No data product type found\n")

        print("Step 5: Connecting to Snowflake ({})...".format(region or "US"))
        share_list = []
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        if region:
            regional_conn = connection_func()
            regional_cur = regional_conn.cursor(DictCursor)
            print("✓ Connected to Snowflake\n")

        print("Step 6: Finding shares for organization...")
        select_sql = "SHOW SHARES LIKE '%{}%'".format(org_id)
        print("Executing SQL:")
        print(select_sql + "\n")

        if region:
            try:
                regional_cur.execute(select_sql)
                for data in regional_cur:
                    share_list.append(data["name"])
            except Exception as e:
                print("✗ Error running query: {}\n".format(e))
        else:
            try:
                cur.execute(select_sql)
                for data in cur:
                    share_list.append(data["name"])
            except Exception as e:
                print("✗ Error running query: {}\n".format(e))

        print(
            "✓ Found {} share(s): {}\n".format(len(share_list), ", ".join(share_list))
        )

        print("Step 7: Revoking view from shares...")
        for idx, share in enumerate(share_list, 1):
            if "SHARE" in share or "ACCESS" in share:
                print("\nShare {}/{}: {}".format(idx, len(share_list), share))

                name = '"{}"'.format(view_name)
                if dp_type == "Wrangle":
                    view = (
                        "CX_DATA_LAKE.{}.{}".format(client_schema, name)
                        if client_schema
                        else "CX_DATA_LAKE.CRUX_WRANGLE.{}".format(name)
                    )
                elif dp_type == "Milestone":
                    parts = name.replace('"', "").split("--", 2)
                    first = parts[0].replace("CX", "Cx")
                    view = 'CX_DATA_LAKE.CRUX_MILESTONE."{}--{}--{}"'.format(
                        first, parts[1], parts[2]
                    )
                else:
                    view = (
                        'CX_DATA_LAKE.{}."{}"'.format(client_schema, view_name)
                        if client_schema
                        else 'CX_DATA_LAKE.CRUX_CURATED."{}"'.format(view_name)
                    )

                revoke_sql = 'REVOKE SELECT ON VIEW {} FROM SHARE "{}"'.format(
                    view, share
                )
                print("Executing SQL:")
                print(revoke_sql)

                if region:
                    try:
                        regional_cur.execute(revoke_sql)
                        print(
                            "✓ View {} has been removed from share {}\n".format(
                                view_name, share
                            )
                        )
                    except snowflake.connector.errors.ProgrammingError as e:
                        print("✗ Revoke failed: [{}]\n".format(e.errno))
                    except Exception as e:
                        print("✗ Error running query: {}\n".format(e))
                else:
                    try:
                        cur.execute(revoke_sql)
                        print(
                            "✓ View {} has been removed from share {}\n".format(
                                view_name, share
                            )
                        )
                    except snowflake.connector.errors.ProgrammingError as e:
                        print("✗ Revoke failed: [{}]\n".format(e.errno))
                    except Exception as e:
                        print("✗ Error running query: {}\n".format(e))

                if dp_type == "Wrangle" and client_schema:
                    print("Deleting wrangle client schema view...")
                    SnowflakeOrgs.delete_client_schema(cur, client_schema, view_name)

        print("=" * 60)
        print("Share delete completed for {}".format(view_name))
        print("=" * 60 + "\n")

        cur.close()
        conn.close()
        if region:
            regional_cur.close()
            regional_conn.close()

        return

    def provision_list(self, org_id, region):
        """List missing provisioned views for an organization.

        Args:
            org_id: Organization ID (e.g., 'CXO82491')
            region: Region (US/EU/VPS)
        """
        # Strip whitespace from inputs
        org_id = org_id.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Listing provisioned views for organization: {}".format(org_id))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Fetching organization details...")
        url = (
            "https://api.cruxinformatics.com/v2/ops"
            "/orgs?filter=externalId.eq.{}&offset=0&limit=100".format(org_id)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
            return
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            return

        response_json = response.json()

        if response_json:
            orgId = response_json[0]["id"]
            print("✓ Organization ID: {}\n".format(orgId))
        else:
            print("✗ Not a valid Org\n")
            return

        print("Step 2: Fetching active CDU subscriptions...")
        selected_cdus = []

        url = (
            "https://api.cruxinformatics.com/v2/ops/cduSubscriptions"
            "?order=cduId&filter=orgId.eq.{},status.eq"
            ".Active&limit=5000&offset=0".format(orgId)
        )
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
            return
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))
            return

        response_json = response.json()
        if response_json:
            for i in response_json:
                selected_cdus.append(i["cduId"])
            # CHANGED: Only print count, not all CDUs
            print("✓ Found {} active CDU subscription(s)\n".format(len(selected_cdus)))
        else:
            print("✗ {} doesn't have any Active CDU entitlements\n".format(org_id))
            return

        print("Step 3: Fetching catalog data for CDUs...")
        catalog = SnowflakeOrgs.get_catalog(selected_cdus, token, None)
        expected_views = set()
        existing_views = set()

        # Build expected views from catalog and normalize to uppercase
        print("Step 4: Building expected views list...")
        print(
            "  Note: Normalizing CDU prefixes from 'Cx' to 'CX' to match Snowflake storage\n"
        )

        for i, items in catalog.items():
            for frame in items["frames"]:
                view_name = frame["view_name"]
                # Normalize the CDU prefix to uppercase to match Snowflake storage
                parts = view_name.split("--", 1)
                if len(parts) == 2:
                    cdu_part = parts[0].upper()  # Convert "Cx13336" to "CX13336"
                    rest = parts[1]
                    normalized_view = "--".join([cdu_part, rest])
                else:
                    normalized_view = view_name.upper()
                expected_views.add(normalized_view)

        print("✓ Built {} expected view(s) from catalog\n".format(len(expected_views)))

        print("Step 5: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        # Check CRUX_CURATED views - keep as-is from Snowflake (already uppercase)
        print("Step 6: Checking CRUX_CURATED views...")
        curated_sql = """
            SELECT table_name
            FROM CX_DATA_LAKE.INFORMATION_SCHEMA.VIEWS
            WHERE table_schema = 'CRUX_CURATED'
              AND (
                  {filter_clause}
              )
        """.format(
            filter_clause=" OR ".join(
                ["table_name ILIKE '{}%'".format(cdu.upper()) for cdu in selected_cdus]
            )
        )
        print("Executing SQL:")
        print(curated_sql + "\n")

        cur.execute(curated_sql)

        curated_count = 0
        for row in cur:
            view_name = row["TABLE_NAME"]
            # Keep as-is from Snowflake - it's already in the correct case (uppercase)
            existing_views.add(view_name)
            curated_count += 1

        print("✓ Found {} existing CRUX_CURATED view(s)\n".format(curated_count))

        missing_curated_views = sorted(expected_views - existing_views)

        # Check CRUX_GENERAL views - keep as-is from Snowflake (already uppercase)
        print("Step 7: Checking CRUX_GENERAL views...")
        general_sql = """
               SELECT table_name
               FROM CX_DATA_LAKE.INFORMATION_SCHEMA.VIEWS
               WHERE table_schema = 'CRUX_GENERAL'
                 AND (
                     {filter_clause}
                 )
           """.format(
            filter_clause=" OR ".join(
                ["table_name ILIKE '{}%'".format(cdu.upper()) for cdu in selected_cdus]
            )
        )
        print("Executing SQL:")
        print(general_sql + "\n")

        cur.execute(general_sql)

        general_count = 0
        for row in cur:
            view_name = row["TABLE_NAME"]
            # Keep as-is from Snowflake - it's already in the correct case (uppercase)
            existing_views.add(view_name)
            general_count += 1

        print("✓ Found {} existing CRUX_GENERAL view(s)\n".format(general_count))

        missing_general_views = sorted(expected_views - existing_views)

        # Print results
        print("=" * 60)
        print("PROVISIONING SUMMARY FOR ORG: {}".format(org_id))
        print("=" * 60 + "\n")

        print("Expected views:  {}".format(len(expected_views)))
        print("Existing views:  {}".format(len(existing_views)))
        print(
            "Missing views:   {}\n".format(
                len(missing_curated_views) + len(missing_general_views)
            )
        )

        if missing_curated_views:
            print("=" * 60)
            print("MISSING CURATED VIEWS ({})".format(len(missing_curated_views)))
            print("=" * 60)
            for idx, view in enumerate(missing_curated_views, 1):
                print("  {}. {}".format(idx, view))
            print()
        else:
            print("✓ No MISSING CURATED VIEWS for Org {}\n".format(org_id))

        if missing_general_views:
            print("=" * 60)
            print("MISSING GENERAL VIEWS ({})".format(len(missing_general_views)))
            print("=" * 60)
            for idx, view in enumerate(missing_general_views, 1):
                print("  {}. {}".format(idx, view))
            print()
        else:
            print("✓ No MISSING GENERAL VIEWS for Org {}\n".format(org_id))

        print("=" * 60)
        print("Provision list completed for {}".format(org_id))
        print("=" * 60 + "\n")

        cur.close()
        conn.close()
        return

    def cdu_provision_add(self, view_name, region):
        # Strip whitespace from inputs
        view_name = view_name.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Adding CDU provision view")
        print("View name: {}".format(view_name))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except HTTPError as http_err:
            print("✗ HTTP error occurred: {}".format(http_err))
            print("✗ HTTP ERROR RESPONSE: {}".format(response.json()))
        except Exception as err:
            print("✗ Other error occurred: {}".format(err))

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print("User roles: {}\n".format(user_roles))

            if "ciam-role-CruxAdmin" in user_roles:
                confirmation = typer.prompt("Type ADD to confirm")
                if confirmation != "ADD":
                    typer.echo("Aborted.")
                    print("✗ You are not Authorized for ADD operation\n")
                    raise typer.Exit(code=1)
                print("✓ Authorization confirmed\n")
            else:
                typer.echo("✗ You are not authorized for ADD")
                raise typer.Exit(code=1)
        else:
            print("✗ Not Authorized for ADD operation\n")
            raise typer.Exit(code=1)

        print("Step 2: Parsing view name...")
        parts = view_name.split("--", 1)
        cdu_id = parts[0].upper()
        table_name = parts[1]

        # Strip the CX prefix for use in CRUX_SERVICE_RESTRICTIONS queries
        cdu_numeric = cdu_id.replace("CX", "").replace("Cx", "")

        print("  CDU ID: {}".format(cdu_id))
        print("  CDU Numeric: {}".format(cdu_numeric))
        print("  Table name: {}\n".format(table_name))

        print("Step 3: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 4: Fetching schema information for table: {}".format(table_name))
        crux_schemas = SnowflakeOrgs.get_crux_schemas(table_name, cur)

        if not crux_schemas:
            print("✗ Table has NOT been CURATED: {}\n".format(table_name))
            print(
                "The table '{}' does not exist in CX_DATA_LAKE.PRIVATE.CRUX_SCHEMA".format(
                    table_name
                )
            )
            print("\nTroubleshooting steps:")
            print("1. Verify the table name is correct")
            print("2. Check if the table exists in CRUX_SCHEMA:")
            print("   SELECT * FROM CX_DATA_LAKE.PRIVATE.CRUX_SCHEMA")
            print("   WHERE tablename = '{}'".format(table_name))
            print("3. The table may need to be curated first before creating a view\n")
            cur.close()
            conn.close()
            return

        print("✓ Schema found for table\n")
        cs = crux_schemas.get(table_name)

        print("Step 5: Building view DDL...")
        ide = {"field_name": "crux_ingestion_dt", "data_type": "DATETIME"}
        sde = {"field_name": "crux_supplier_implied_dt", "data_type": "DATETIME"}
        fields = []

        for pos in sorted(cs["fields"].keys()):
            ce = cs["fields"][pos]
            if ce["field_name"] == sde["field_name"]:
                ce = sde
            elif ce["field_name"] == ide["field_name"]:
                ce = ide
            elif cs and cs["fields"].get(pos):
                ce = cs["fields"][pos]
            field = '"{}"'.format(ce["field_name"])
            if len(ce["field_name"]) > 255:
                print("✗ Curated field name too long: {}\n".format(ce["field_name"]))
                cur.close()
                conn.close()
                return
            if ce["data_type"] == "DATE":
                field = ' try_to_date(cast("{}" as varchar))'.format(ce["field_name"])
            else:
                field += "::" + ce["data_type"]

            field += ' as "{}"'.format(ce["field_name"])
            fields.append(field)

        print("  Total fields: {}\n".format(len(fields)))

        vname = "--".join([cdu_id, table_name])
        entitlement_date = "crux_supplier_implied_dt"
        create = 'CREATE SECURE VIEW CX_DATA_LAKE.CRUX_CURATED."{}" AS'.format(vname)
        select = 'SELECT {} FROM CX_DATA_LAKE.PRIVATE."{}"'.format(
            ", ".join(fields), table_name
        )
        view_parts = [create, select]

        predicate = (
            'WHERE "{}" < '.format(entitlement_date)
            + '(select distinct "SVC_BLOCK_DT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + "and \"CDU_ID\"= '{}' and \"STATUS\"='ACTIVE') ".format(cdu_numeric)
            + 'AND (("{}" >= '.format(entitlement_date)
            + '(select distinct "R1_SDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + "and \"CDU_ID\"= '{}' and \"STATUS\"='ACTIVE') ".format(cdu_numeric)
            + 'AND "{}" <='.format(entitlement_date)
            + '(select distinct "R1_EDT"'
            + ' from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + "and \"CDU_ID\"= '{}' and \"STATUS\"='ACTIVE')) ".format(cdu_numeric)
            + 'OR ("{}" >= '.format(entitlement_date)
            + '(select distinct "R2_SDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account() '
            + "and \"CDU_ID\"= '{}' and \"STATUS\"='ACTIVE') ".format(cdu_numeric)
            + 'AND "{}" <= '.format(entitlement_date)
            + '(select distinct "R2_EDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + 'where "SVC_ACCT" = current_account()'
            + " and \"CDU_ID\"= '{}' and \"STATUS\"='ACTIVE'))".format(cdu_numeric)
        )
        view_parts.append(predicate)
        view_ddl = " ".join(view_parts) + ";"

        print("Step 6: Creating view: CX_DATA_LAKE.CRUX_CURATED.{}".format(vname))
        print("Executing DDL:")
        print(view_ddl[:500] + "...\n")  # Print first 500 chars

        try:
            cur.execute(view_ddl)
            print("✓ Created view: CX_DATA_LAKE.CRUX_CURATED.{}\n".format(view_name))
        except Exception as err:
            print("✗ Other error occurred: {}\n".format(err))

        print("=" * 60)
        print("CDU provision add completed")
        print("=" * 60 + "\n")

        cur.close()
        conn.close()
        return

    def cdu_provision_list(self, cdu_id, region):
        """List provisioned views for a specific CDU.

        Args:
            cdu_id: CDU ID (e.g., 'Cx00717')
            region: Region (US/EU/VPS)
        """
        # Strip whitespace from inputs
        cdu_id = cdu_id.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Listing provisioned views for CDU: {}".format(cdu_id))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        # Normalize CDU ID to uppercase
        cdu_id_upper = cdu_id.upper()
        print("CDU ID (normalized): {}\n".format(cdu_id_upper))

        print("Step 1: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        # Check CRUX_CURATED views
        print("Step 2: Checking CRUX_CURATED views for CDU {}...".format(cdu_id_upper))
        curated_sql = """
            SELECT table_name
            FROM CX_DATA_LAKE.INFORMATION_SCHEMA.VIEWS
            WHERE table_schema = 'CRUX_CURATED'
              AND table_name ILIKE '{}%'
            ORDER BY table_name
        """.format(
            cdu_id_upper
        )

        print("Executing SQL:")
        print(curated_sql + "\n")

        try:
            cur.execute(curated_sql)
            curated_views = []
            for row in cur:
                curated_views.append(row["TABLE_NAME"])

            print("✓ Found {} CRUX_CURATED view(s)\n".format(len(curated_views)))
        except Exception as e:
            print("✗ Error querying CRUX_CURATED: {}\n".format(e))
            curated_views = []

        # Check CRUX_GENERAL views
        print("Step 3: Checking CRUX_GENERAL views for CDU {}...".format(cdu_id_upper))
        general_sql = """
            SELECT table_name
            FROM CX_DATA_LAKE.INFORMATION_SCHEMA.VIEWS
            WHERE table_schema = 'CRUX_GENERAL'
              AND table_name ILIKE '{}%'
            ORDER BY table_name
        """.format(
            cdu_id_upper
        )

        print("Executing SQL:")
        print(general_sql + "\n")

        try:
            cur.execute(general_sql)
            general_views = []
            for row in cur:
                general_views.append(row["TABLE_NAME"])

            print("✓ Found {} CRUX_GENERAL view(s)\n".format(len(general_views)))
        except Exception as e:
            print("✗ Error querying CRUX_GENERAL: {}\n".format(e))
            general_views = []

        # Check CRUX_WRANGLE views
        print("Step 4: Checking CRUX_WRANGLE views for CDU {}...".format(cdu_id_upper))
        wrangle_sql = """
            SELECT table_name
            FROM CX_DATA_LAKE.INFORMATION_SCHEMA.VIEWS
            WHERE table_schema = 'CRUX_WRANGLE'
              AND table_name ILIKE '{}%'
            ORDER BY table_name
        """.format(
            cdu_id_upper
        )

        print("Executing SQL:")
        print(wrangle_sql + "\n")

        try:
            cur.execute(wrangle_sql)
            wrangle_views = []
            for row in cur:
                wrangle_views.append(row["TABLE_NAME"])

            print("✓ Found {} CRUX_WRANGLE view(s)\n".format(len(wrangle_views)))
        except Exception as e:
            print("✗ Error querying CRUX_WRANGLE: {}\n".format(e))
            wrangle_views = []

        # Print results
        print("=" * 60)
        print("PROVISIONED VIEWS FOR CDU: {}".format(cdu_id_upper))
        print("=" * 60 + "\n")

        total_views = len(curated_views) + len(general_views) + len(wrangle_views)
        print("Total views found: {}\n".format(total_views))

        if curated_views:
            print("=" * 60)
            print("CRUX_CURATED VIEWS ({})".format(len(curated_views)))
            print("=" * 60)
            for idx, view in enumerate(curated_views, 1):
                print("  {}. {}".format(idx, view))
            print()
        else:
            print("No CRUX_CURATED views found\n")

        if general_views:
            print("=" * 60)
            print("CRUX_GENERAL VIEWS ({})".format(len(general_views)))
            print("=" * 60)
            for idx, view in enumerate(general_views, 1):
                print("  {}. {}".format(idx, view))
            print()
        else:
            print("No CRUX_GENERAL views found\n")

        if wrangle_views:
            print("=" * 60)
            print("CRUX_WRANGLE VIEWS ({})".format(len(wrangle_views)))
            print("=" * 60)
            for idx, view in enumerate(wrangle_views, 1):
                print("  {}. {}".format(idx, view))
            print()
        else:
            print("No CRUX_WRANGLE views found\n")

        print("=" * 60)
        print("CDU provision list completed for {}".format(cdu_id_upper))
        print("=" * 60 + "\n")

        cur.close()
        conn.close()
        return

    def cdu_provision_add_table(self, table_name, region):
        # Strip whitespace from inputs
        table_name = table_name.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Creating CDU table from deliveries")
        print("Table name: {}".format(table_name))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Parsing table name...")
        if "__" in table_name:
            frames, dataset_id = table_name.split("__")[0], table_name.split("__")[1]
        else:
            frames, dataset_id = table_name.split("--")[0], table_name.split("--")[1]

        print("  Frame: {}".format(frames))
        print("  Dataset ID: {}\n".format(dataset_id))

        print("Step 2: Fetching latest frame resources...")
        schemas = {}
        frame_resources = SnowflakeOrgs.get_latest_frame_resources(
            dataset_id, frames, token
        )

        frame_resources = {k.upper(): v for k, v in frame_resources.items()}
        unused_frames = [f for f in frame_resources if frame_resources[f] is None]

        if not frame_resources:
            print("✗ Could not find any deliveries for dataset {}\n".format(dataset_id))
            return

        print("✓ Found frame resources\n")

        print("Step 3: Extracting Avro schemas from deliveries...")
        for frame, file in frame_resources.items():
            if file is None:
                continue
            table = "{}--{}".format(frame, file["datasetId"])
            print("\nProcessing frame: {}".format(frame))
            print("  Table: {}".format(table))

            resource_id = file["resourceId"]
            print("  Resource ID: {}".format(resource_id))

            url = "https://api.cruxinformatics.com/v2/ops/resources/{}/content-url".format(
                resource_id
            )
            print("  API URL: {}".format(url))

            try:
                response = requests.get(
                    url,
                    headers={
                        "Accept": "application/json",
                        "Authorization": "Bearer {}".format(token),
                    },
                )
                response.raise_for_status()
                print("  API Response Status: {}".format(response.status_code))
            except HTTPError as http_err:
                print("  ✗ HTTP error occurred: {}".format(http_err))
                print("  ✗ HTTP ERROR RESPONSE: {}".format(response.json()))
                continue
            except Exception as err:
                print("  ✗ Other error occurred: {}".format(err))
                continue

            response_json = response.json()
            signed_url = response_json.get("url")
            if not signed_url:
                print("  ✗ No signed URL returned for resource {}".format(resource_id))
                continue

            print("  ✓ Got signed URL")
            print("  Downloading and reading Avro schema...")

            try:
                with requests.get(signed_url, stream=True) as r:
                    schema = fastavro.reader(r.raw).writer_schema
                print("  ✓ Avro schema extracted")
            except Exception as e:
                print("  ✗ Failed to read Avro schema: {}".format(e))
                continue

            fields = []
            for schema_element in schema["fields"]:
                fname = schema_element["name"].replace('"', "inch")
                ftype = schema_element["type"][
                    1
                ]  # assumes union types like ["null", "string"]

                if isinstance(ftype, dict) and "logicalType" in ftype:
                    ftype = "date"
                elif ftype == "double":
                    ftype = "float"
                elif ftype in ("int64", "long"):
                    ftype = "integer"

                fields.append('"{}" {}'.format(fname, ftype))

            create_sql = 'CREATE TABLE CX_DATA_LAKE.PRIVATE."{}" ({})'.format(
                table_name, ", ".join(fields)
            )
            schemas[table] = {"create_sql": create_sql}
            print("  ✓ Schema built with {} fields".format(len(fields)))

        if not schemas:
            print("\n✗ No valid schemas found to create table {}\n".format(table_name))
            return

        print("\nStep 4: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")

        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        print("Step 5: Creating table in Snowflake...")
        if table_name not in schemas:
            print(
                "✗ Frame not found in deliveries to create table {}\n".format(
                    table_name
                )
            )
            if unused_frames:
                print("  Unused frames: {}\n".format(unused_frames))
            cur.close()
            conn.close()
            return
        else:
            create_sql = schemas[table_name]["create_sql"]
            print("Executing DDL:")
            print(create_sql + "\n")

            try:
                cur.execute(create_sql)
                print(
                    '✓ Created Private table: CX_DATA_LAKE.PRIVATE."{}"\n'.format(
                        table_name
                    )
                )
            except snowflake.connector.errors.ProgrammingError as e:
                print("✗ Failed to create table {}:".format(table_name))
                print("   DDL: {}".format(create_sql))
                print("   Error [{}]: {}\n".format(e.errno, e.msg))

        print("=" * 60)
        print("CDU provision add table completed")
        print("=" * 60 + "\n")

        cur.close()
        conn.close()

    @staticmethod
    def get_catalog(selected_cdus, token, client_schema):
        frames_catalog = defaultdict(list)
        dataframes_catalog = defaultdict(list)

        full_response = []

        for i in range(0, len(selected_cdus), 100):
            chunk = selected_cdus[i : i + 100]
            in_filter = ":".join(f"cduId.eq.{cdu.capitalize()}" for cdu in chunk)
            url = (
                "https://api.cruxinformatics.com/v2/ops/frame-catalog"
                f"?filter={in_filter}&offset=0&limit=10000"
            )
            try:
                response = requests.get(
                    url,
                    headers={
                        "Accept": "application/json",
                        "Authorization": f"Bearer {token}",
                    },
                )
                response.raise_for_status()
            except HTTPError as http_err:
                print(f"HTTP error occurred: {http_err}")
                try:
                    print("HTTP ERROR RESPONSE:", response.json())
                except Exception:
                    pass
                continue
            except Exception as err:
                print(f"Other error occurred: {err}")
                continue

            try:
                response_json = response.json()
                full_response.extend(
                    response_json
                )  # assuming response is a list of dicts
            except Exception as json_err:
                print(f"JSON decode error: {json_err}")
                continue

        for data in full_response:
            cduid = data.get("cduId")
            frameid = data.get("frameId", "").upper()
            legacyid = data.get("datasetLegacyId", "")
            datasetid = data.get("datasetId")
            view_name = "--".join([cduid, frameid, legacyid])
            table_name = "--".join([frameid, legacyid])
            client_schema_view_name = "_".join([cduid, frameid, datasetid])
            datadict = {
                "view_name": view_name,
                "table_name": table_name,
                "client_schema_view_name": client_schema_view_name.upper(),
            }
            if cduid:
                frames_catalog[cduid].append(datadict)

        for i in range(0, len(selected_cdus), 100):
            chunk = selected_cdus[i : i + 100]
            in_filter = ":".join(f"outFrame-cduId.eq.{i.capitalize()}" for i in chunk)
            url = (
                f"https://api.cruxinformatics.com/v2/ops/dataframe-catalog?"
                f"filter={in_filter}&offset=0&limit=10000"
            )

            try:
                response = requests.get(
                    url,
                    headers={
                        "Accept": "application/json",
                        "Authorization": f"Bearer {token}",
                    },
                )
                response.raise_for_status()
            except HTTPError as http_err:
                print(f"HTTP error occurred: {http_err}")
                try:
                    print("HTTP ERROR RESPONSE:", response.json())
                except Exception:
                    pass
                continue
            except Exception as err:
                print(f"Other error occurred: {err}")
                continue

            try:
                response_json = response.json()
                full_response.extend(
                    response_json
                )  # assuming response is a list of dicts
            except Exception as json_err:
                print(f"JSON decode error: {json_err}")
                continue

            for data in full_response:
                out_frame = data.get("outFrame", {})
                cduid = out_frame.get("cduId")
                if cduid:
                    dataframes_catalog[cduid].append(data)

        catalog = {}
        for cdu_input in selected_cdus:
            key = cdu_input.capitalize()
            frames = frames_catalog.get(key, [])
            data_frames = dataframes_catalog.get(key)

            cdu_catalog = {"frames": frames, "data_frames": data_frames}
            catalog[key] = cdu_catalog

        return catalog

    @staticmethod
    def get_crux_schemas(table_name, cur):
        schemas = {}
        if not table_name:
            return schemas

        sql_query = (
            "SELECT tablename, ord, fieldname as field_name, "
            + "ftype as data_type, pkey as primary_key FROM ("
            + "SELECT *, ROW_NUMBER() "
            + "OVER (PARTITION BY tablename, ord "
            + "order by tablename, ord) row_number "
            + "FROM CX_DATA_LAKE.PRIVATE.CRUX_SCHEMA) "
            + f"WHERE row_number = 1 and tablename = '{table_name}' "
            + "order by tablename, ord"
        )
        try:
            cur.execute(sql_query)
            for data in cur:
                table_name = data["TABLENAME"]
                ord = data["ORD"]
                if table_name not in schemas:
                    schemas[table_name] = {
                        "table_name": table_name,
                        "fields": {},
                        "primary_key": None,
                    }
                schemas[table_name]["fields"][ord] = {
                    "field_name": data["FIELD_NAME"],
                    "data_type": data["DATA_TYPE"],
                    "ord": ord,
                }
                if data["PRIMARY_KEY"] == "Yes":
                    schemas[table_name]["primary_key"] = data["FIELD_NAME"]
        except snowflake.connector.errors.ProgrammingError as e:
            print(f"Crux schema query failed: errno={e.errno} msg={e.msg}")
        return schemas

    @staticmethod
    def create_client_schema(cur, name, org, account):
        errors = []

        client_sql = f"create schema if not exists CX_DATA_LAKE.{name}"
        try:
            cur.execute(client_sql)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Create client schema failed: [{e.errno}] "
            errors.append(status)
            return

        share = None
        sql = f"show shares like 'CX00000: (CRUX) SHARE FOR {org}%' "
        print(f"   {sql}")
        cur.execute(sql)
        for data in cur:
            if data:
                share = data["name"]

        if not share:
            share = f"CX00000: (CRUX) SHARE FOR {org} - CX_DATA_LAKE"
            create_share = f'CREATE SHARE "{share}"'
            grant_db_ddl = f'GRANT USAGE ON DATABASE CX_DATA_LAKE TO SHARE "{share}"'
            grant_schema_ddl = (
                f'GRANT USAGE ON SCHEMA CX_DATA_LAKE.{name} TO SHARE "{share}"'
            )
            alter_share_ddl = f'ALTER SHARE "{share}" ADD ACCOUNTS = "{account}"'

            sql = [create_share, grant_db_ddl, grant_schema_ddl, alter_share_ddl]

            for create_ddl in sql:
                try:
                    print("DDL", create_ddl)
                    cur.execute(create_ddl)
                except snowflake.connector.errors.ProgrammingError as e:
                    status = f"Create share failed: [{e.errno}] "
                    errors.append(status)
                else:
                    status = f"Created/Granted share {create_ddl}"
                print(f"   {status}")

        else:
            grant_schema_ddl = (
                f'GRANT USAGE ON SCHEMA CX_DATA_LAKE.{name} TO SHARE "{share}"'
            )
            alter_share_ddl = f'ALTER SHARE "{share}" ADD ACCOUNTS = "{account}"'
            sql = [grant_schema_ddl, alter_share_ddl]
            for create_ddl in sql:
                try:
                    print("DDL", create_ddl)
                    cur.execute(create_ddl)
                except snowflake.connector.errors.ProgrammingError as e:
                    status = f"Create share failed: [{e.errno}] "
                    errors.append(status)
                else:
                    status = f"Created/Granted share {create_ddl}"
                print(f"   {status}")

        create_ddl = (
            "CREATE OR REPLACE SECURE VIEW "
            f"CX_DATA_LAKE.{name}.CRUX_CATALOG AS SELECT "
            + "cat.data_partiton, cat.catalog_source, cat.sensitive,"
            + " cat.version, cat.supplier_acronym, "
            + "cat.source_type, cat.market_vertical, "
            + "cat.asset_class, cat.category,"
            + " cat.crux_delivery_unit, cat.geographic_scope,"
            + " cat.country,"
            + "concat('CX', cat.cdu_id, '_', "
            + "replace(replace(cat.view_name, '.', '_'),'-','_'), "
            + "'_', replace(replace("
            "upper(cat.dsid_id),'.','_'),'-','_'))  as view_name,"
            + "cat.dataset_id, cat.frequency, cat.history_start, "
            + "cat.cdu_id, cat.display, cat.common, "
            "cat.source_file, cat.frame_group, cat.optional,"
            + "cat.cdu_type, cat.desc, cat.source_name, cat.source_acronym,"
            + " cat.supplier_name, cat.product_category, "
            "cat.cdu_summary, cat.doc_link, cat.source_categorization,"
            + "cat.dataset_name, cat.dsid_summary, cat.frame_name,"
            + " cat.frame_description, cat.frame_url, "
            + "cat.cron_schedule, cat.schedule, cat.sla, cat.underlying_cdu,"
            + " cat.curation_score, cat.buy, cat.packages, "
            + "cat.dsid_id, cat.product_status"
            f" from CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS ent,"
            f" CX_DATA_LAKE.PRIVATE.CRUX_CATALOG cat"
            f" WHERE ENT.ORG_ID = '{org}' AND "
            f"ENT.ENT_TYPE = 'ENTITLE' "
            f"AND ENT.CDU_ID=CAT.CDU_ID "
        )

        try:
            cur.execute(create_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Create CRUX_CATALOG failed: [{e.errno}] "
            errors.append(status)
        else:
            status = f"Created CRUX_CATALOG {create_ddl}"
            print(f"   {status}")

        grant_ddl = (
            f"GRANT SELECT ON VIEW "
            f"CX_DATA_LAKE.{name}.CRUX_CATALOG "
            f'TO SHARE "CX00000: (CRUX) SHARE FOR {org} - '
            f'CX_DATA_LAKE"'
        )
        try:
            cur.execute(grant_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Grant failed: ({grant_ddl}) [{e.errno}]"
            errors.append(status)
        else:
            status = f"Granted {grant_ddl}"
            print(f"   {status}")

        create_ddl = (
            f"CREATE OR REPLACE SECURE VIEW"
            f" CX_DATA_LAKE.{name}.CRUX_NOTIFICATIONS AS "
            f"select C.ORG, cast(C.notification_dt as timestamp) "
            f"as NOTIFICATION_DT, C.NOTIFICATION_TYPE, "
            f"CONCAT(SPLIT_PART(view_name, '--', 1), "
            f"'_', SPLIT_PART(view_name, '--', 2), '_',UPPER(A.DSID_ID)) as "
            f"VIEW_NAME,"
            f"C.CRUX_RESOURCE_ID, C.CRUX_DELIVERY_VERSION, "
            f"cast(C.crux_ingestion_dt as timestamp) as CRUX_INGESTION_DT, "
            f"C.FRAME_GROUP "
            f"from CX_DATA_LAKE.PRIVATE.CRUX_NOTIFICATIONS C INNER "
            f"JOIN (SELECT distinct SUBSTRING(cn.view_name, -26, "
            f"26) AS notif_dsid, cc.dsid_id "
            f"From CX_DATA_LAKE.PRIVATE.CRUX_NOTIFICATIONS AS CN,"
            f" CX_DATA_LAKE.PRIVATE.CRUX_CATALOG AS CC "
            f"where CC.dataset_id = SUBSTRING(cn.view_name, -26, 26)"
            f" and CN.ORG = '{org}')A where A.notif_dsid = "
            f"SUBSTRING(C.view_name, -26, 26)"
            f"and C.ORG = '{org}';"
        )
        try:
            cur.execute(create_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Create CRUX_NOTIFICATIONS failed: [{e.errno}] "
            errors.append(status)
        else:
            status = f"Created CRUX_NOTIFICATIONS share {create_ddl}"
            print(f"   {status}")

        grant_ddl = (
            f"GRANT SELECT ON VIEW "
            f"CX_DATA_LAKE.{name}.CRUX_NOTIFICATIONS "
            'TO SHARE "CX00000: (CRUX) SHARE '
            f'FOR {org} - CX_DATA_LAKE"'
        )
        try:
            cur.execute(grant_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Grant failed: ({grant_ddl}) [{e.errno}]"
            errors.append(status)
        else:
            status = f"Granted {grant_ddl}"
            print(f"   {status}")

        create_ddl = (
            f"CREATE OR REPLACE SECURE VIEW "
            f"CX_DATA_LAKE.{name}.CRUX_SCHEMAS AS "
            f"SELECT CONCAT('CX', ca.CDU_ID,'_',"
            f" SPLIT_PART(view_name, '--', 1), '_', "
            f"SPLIT_PART(view_name, '--', 2),"
            f"UPPER(CA.DSID_ID)) as VIEW_NAME,"
            f"ca.CDU_ID, UPPER(ca.FRAME_NAME) as FRAME_NAME, "
            f"ca.DSID_ID, sc.ORD, sc.FIELDNAME, sc.FTYPE, sc.DESC, "
            f"sc.CRUX_TYPE, sc.CXREF, sc.PKEY "
            f"FROM CX_DATA_LAKE.PRIVATE.CRUX_CATALOG AS ca,"
            f"CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS AS en, "
            f"CX_DATA_LAKE.PRIVATE.CRUX_SCHEMA AS sc "
            f"WHERE en.ORG_ID = '{org}' AND en.ENT_TYPE = 'ENTITLE' "
            f"AND en.CDU_ID = ca.CDU_ID "
            f"AND sc.DSID = ca.DATASET_ID AND sc.FRAME = UPPER(ca.FRAME_NAME) "
            f"ORDER BY ca.VIEW_NAME, sc.ORD"
        )
        try:
            cur.execute(create_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Create CRUX_SCHEMAS failed: [{e.errno}]"
            errors.append(status)
        else:
            status = f"Created CRUX_SCHEMAS share {create_ddl}"
            print(f"   {status}")

        grant_ddl = (
            f"GRANT SELECT ON VIEW CX_DATA_LAKE.{name}.CRUX_SCHEMAS"
            f' TO SHARE "CX00000: (CRUX) SHARE FOR {org} - '
            f'CX_DATA_LAKE"'
        )
        try:
            cur.execute(grant_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            status = f"Grant failed: ({grant_ddl}) [{e.errno}] "
            errors.append(status)
        else:
            status = f"Granted {grant_ddl}"
            print(f"   {status}")

        return share, errors

    @staticmethod
    def create_client_share(cur, name, org, account):
        errors = []

        share = None
        sql = f"show shares like 'CX00000: (CRUX) OLR SHARE FOR {org}%' "
        print(f"   {sql}")
        cur.execute(sql)
        for data in cur:
            if data:
                share = data["name"]

        if not share:
            share = f"CX00000: (CRUX) OLR SHARE FOR {org} - CX_DATA_LAKE"
            create_share = f'CREATE SHARE "{share}"'
            grant_db_ddl = f'GRANT USAGE ON DATABASE CX_DATA_LAKE TO SHARE "{share}"'
            alter_share_ddl = f'ALTER SHARE "{share}" ADD ACCOUNTS = "{account}"'

            sql = [create_share, grant_db_ddl, alter_share_ddl]

            for create_ddl in sql:
                try:
                    print("DDL", create_ddl)
                    cur.execute(create_ddl)
                except snowflake.connector.errors.ProgrammingError as e:
                    status = f"Create share failed: [{e.errno}] "
                    errors.append(status)
                else:
                    status = f"Created/Granted share {create_ddl}"
                print(f"   {status}")

            default_olr_share = [
                "CX00000--CRUX_CATALOG",
                "CX00000--CRUX_NOTIFICATIONS",
                "CX00000--CRUX_SCHEMAS",
            ]

            for data in default_olr_share:
                name = "--".join([data, org])
                vname = f'"{name}"'
                view = ".".join(["CX_DATA_LAKE", "CRUX_GENERAL", vname])
                grant_sql = f'GRANT SELECT ON VIEW {view} TO SHARE "{share}"'
                try:
                    cur.execute(grant_sql)
                except snowflake.connector.errors.ProgrammingError as e:
                    status = f"Grant failed: ({grant_sql}) [{e.errno}]"
                    print(status)
                except Exception as e:
                    print("Error running query:", e)
                print(f"View {view} has been added to share {share}")

        return share, errors

    @staticmethod
    def insert_client_schema(cur, client_schema, cdu_id, view_name):
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)
        selected_cdus = cdu_id.split(",")
        catalog = SnowflakeOrgs.get_catalog(selected_cdus, token, client_schema)
        table_name = ""
        cdu = cdu_id.replace("CX", "").replace("Cx", "")

        for i, items in catalog.items():
            for frame in items["frames"]:
                if view_name == frame["client_schema_view_name"]:
                    table_name = frame["table_name"]
                    crux_schemas = SnowflakeOrgs.get_crux_schemas(table_name, cur)
                    if not crux_schemas:
                        print("Table has NOT been CURATED")
                        return
                    cs = crux_schemas.get(table_name)
                    fields = []
                    for pos in sorted(cs["fields"].keys()):
                        ce = cs["fields"][pos]
                        field = f'"{ce["field_name"]}"'
                        if len(ce["field_name"]) > 255:
                            print(f"Curated field name too long: {ce['field_name']}")
                            break
                        field += f"::{ce['data_type']}"
                        field += f' as {ce["field_name"]}'
                        fields.append(field)

        entitlement_date = "to_varchar(current_date)"
        create = (
            f"CREATE OR REPLACE SECURE "
            f'VIEW CX_DATA_LAKE.{client_schema}."{view_name}" AS'
        )
        select = f'SELECT {", ".join(fields)} FROM CX_DATA_LAKE.PRIVATE."{table_name}"'
        view_parts = [create, select]

        predicate = (
            f"WHERE {entitlement_date} < "
            + '(select distinct "SVC_BLOCK_DT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + f'where "SVC_ACCT" = current_account() and "CDU_ID"= \'{cdu}\''
            + " and \"STATUS\"='ACTIVE') "
            + f"AND (({entitlement_date} >= "
            + '(select distinct "R1_SDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + f'where "SVC_ACCT" = current_account() and "CDU_ID"= \'{cdu}\''
            + " and \"STATUS\"='ACTIVE') "
            + f"AND {entitlement_date} <="
            + '(select distinct "R1_EDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + f'where "SVC_ACCT" = current_account() and "CDU_ID"= \'{cdu}\' '
            + "and \"STATUS\"='ACTIVE')) "
            + f"OR ({entitlement_date} >= "
            + '(select distinct "R2_SDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + f'where "SVC_ACCT" = current_account() and "CDU_ID"= \'{cdu}\' '
            + "and \"STATUS\"='ACTIVE') "
            + f"AND {entitlement_date} <= "
            + '(select distinct "R2_EDT" '
            + 'from CX_DATA_LAKE.PRIVATE."CRUX_SERVICE_RESTRICTIONS" '
            + f'where "SVC_ACCT" = current_account() and "CDU_ID"= \'{cdu}\''
            + " and \"STATUS\"='ACTIVE')))"
        )

        view_parts.append(predicate)
        view_ddl = " ".join(view_parts) + ";"
        try:
            cur.execute(view_ddl)
        except Exception as err:
            print("Other error occurred: {0}".format(err))
        else:
            print(f"Created view: CX_DATA_LAKE.{client_schema}.{view_name}")

        return

    @staticmethod
    def insert_wrangle_client_schema(cur, client_schema, view_name, wrangle_view_name):
        parts = wrangle_view_name.split("--")
        first_part = parts[0].replace("Cx", "CX")
        second_part = parts[1]
        last_part = parts[2]
        wrangle_table_name = "--".join([first_part, second_part, last_part])

        schemas = {}
        fields = []

        sql_query = (
            "SELECT table_name, ordinal_position AS ord, column_name AS field_name, data_type "
            "FROM INFORMATION_SCHEMA.COLUMNS "
            "WHERE table_catalog = 'CX_DATA_LAKE' "
            "AND table_schema = 'CRUX_WRANGLE' "
            f"AND table_name = '{wrangle_table_name}' "
            "ORDER BY table_name, ordinal_position"
        )

        cur.execute(sql_query)

        for data in cur:
            table_name = data["TABLE_NAME"]
            ord_pos = data["ORD"]

            if table_name not in schemas:
                schemas[table_name] = {"table_name": table_name, "fields": {}}

            schemas[table_name]["fields"][ord_pos] = {
                "field_name": data["FIELD_NAME"],
                "data_type": data["DATA_TYPE"].replace("TEXT", "STRING"),
                "ord": ord_pos,
            }

        table_schema = schemas.get(wrangle_table_name)
        if not table_schema:
            raise ValueError(f"No schema found for table: {wrangle_table_name}")

        for pos in sorted(table_schema["fields"].keys()):
            field_info = table_schema["fields"][pos]
            field_name = field_info["field_name"]
            field = f'"{field_name}" AS "{field_name}"'
            fields.append(field)

        wrangle_fields = fields

        create = (
            "CREATE OR REPLACE SECURE"
            f' VIEW CX_DATA_LAKE.{client_schema}."{view_name}" AS'
        )
        select = (
            f'SELECT {", ".join(wrangle_fields)}'
            f' FROM CX_DATA_LAKE.CRUX_WRANGLE."{wrangle_table_name}"'
        )
        view_parts = [create, select]
        view_ddl = " ".join(view_parts) + ";"

        try:
            cur.execute(view_ddl)
        except snowflake.connector.errors.ProgrammingError as e:
            print(f"Create failed:\n{view_ddl}\nError: [{e.errno}] {e.msg}")
        else:
            print(f"Created view:\n{view_ddl}")

    @staticmethod
    def delete_client_schema(cur, client_schema, view_name):
        drop_sql = f"DROP VIEW CX_DATA_LAKE.{client_schema}.{view_name}"

        try:
            cur.execute(drop_sql)
            print(f"Successfully dropped view: {drop_sql}")
        except Exception as err:
            print(f"Error dropping view {drop_sql}: {err}")

        return

    @staticmethod
    def get_latest_frame_resources(dataset_id, frames, token):
        return_frames = {}
        resource_id_to_frame = {}

        now = datetime.utcnow()
        codt = date(now.year, now.month, now.day)

        lookbacks = [
            {"start": codt - timedelta(days=16), "end": None},
            {"start": codt - timedelta(days=32), "end": codt - timedelta(days=17)},
            {"start": codt - timedelta(days=92), "end": codt - timedelta(days=33)},
            {"start": codt - timedelta(days=212), "end": codt - timedelta(days=93)},
            {"start": codt - timedelta(days=366), "end": codt - timedelta(days=213)},
            {
                "start": datetime(year=codt.year - 1, month=6, day=1),
                "end": datetime(year=codt.year - 1, month=6, day=15),
            },
            {
                "start": datetime(year=codt.year - 1, month=1, day=1),
                "end": datetime(year=codt.year - 1, month=1, day=15),
            },
            {"start": None, "end": None},
        ]

        for dt in lookbacks:
            start_date = dt["start"]
            if start_date is None:
                continue  # Skip if no date to query

            lookback_frames = set()

            url = (
                f"https://api.cruxinformatics.com/v1/ops/deliveries/"
                f"{dataset_id}/ids?start_date={start_date}&delivery_status="
                f"DELIVERY_SUCCEEDED&limit=1000&offset=0"
            )

            try:
                response = requests.get(
                    url,
                    headers={
                        "Accept": "application/json",
                        "Authorization": f"Bearer {token}",
                    },
                )
                response.raise_for_status()
            except HTTPError as http_err:
                if response.status_code == 404:
                    # Not Found – skip this lookback
                    continue
                print(f"HTTP error occurred: {http_err}")
                try:
                    print("HTTP ERROR RESPONSE:", response.json())
                except Exception:
                    pass
                continue
            except Exception as err:
                print(f"Other error occurred: {err}")
                continue
            try:
                lb_deliveries = response.json()
            except Exception as err:
                print(f"Error parsing response JSON: {err}")
                continue

            for delivery in reversed(lb_deliveries):
                url = (
                    f"https://api.cruxinformatics.com/v1/ops/deliveries/"
                    f"{dataset_id}/{delivery}/data?delivery_resource_format=avro/binary&"
                    f"delivery_lookback=True&limit=1000&offset=0"
                )
                try:
                    response = requests.get(
                        url,
                        headers={
                            "Accept": "application/json",
                            "Authorization": f"Bearer {token}",
                        },
                    )
                    response.raise_for_status()
                    delivery_data = response.json()
                except HTTPError as http_err:
                    if response.status_code == 404:
                        # Not Found – skip this lookback
                        continue
                    print(f"HTTP error occurred: {http_err}")
                    try:
                        print("HTTP ERROR RESPONSE:", response.json())
                    except Exception:
                        pass
                    continue
                except Exception as err:
                    print(f"Other error occurred: {err}")
                    continue
                delivery_frames = set()
                for item in delivery_data.get("resources", []):
                    assert isinstance(item, dict)
                    resource_id = item["resource_id"]
                    frame_id = item["frame_id"]
                    if frame_id in delivery_frames:
                        continue  # backfills can have many resources for the same frame
                    delivery_frames.add(frame_id)
                    if frame_id not in return_frames:
                        lookback_frames.add(frame_id.upper())
                        return_frames[frame_id] = None
                    if frames and frame_id.upper() not in frames:
                        continue
                    resource_id_to_frame[resource_id] = frame_id

            print(
                f"   New frames found: {lookback_frames if lookback_frames else None}"
            )
            if resource_id_to_frame:
                break

        limit = 100
        resources = []
        for i in range(0, len(resource_id_to_frame), limit):
            chunk = list(resource_id_to_frame.keys())[i : i + limit]
            ids = ",".join(chunk)
            url = f"https://api.cruxinformatics.com/v2/ops/resources?" f"ids={ids}"
            try:
                response = requests.get(
                    url,
                    headers={
                        "Accept": "application/json",
                        "Authorization": f"Bearer {token}",
                    },
                )
                response.raise_for_status()
                for resource in response.json():
                    resources.append(resource)

            except HTTPError as http_err:
                if response.status_code == 404:
                    # Not Found – skip this lookback
                    continue
                print(f"HTTP error occurred: {http_err}")
                try:
                    print("HTTP ERROR RESPONSE:", response.json())
                except Exception:
                    pass
                continue
            except Exception as err:
                print(f"Other error occurred: {err}")
                continue

        for file in resources:
            resource_id = file["resourceId"]
            frame_id = resource_id_to_frame[resource_id]
            supplier_implied_dt = file["supplierImpliedDt"]
            ingestion_dt = file["ingestionDt"]
            best_file = return_frames[frame_id]
            if (
                best_file is None
                or (supplier_implied_dt > best_file["supplier_implied_dt"])
                or (
                    supplier_implied_dt == best_file["supplier_implied_dt"]
                    and ingestion_dt > best_file["ingestion_dt"]
                )
            ):
                file["frame_id"] = frame_id
                file["supplier_implied_dt"] = supplier_implied_dt
                file["ingestion_dt"] = ingestion_dt
                return_frames[frame_id] = file
        return return_frames

    def wrangles_validate(self, sql_file, region=None):
        """
        Validate wrangle SQL by creating view in CRUX_WRANGLE_UAT schema.

        Args:
            sql_file: Path to SQL file containing wrangle definition
            region: Region (US/EU/VPS) - defaults to US
        """
        import os

        if not os.path.exists(sql_file):
            print(f"Error: SQL file not found: {sql_file}")
            raise typer.Exit(1)

        with open(sql_file, "r") as f:
            sql_content = f.read()

        # Extract view name - look for the full qualified name with quotes
        view_match = re.search(
            (
                r"CREATE\s+(?:OR\s+REPLACE\s+)?(?:SECURE\s+)?VIEW\s+"
                r'CX_DATA_LAKE\.CRUX_WRANGLE\."([^"]+)"'
            ),
            sql_content,
            re.IGNORECASE,
        )

        if not view_match:
            print("Error: Could not find CREATE VIEW statement with expected format")
            print('Expected: CREATE ... VIEW CX_DATA_LAKE.CRUX_WRANGLE."<view_name>"')
            raise typer.Exit(1)

        view_name = view_match.group(1)  # This is just the view name without quotes

        print("Validating wrangle SQL in CRUX_WRANGLE_UAT...")
        print(f"View name: {view_name}")

        # Replace schema with UAT schema
        uat_sql = sql_content.replace(
            "CX_DATA_LAKE.CRUX_WRANGLE", "CX_DATA_LAKE.CRUX_WRANGLE_UAT"
        )

        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")
        if not connection_func:
            print(f"Unsupported region: {region}")
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)

        try:
            # Create view in UAT
            cur.execute(uat_sql)
            print("✓ SQL validation successful!")

            # Construct UAT view name for testing
            uat_view = f'CX_DATA_LAKE.CRUX_WRANGLE_UAT."{view_name}"'

            # Test a simple query
            test_query = f"SELECT * FROM {uat_view} LIMIT 1"
            print("Testing query on UAT view...")

            cur.execute(test_query)
            result = cur.fetchone()

            if result:
                print("✓ Test query successful - view contains data")
                print(f"✓ View is working: {uat_view}")
            else:
                print("⚠ Warning: View created but returned no data")

            print("\nNext steps:")
            print(f"1. Review the UAT view: {uat_view}")
            print(
                f"2. If satisfied, run: wrangles-add --sql_file {sql_file} --org_id <ORG_ID>"
            )

        except Exception as e:
            print(f"✗ Validation failed: {e}")
            raise typer.Exit(1)
        finally:
            cur.close()
            conn.close()

    def wrangles_add(self, sql_file, org_id, region=None, dry_run=False):
        """
        Create wrangle view in CRUX_WRANGLE (production) and grant to customer share.

        Args:
            sql_file: Path to SQL file containing wrangle definition
            org_id: Organization ID to grant access
            region: Region (US/EU/VPS) - defaults to US
            dry_run: If True, show what would be done without executing
        """
        import os
        import re

        print(f"\n{'=' * 60}")
        print(f"{'[DRY RUN] ' if dry_run else ''}Adding wrangle view")
        print(f"SQL File: {sql_file}")
        print(f"Organization: {org_id}")
        print(f"Region: {region or 'US'}")
        print(f"{'=' * 60}\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Reading SQL file...")
        if not os.path.exists(sql_file):
            print(f"✗ Error: SQL file not found: {sql_file}\n")
            raise typer.Exit(1)

        with open(sql_file, "r") as f:
            sql_content = f.read()

        print(f"✓ SQL file loaded ({len(sql_content)} characters)\n")

        print("Step 2: Extracting view name from SQL...")
        # Extract view name - look for the full qualified name with quotes
        view_match = re.search(
            (
                r"CREATE\s+(?:OR\s+REPLACE\s+)?(?:SECURE\s+)?VIEW\s+"
                r'CX_DATA_LAKE\.CRUX_WRANGLE\."([^"]+)"'
            ),
            sql_content,
            re.IGNORECASE,
        )

        if not view_match:
            print("✗ Error: Could not find view name in SQL")
            print('Expected: CREATE ... VIEW CX_DATA_LAKE.CRUX_WRANGLE."<view_name>"\n')
            raise typer.Exit(1)

        view_name = view_match.group(1)
        cdu_id = view_name.split("--")[0]
        cdu_numeric = cdu_id.replace("CX", "").replace("Cx", "")

        print(f"✓ View name: {view_name}")
        print(f"  CDU ID: {cdu_id}")
        print(f"  CDU Numeric: {cdu_numeric}\n")

        print("Step 3: Validating CDU_ID in SQL WHERE clause...")
        # Verify CDU_ID in SQL matches view name
        if (
            f"\"CDU_ID\"= '{cdu_numeric}'" not in sql_content
            and f"\"CDU_ID\"= '{cdu_numeric}'" not in sql_content
        ):
            print(
                "⚠ WARNING: SQL WHERE clause should use CDU_ID "
                f"= '{cdu_numeric}' (without CX prefix)"
            )
            print(f"   Found in view name: {cdu_id}\n")
            if not dry_run:
                confirmation = typer.prompt("Continue anyway? (yes/no)")
                if confirmation.lower() != "yes":
                    raise typer.Exit(1)
        else:
            print("✓ CDU_ID validation passed\n")

        print(f"Step 4: Connecting to Snowflake ({region or 'US'})...")
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")
        if not connection_func:
            print(f"✗ Unsupported region: {region}\n")
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        try:
            # Check if view already exists and get existing grants
            print("Step 5: Checking for existing view and grants...")
            existing_grants = []
            try:
                show_grants = (
                    f'SHOW GRANTS ON VIEW CX_DATA_LAKE.CRUX_WRANGLE."{view_name}"'
                )
                print(f"Executing SQL:\n{show_grants}\n")

                cur.execute(show_grants)
                for row in cur:
                    if row.get("granted_to") == "SHARE":
                        share_name_full = row["grantee_name"]
                        print(f"  Found existing grant: {share_name_full}")

                        # IMPROVED: Handle multiple account prefixes and extract clean share name
                        if "." in share_name_full:
                            # Split by dots and find the part that starts with
                            # CX (the actual share name)
                            parts = share_name_full.split(".")
                            share_name_clean = None
                            for part in parts:
                                if part.strip().startswith("CX"):
                                    share_name_clean = part.strip()
                                    break
                            if not share_name_clean:
                                share_name_clean = parts[-1].strip()
                        else:
                            share_name_clean = share_name_full

                        # Only add if not empty
                        if share_name_clean:
                            existing_grants.append(share_name_clean)
                            print(f"    Cleaned share name: {share_name_clean}")

                if existing_grants:
                    print(
                        f"\n⚠ View already exists with grants to {len(existing_grants)} share(s):"
                    )
                    for share in existing_grants:
                        print(f"   - {share}")
                    print(
                        "\nOverwriting will disconnect these shares - "
                        "they will be re-granted automatically\n"
                    )
                else:
                    print("✓ No existing view found\n")
            except Exception:
                print("✓ No existing view found\n")

            if dry_run:
                print(f"\n{'=' * 60}")
                print("[DRY RUN] Would execute the following:")
                print(f"{'=' * 60}")
                print(f'1. Create view: CX_DATA_LAKE.CRUX_WRANGLE."{view_name}"')
                print(f"   SQL: {sql_content[:200]}...\n")
                print(
                    f"2. Grant to share: CX00000: (CRUX) WRANGLES FOR {org_id} - CX_DATA_LAKE"
                )
                if existing_grants:
                    print(f"3. Re-grant to {len(existing_grants)} existing share(s):")
                    for share in existing_grants:
                        print(f"   - {share}")
                print(f"{'=' * 60}\n")
                return

            # Create the view
            print("Step 6: Creating wrangle view...")
            print(f"Executing DDL:\n{sql_content[:500]}...\n")

            cur.execute(sql_content)
            print(f"✓ View created: CX_DATA_LAKE.CRUX_WRANGLE.{view_name}\n")

            # Find or create wrangle share for org
            share_name = f"CX00000: (CRUX) WRANGLES FOR {org_id} - CX_DATA_LAKE"

            print("Step 7: Checking if wrangle share exists...")
            cur.execute(f"SHOW SHARES LIKE '%{org_id}%'")
            shares = [row["name"] for row in cur if "WRANGLE" in row["name"]]

            if shares:
                print(f"✓ Found {len(shares)} wrangle share(s):")
                for s in shares:
                    print(f"  - {s}")
            else:
                print("⚠ No wrangle shares found")

            if share_name not in shares:
                print(f"\n⚠ Wrangle share not found: {share_name}")
                print("   You may need to create the share first\n")

            # Grant to org's wrangle share
            print("Step 8: Granting view to organization share...")
            grant_sql = (
                f'GRANT SELECT ON VIEW CX_DATA_LAKE.CRUX_WRANGLE."{view_name}" '
                f'TO SHARE "{share_name}"'
            )
            print(f"Executing SQL:\n{grant_sql}\n")

            try:
                cur.execute(grant_sql)
                print(f"✓ Granted to: {share_name}\n")
            except Exception as e:
                print(f"✗ Failed to grant: {e}\n")

            # Re-grant to existing shares if any
            if existing_grants:
                # Remove duplicates and the current share_name to avoid re-granting to itself
                unique_grants = []
                for share in existing_grants:
                    # Skip if it's the same as the share we just granted to
                    if share != share_name and share not in unique_grants:
                        unique_grants.append(share)

                if unique_grants:
                    print(
                        f"Step 9: Re-granting to {len(unique_grants)} existing share(s)..."
                    )
                    for share in unique_grants:
                        grant_sql = (
                            f"GRANT SELECT ON VIEW CX_DATA_LAKE.CRUX_WRANGLE."
                            f'"{view_name}" TO SHARE "{share}"'
                        )
                        print(f"\n  Share: {share}")
                        print(f"  Executing SQL:\n  {grant_sql}\n")

                        try:
                            cur.execute(grant_sql)
                            print(f"  ✓ Re-granted to: {share}")
                        except Exception as e:
                            print(f"  ✗ Failed to re-grant to {share}: {e}")

            print(f"\n{'=' * 60}")
            print("✓ Wrangle successfully deployed!")
            print(f"{'=' * 60}\n")

        except Exception as e:
            print(f"\n{'=' * 60}")
            print(f"✗ Failed: {e}")
            print(f"{'=' * 60}\n")
            raise typer.Exit(1)
        finally:
            cur.close()
            conn.close()

    def wrangles_list(self, org_id, region=None):
        """
        List all wrangle views for an organization and their share grants.

        Args:
            org_id: Organization ID
            region: Region (US/EU/VPS) - defaults to US
        """
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")
        if not connection_func:
            print(f"Unsupported region: {region}")
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)

        try:
            # Find wrangle share for org
            share_name = f"CX00000: (CRUX) WRANGLES FOR {org_id} - CX_DATA_LAKE"

            print(f"Wrangle views for {org_id}:")
            print("=" * 80)

            # Get all views in the share
            show_grants = f'SHOW GRANTS TO SHARE "{share_name}"'
            try:
                cur.execute(show_grants)
                views = []
                for row in cur:
                    if row.get("granted_on") == "VIEW" and "CRUX_WRANGLE" in row.get(
                        "name", ""
                    ):
                        view_name = row["name"].split(".")[-1].strip('"')
                        views.append(view_name)

                if not views:
                    print(f"No wrangle views found in share: {share_name}")
                else:
                    print(f"\nFound {len(views)} wrangle view(s):\n")
                    for view in sorted(views):
                        print(f"  • {view}")

                        # Show all shares this view is granted to
                        show_view_grants = (
                            f'SHOW GRANTS ON VIEW CX_DATA_LAKE.CRUX_WRANGLE."{view}"'
                        )
                        try:
                            cur.execute(show_view_grants)
                            shares = [
                                r["grantee_name"]
                                for r in cur
                                if r.get("granted_to") == "SHARE"
                            ]
                            if len(shares) > 1:
                                print(
                                    f"    Also granted to {len(shares) - 1} other share(s)"
                                )
                        except Exception:
                            pass
                        print()

            except Exception as e:
                print(f"Error: {e}")
                print(f"Share may not exist: {share_name}")

        finally:
            cur.close()
            conn.close()

    def wrangles_share_add(self, view_name, org_id, region=None):
        """
        Grant wrangle view to organization's share.

        Args:
            view_name: Wrangle view name (e.g., CX09815--TABLE_NAME--DATASET_ID)
            org_id: Organization ID to grant access
            region: Region (US/EU/VPS) - defaults to US
        """
        # Strip whitespace from inputs
        view_name = view_name.strip()
        org_id = org_id.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Granting wrangle view to share")
        print("View: {}".format(view_name))
        print("Organization: {}".format(org_id))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        print("Step 1: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")
        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        try:
            share_name = "CX00000: (CRUX) WRANGLES FOR {} - CX_DATA_LAKE".format(org_id)

            print("Step 2: Granting view to share...")
            print("View name: {}".format(view_name))
            print("Share name: {}\n".format(share_name))

            grant_sql = (
                'GRANT SELECT ON VIEW CX_DATA_LAKE.CRUX_WRANGLE."{}" '
                'TO SHARE "{}"'.format(view_name, share_name)
            )

            print("Executing SQL:")
            print(grant_sql + "\n")

            cur.execute(grant_sql)
            print("✓ Successfully granted access\n")

            print("=" * 60)
            print("Wrangle share add completed")
            print("=" * 60 + "\n")

        except Exception as e:
            print("✗ Failed: {}\n".format(e))
            raise typer.Exit(1)
        finally:
            cur.close()
            conn.close()

    def wrangles_share_remove(self, view_name, org_id, region=None):
        """
        Revoke wrangle view from organization's share.

        Args:
            view_name: Wrangle view name
            org_id: Organization ID
            region: Region (US/EU/VPS) - defaults to US
        """
        # Strip whitespace from inputs
        view_name = view_name.strip()
        org_id = org_id.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Revoking wrangle view from share")
        print("View: {}".format(view_name))
        print("Organization: {}".format(org_id))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Verifying admin authorization...")
        url = "https://api.cruxinformatics.com/v2/ops/identities/profile/api-key"
        print("API URL: {}\n".format(url))

        try:
            response = requests.get(
                url,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer {}".format(token),
                },
            )
            print("API Response Status: {}".format(response.status_code))
        except Exception as err:
            print("✗ Error occurred: {}\n".format(err))
            raise typer.Exit(1)

        response_json = response.json()
        if response_json:
            user_roles = response_json.get("roles")
            print("User roles: {}\n".format(user_roles))

            if "ciam-role-CruxAdmin" not in user_roles:
                print("✗ You are not authorized for this operation\n")
                raise typer.Exit(1)

            confirmation = typer.prompt("Type REVOKE to confirm")
            if confirmation != "REVOKE":
                print("Aborted.\n")
                raise typer.Exit(1)

            print("✓ Authorization confirmed\n")
        else:
            print("✗ Not Authorized\n")
            raise typer.Exit(1)

        print("Step 2: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")
        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        try:
            share_name = "CX00000: (CRUX) WRANGLES FOR {} - CX_DATA_LAKE".format(org_id)

            print("Step 3: Revoking view from share...")
            print("View name: {}".format(view_name))
            print("Share name: {}\n".format(share_name))

            revoke_sql = (
                "REVOKE SELECT ON VIEW CX_DATA_LAKE.CRUX_WRANGLE."
                '"{}" FROM SHARE "{}"'.format(view_name, share_name)
            )

            print("Executing SQL:")
            print(revoke_sql + "\n")

            cur.execute(revoke_sql)
            print("✓ Successfully revoked access\n")

            print("=" * 60)
            print("Wrangle share remove completed")
            print("=" * 60 + "\n")

        except Exception as e:
            print("✗ Failed: {}\n".format(e))
            raise typer.Exit(1)
        finally:
            cur.close()
            conn.close()

    def wrangles_share_create(self, org_id, account_locator=None, region=None):
        """
        Create wrangle share for an organization.

        Args:
            org_id: Organization ID
            account_locator: Customer's Snowflake account locator (optional)
            region: Region (US/EU/VPS) - defaults to US
        """
        # Strip whitespace from inputs
        org_id = org_id.strip()
        if account_locator:
            account_locator = account_locator.strip()
        if region:
            region = region.strip()

        print("\n" + "=" * 60)
        print("Creating wrangle share for organization")
        print("Organization: {}".format(org_id))
        print("Account Locator: {}".format(account_locator or "Not provided"))
        print("Region: {}".format(region or "US"))
        print("=" * 60 + "\n")

        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        print("Step 1: Connecting to Snowflake ({})...".format(region or "US"))
        region_connection_map = {
            "US": get_snowflake_connection_instance,
            "EU": get_snowflake_eu_connection_instance,
            "VPS": get_snowflake_vps_connection_instance,
        }

        connection_func = region_connection_map.get(region or "US")
        if not connection_func:
            print("✗ Unsupported region: {}\n".format(region))
            raise typer.Exit(1)

        conn = connection_func()
        cur = conn.cursor(DictCursor)
        print("✓ Connected to Snowflake\n")

        try:
            share_name = "CX00000: (CRUX) WRANGLES FOR {} - CX_DATA_LAKE".format(org_id)
            print("Step 2: Checking if wrangle share already exists...")
            print("Share name: {}\n".format(share_name))

            # Check if share already exists
            show_shares_sql = "SHOW SHARES LIKE '%{}%'".format(org_id)
            print("Executing SQL:")
            print(show_shares_sql + "\n")

            cur.execute(show_shares_sql)
            existing_shares = [row["name"] for row in cur if "WRANGLE" in row["name"]]

            if existing_shares:
                print(
                    "✓ Found {} existing wrangle share(s):".format(len(existing_shares))
                )
                for share in existing_shares:
                    print("  - {}".format(share))
                print()

            if share_name in existing_shares:
                print("✓ Wrangle share already exists: {}\n".format(share_name))
                print("=" * 60)
                print("Wrangle share create completed (already exists)")
                print("=" * 60 + "\n")
                return

            print("✓ Share does not exist, proceeding with creation\n")

            print("Step 3: Creating wrangle share...")
            # Create share
            create_sql = 'CREATE SHARE "{}"'.format(share_name)
            print("Executing SQL:")
            print(create_sql + "\n")

            cur.execute(create_sql)
            print("✓ Created share: {}\n".format(share_name))

            # Grant database usage
            print("Step 4: Granting database usage...")
            grant_db_sql = 'GRANT USAGE ON DATABASE CX_DATA_LAKE TO SHARE "{}"'.format(
                share_name
            )
            print("Executing SQL:")
            print(grant_db_sql + "\n")

            cur.execute(grant_db_sql)
            print("✓ Granted database usage\n")

            # Grant schema usage
            print("Step 5: Granting schema usage...")
            grant_schema_sql = (
                "GRANT USAGE ON SCHEMA CX_DATA_LAKE."
                'CRUX_WRANGLE TO SHARE "{}"'.format(share_name)
            )
            print("Executing SQL:")
            print(grant_schema_sql + "\n")

            cur.execute(grant_schema_sql)
            print("✓ Granted schema usage\n")

            # Add account if provided
            if account_locator:
                print("Step 6: Adding account to share...")
                alter_sql = 'ALTER SHARE "{}" ADD ACCOUNTS = "{}"'.format(
                    share_name, account_locator
                )
                print("Executing SQL:")
                print(alter_sql + "\n")

                cur.execute(alter_sql)
                print("✓ Added account: {}\n".format(account_locator))
            else:
                print(
                    "Step 6: Skipping account assignment (no account locator provided)"
                )
                print("  ⚠ Share created but not assigned to any account")
                print("  To add an account later, run:")
                print(
                    '  ALTER SHARE "{}" ADD ACCOUNTS = "<account_locator>"\n'.format(
                        share_name
                    )
                )

            print("=" * 60)
            print("✓ Wrangle share created successfully!")
            print("Share name: {}".format(share_name))
            if account_locator:
                print("Account: {}".format(account_locator))
            print("=" * 60 + "\n")

        except Exception as e:
            print("✗ Failed to create share: {}\n".format(e))
            raise typer.Exit(1)
        finally:
            cur.close()
            conn.close()

    def entitlements_sync_from_snowflake(self, org_id, dry_run=False):
        """
        Sync entitlements using Snowflake ORG_CDU_ENTITLEMENTS as source of truth.
        Creates missing SERVICE_RESTRICTIONS for all account-CDU combinations.
        """
        # Strip whitespace from inputs
        org_id = org_id.strip()

        print("\n" + "=" * 60)
        print(
            "{}Syncing entitlements from Snowflake for {}".format(
                "[DRY RUN] " if dry_run else "", org_id
            )
        )
        print("=" * 60 + "\n")

        conn = get_snowflake_connection_instance()
        cur = conn.cursor(DictCursor)

        # Get all accounts for this org
        print("Step 1: Fetching organization accounts...")
        accounts = []
        sql_query = (
            "SELECT DISTINCT ACCOUNT FROM "
            "CX_DATA_LAKE.PRIVATE.ORG_ACCOUNTS WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for data in cur:
                accounts.append(data["ACCOUNT"])

        except Exception as err:
            print("✗ Error fetching accounts: {}\n".format(err))
            cur.close()
            conn.close()
            return

        if not accounts:
            print("✗ No accounts found for {}\n".format(org_id))
            cur.close()
            conn.close()
            return

        print("✓ Found {} account(s): {}\n".format(len(accounts), ", ".join(accounts)))

        # Get all entitled CDUs from Snowflake
        print("Step 2: Fetching entitled CDUs from Snowflake...")
        cdus = []
        sql_query = (
            "SELECT DISTINCT CDU_ID FROM CX_DATA_LAKE.PRIVATE.ORG_CDU_ENTITLEMENTS "
            "WHERE ORG_ID = '{}' AND ENT_TYPE = 'CATALOG'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for data in cur:
                cdus.append(data["CDU_ID"])
        except Exception as err:
            print("✗ Error fetching CDUs: {}\n".format(err))
            cur.close()
            conn.close()
            return

        if not cdus:
            print("✗ No entitled CDUs found for {}\n".format(org_id))
            cur.close()
            conn.close()
            return

        print("✓ Found {} entitled CDU(s): {}\n".format(len(cdus), ", ".join(cdus)))

        # Get existing service restrictions
        print("Step 3: Fetching existing service restrictions...")
        existing_restrictions = set()
        sql_query = (
            "SELECT DISTINCT SVC_ACCT, CDU_ID "
            "FROM CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
            "WHERE ORG = '{}'".format(org_id)
        )
        print("Executing SQL:")
        print(sql_query + "\n")

        try:
            cur.execute(sql_query)
            for row in cur:
                existing_restrictions.add((row["SVC_ACCT"], row["CDU_ID"]))
        except Exception as err:
            print("✗ Error fetching restrictions: {}\n".format(err))
            cur.close()
            conn.close()
            return

        print(
            "✓ Found {} existing SERVICE_RESTRICTION(s)\n".format(
                len(existing_restrictions)
            )
        )

        # Track what needs to be added
        total_added = 0
        total_skipped = 0
        errors = []

        print("Step 4: Processing entitled CDUs...")
        # Process each CDU
        for cdu_id in cdus:
            print("\nProcessing CDU: {}".format(cdu_id))

            # Check each account for this CDU
            for acct in accounts:
                # Check if restriction already exists
                if (acct, cdu_id) in existing_restrictions:
                    print("  ✓ {} - already exists".format(acct))
                    total_skipped += 1
                    continue

                # Need to add this restriction
                if dry_run:
                    print("  + {} - WOULD ADD".format(acct))
                    total_added += 1
                    continue

                # Get service dates (use defaults if not found)
                svc_start_date = "2020-01-01T00:00:00.000+0000"
                svc_end_date = "2050-12-31T00:00:00.000+0000"

                # Actually insert the restriction
                sql_query = (
                    "INSERT INTO CX_DATA_LAKE.PRIVATE.CRUX_SERVICE_RESTRICTIONS "
                    '("ORG", "SVC_ACCT", "CDU_ID", "SVC_ST_DT", "SVC_ET_DT", '
                    '"R1_SDT", "R1_EDT", "R2_SDT", "R2_EDT", "SVC_BLOCK_DT", '
                    '"STATUS", "LICENSE", "SF", "FTP", "S3", "API") VALUES '
                    "('{}', '{}', '{}', "
                    "'{}', '{}', "
                    "'1970-12-31T00:00:00.000+0000', '2030-12-31T00:00:00.000+0000', "
                    "'2050-12-31T00:00:00.000+0000', '2050-12-31T00:00:00.000+0000', "
                    "'{}', 'ACTIVE', 'FULL', 'TRUE', 'FALSE', 'FALSE', 'FALSE')".format(
                        org_id, acct, cdu_id, svc_start_date, svc_end_date, svc_end_date
                    )
                )

                try:
                    cur.execute(sql_query)
                    print("  ✓ {} - ADDED".format(acct))
                    total_added += 1
                except Exception as err:
                    error_msg = "  ✗ {} - ERROR: {}".format(acct, err)
                    print(error_msg)
                    errors.append(error_msg)

        cur.close()
        conn.close()

        # Print summary
        print("\n" + "=" * 60)
        print("SYNC SUMMARY for {}".format(org_id))
        print("=" * 60)
        print("CDUs processed:      {}".format(len(cdus)))
        print("Accounts processed:  {}".format(len(accounts)))
        print("Already existed:     {}".format(total_skipped))
        print(
            "{}:           {}".format("Would add" if dry_run else "Added", total_added)
        )
        if errors:
            print("Errors:              {}".format(len(errors)))
        print("=" * 60)

        if dry_run:
            print("\n[DRY RUN] No changes were made.\n")
        elif total_added > 0:
            print("\n✓ Successfully synced {} entitlement(s)\n".format(total_added))
