import os
import re
from requests import RequestException
import hashlib
from tabulate import tabulate
from functools import wraps


from collections import defaultdict
from cruxctl.command_groups.auth.connectors.databricks_conn import open_aws_dbctx
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 cruxctl.common.utils.sql_utils import format_sql
from cruxctl.common.utils.typer_utils import typer, logger
from cruxctl.common.api.crux.v2 import CruxApiV2Ops
from cruxctl.common.api.crux.v4 import CruxApiV4Ops
from cruxctl.common import constants
from typing import List, Dict


def handle_http_errors(func):
    @wraps(func)
    def wrap(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
        except RequestException as ex:
            logger.error(f"HTTP error occurred: {ex}")
            logger.error(f"Unexpected error occurred: {ex}")
            raise typer.Exit(1)
        return result

    return wrap


class DatabricksOrgs(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 _validate_admin_access(self, org_id):
        response = handle_http_errors(self.crux_v2_api.whoami)()
        if not response:
            if not org_id:
                logger.error("User Not Authorized")
            else:
                logger.error(f"{org_id} Not Authorized")
            raise typer.Exit(1)

        user_roles = response.get("roles", [])
        if constants.CIAM_ROLE_CRUX_ADMIN not in user_roles:
            logger.error("You are not authorized")
            raise typer.Exit(1)

    def _validate_org_format(self, org_id):
        org_id = org_id.upper()
        if not re.match(r"^CXO\d{5}$", org_id):
            logger.error(
                f"Invalid org_id '{org_id}'. Must start with CXO followed by 5 digits."
            )
            raise typer.Exit(1)
        return org_id

    def get_catalog(self, selected_cdus, token):
        """
        Fetches the frame and dataframe catalog for a list of CDUs from the Crux API.

        Steps:
            1. Break the selected CDUs into chunks of 100 to avoid large API requests.
            2. Query the `frame-catalog` endpoint for each chunk of CDUs.
            3. Parse the response to extract view names, table names, and client schema names.
            4. Query the `dataframe-catalog` endpoint for the same chunks.
            5. Collect all frames and dataframes in dictionaries keyed by CDU.
            6. Merge results into a final catalog mapping CDU to its frames and dataframes.

        Args:
            selected_cdus (List[str]): List of CDU IDs to fetch catalog for.
            token (str): API token for authentication with the Crux API.
            client_schema (str): Client schema name to prefix views (used in catalog naming).

        Returns:
            dict: A dictionary mapping each CDU to its corresponding frames and dataframes:
                {
                    "CduId1": {
                        "frames": [ { "view_name":, "table_name": }, ... ],
                        "data_frames": [ {...}, ... ]
                    },
                    ...
                }
        """

        # Catalogs to store frames and dataframes grouped by CDU
        frames_catalog = defaultdict(list)
        dataframes_catalog = defaultdict(list)

        # ---- STEP 1 & 2: Fetch frame catalog in chunks ----
        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)
            try:
                safe_get_frame_catalogs = handle_http_errors(
                    self.crux_v2_api.get_frame_catalogs
                )
                response = safe_get_frame_catalogs(
                    filter_query=f"{in_filter}", offset=0, limit=10000
                )

            except Exception as e:
                logger.warning(f"Error fetching frame catalog: {e}")
                response = []

        # ---- STEP 3: Process frame catalog response ----
        for data in response:
            cduid = data.get("cduId").replace("CX", "Cx")
            frameid = data.get("frameId", "").upper()
            legacyid = data.get("datasetLegacyId", "")
            view_name = "__".join([cduid, frameid, legacyid])
            table_name = "__".join([frameid, legacyid])
            table_name = ".".join([cduid.lower(), table_name])
            datadict = {
                "view_name": view_name.lower(),
                "table_name": table_name.lower(),
            }
            if cduid:
                frames_catalog[cduid].append(datadict)

        # ---- STEP 4: Fetch dataframe catalog in chunks ----
        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)

            try:
                safe_get_frame_catalogs = handle_http_errors(
                    self.crux_v2_api.get_frame_catalogs
                )
                response = safe_get_frame_catalogs(
                    filter_query=f"{in_filter}", offset=0, limit=10000
                )
            except Exception as e:
                logger.warning(f"Error fetching frame catalog: {e}")
                response = []

            # ---- STEP 5: Process dataframe catalog response ----
            for data in response:
                out_frame = data.get("outFrame", {})
                cduid = out_frame.get("cduId")
                if cduid:
                    dataframes_catalog[cduid].append(data)
        # ---- STEP 6: Merge frames and dataframes into final catalog ----
        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, cur) -> Dict[str, dict]:
        schemas = {}
        if not table:
            return schemas
        if "--" in table:
            part1 = table.split("--")[0]
            part2 = table.split("--")[1].replace("__", "--")
        elif "__" in table:
            part1 = table.split("__")[0]
            part2 = table.split("__")[1].replace("__", "--")
        table = "'{}'".format(part1 + "--" + part2)
        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 crux_sf_schema.private.crux_schema_gcs_test) "
            + f"WHERE row_number = 1 and TABLENAME LIKE '{part1.upper()}%' "
            + "order by TABLENAME, ORD"
        )

        try:
            cur.execute(sql_query)
            for data in cur:
                table_name = data[0]
                normalized_table_name = table_name.lower().replace("--", "__")
                ord = data[1]
                if normalized_table_name not in schemas:
                    schemas[normalized_table_name] = {
                        "table_name": table_name,
                        "fields": {},
                        "primary_key": None,
                    }
                schemas[normalized_table_name]["fields"][ord] = {
                    "field_name": data[2],
                    "data_type": data[3],
                    "ord": ord,
                }
                if data[4] == "Yes":
                    schemas[normalized_table_name]["primary_key"] = data[2]
                # db_tablename = table_name.lower().replace("--", "__")
        except Exception as e:
            logger.error(f"Crux schema query failed: {e}.")
        return schemas

    def list_all(self):
        """
        Retrieve a list of Databricks recipients whose names start with 'CXO'
        and enrich them with Crux organization metadata.
        """
        logger.info("Fetching recipients from Databricks...")

        recipient_list = []
        conn = None
        cur = None

        try:
            # --- Step 1: Open DB connection and fetch recipients ---
            conn = open_aws_dbctx()
            cur = conn.cursor()
            cur.execute("SHOW RECIPIENTS LIKE 'CXO*'")
            results = cur.fetchall()

            if not results:
                logger.warning("No recipients found matching pattern 'CXO*'.")
                return []

            for row in results:
                recipient_list.append(
                    {
                        "recipient": row[0],
                        "comment": row[4] if len(row) > 4 else None,
                    }
                )

        except Exception as e:
            logger.error(f"Error fetching recipients from Databricks: {e}")
            return []
        finally:
            if cur:
                cur.close()
            if conn:
                conn.close()

        # --- Step 2: Enrich with org metadata from Crux API ---
        logger.info(
            f"Found {len(recipient_list)} recipients. Fetching organization details..."
        )

        recipients_details = []
        for recipient in recipient_list:
            org_id = recipient["recipient"].split("_")[0].upper()

            if not re.match(r"^CXO\d{5}$", org_id):
                logger.warning(f"Skipping invalid org_id format: {org_id}")
                continue

            try:
                response = self.crux_v2_api.get_orgs(
                    filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
                )
                if not response:
                    logger.warning(f"No organization found for org_id={org_id}")
                    continue

                org = response[0]
                recipients_details.append(
                    {
                        "org_id": org.get("externalId"),
                        "org_code": org.get("code"),
                        "org_type": org.get("type"),
                        "org_name": org.get("name"),
                        "recipient": recipient["recipient"],
                        "comment": recipient["comment"],
                    }
                )

            except Exception as e:
                logger.warning(f"Failed to fetch org details for {org_id}: {e}")
                continue

        # --- Step 3: Print final result ---
        if not recipients_details:
            logger.warning("No valid organizations found.")
            return []

        table = tabulate(
            [v.values() for v in recipients_details],
            headers=[
                "Org ID",
                "Org Code",
                "Org Type",
                "Org Name",
                "Recipient",
                "Comment",
            ],
            tablefmt="pretty",
        )

        logger.info(f"\nRecipients and Organization Details:\n{table}")
        return recipients_details

    def list(self, org_id, org_name=None):
        """
        Retrieve recipients for a specific organization ID from Databricks.
        Args:
        org_id (str): The organization ID to look up. Automatically converted
        to uppercase before querying.

        Returns:
        list[dict]: A list of dictionaries, where each dictionary contains:
            - "org": The organization ID.
            - "recipient_name": The associated recipient name.
        """

        logger.info(f"Calling list org ({org_id}) from Databricks Orgs class.")
        org_list = []
        org_id = self._validate_org_format(org_id)
        conn = open_aws_dbctx()
        cur = conn.cursor()
        try:
            select_sql = (
                "select org_id, recipient_name from"
                + f" provisioning.bigsnow.org_accounts where org_id ='{org_id}'"
            )
            cur.execute(select_sql)
            for data in cur:
                org_list.append({"org": data[0], "recipient_name": data[1]})
        except Exception as e:
            status = "Failed: select org from provisioning.bigsnow.orgs_accounts"
            logger.error(status, e)
            raise
        finally:
            cur.close()
            conn.close()
        recipients_details = []
        try:
            response = self.crux_v2_api.get_orgs(
                filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
            )
            if not response:
                logger.warning(f"No organization found for org_id={org_id}")
            else:
                org = response[0]
                recipient_name = None
                recipient_name = (
                    org_list[0].get("recipient_name")
                    if isinstance(org_list[0], dict)
                    else org_list[0]
                )

                recipients_details.append(
                    {
                        "org_id": org.get("externalId"),
                        "org_code": org.get("code"),
                        "org_type": org.get("type"),
                        "org_name": org.get("name"),
                        "recipients": recipient_name,
                    }
                )

        except Exception as e:
            logger.error(f"Failed to fetch Orgs: {e}")

        table = tabulate(
            [v.values() for v in recipients_details],  # remove .values() on list
            headers=["org_id", "org_code", "org_type", "org_name", "recipient"],
            tablefmt="pretty",
        )
        logger.info(f"Recipients in {org_id}:\n{table}")
        return org_list

    def orgs_add(self, org_id, recipient_id):
        """
        Create a Databricks recipient and share for a given organization.

        This operation:
        1. Upserts an organization record into `provisioning.bigsnow.org_accounts`
        2. Creates a Databricks recipient (if missing) using the provided `recipient_id`
        3. Creates a Databricks share (if missing)
        4. Grants SELECT permissions on the share to the recipient

        Args:
            org_id (str): Organization ID
            (must start with 'CXO' and include 5 digits, e.g., CXO12345)
            recipient_id (str): Customer metastore identifier in the format
            <cloud>:<region>:<uuid>

        Returns:
            str: Success message if all steps complete, or descriptive failure message otherwise.
        """
        print(f"Starting org setup for org_id={org_id}, recipient_id={recipient_id}")

        # Validate org_id format
        org_id = self._validate_org_format(org_id)

        # --- Validate API Access ---
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        confirmation = typer.prompt("Type ADD to confirm")
        if confirmation != "ADD":
            logger.error(f"Aborted addition for org_id={org_id}")
            raise typer.Exit(code=1)

        # Derive unique recipient/share names
        hash_name = hashlib.md5(recipient_id.encode("utf-8")).hexdigest()
        rname = f"{org_id.lower()}_{hash_name}_recipient"
        sname = f"{org_id.lower()}_{hash_name}_share"

        conn = None
        cur = None
        try:
            conn = open_aws_dbctx()
            cur = conn.cursor()

            # --- Step 1: Upsert org record ---
            merge_sql = f"""
                MERGE INTO provisioning.bigsnow.org_accounts AS t
                USING (SELECT '{org_id}' AS org_id, '{rname}' AS recipient_name) AS s
                ON t.org_id = s.org_id AND t.recipient_name = s.recipient_name
                WHEN NOT MATCHED THEN
                INSERT (org_id, recipient_name)
                VALUES (s.org_id, s.recipient_name)
            """
            logger.info(f"Upserting org record for {org_id}")
            cur.execute(merge_sql)

            # --- Step 2: Create recipient ---
            create_recipient_sql = (
                f"CREATE RECIPIENT IF NOT EXISTS {rname} USING ID '{recipient_id}'"
            )
            logger.info(f"Creating recipient {rname}")
            cur.execute(create_recipient_sql)

            # Optional debug: show recipient metadata
            cur.execute(f"DESCRIBE RECIPIENT {rname}")
            for row in cur:
                logger.info(f"Recipient metadata: {row}")

            # --- Step 3: Create share ---
            create_share_sql = f"CREATE SHARE IF NOT EXISTS {sname}"
            logger.info(f"Creating share {sname}")
            cur.execute(create_share_sql)

            # --- Step 4: Grant share permissions ---
            grant_sql = f"GRANT SELECT ON SHARE {sname} TO RECIPIENT {rname}"
            logger.info(f"Granting SELECT on {sname} to {rname}")
            cur.execute(grant_sql)

            conn.commit()
            logger.info("All operations completed successfully.")
            return f"Successfully created and granted access for org_id={org_id}"

        except Exception as e:
            error_msg = f"Operation failed for org_id={org_id}: {e}"
            logger.error(error_msg)
            if conn:
                conn.rollback()
            return error_msg

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

    def orgs_delete(self, org_id: str, recipient_name: str) -> str:
        """
        Delete a Databricks recipient and its associated share for a given organization.

        This operation:
        1. Revokes SELECT privileges on the associated share
        2. Drops the recipient
        3. Drops the share
        4. Deletes the org record from `provisioning.bigsnow.org_accounts`

        Args:
            org_id (str): Organization ID associated with the recipient.
            recipient_name (str): Databricks recipient name to delete.

        Returns:
            str: Success message if all operations complete, otherwise error message.
        """
        org_id = self._validate_org_format(org_id)
        share_name = recipient_name.replace("_recipient", "_share")

        logger.info(
            f"Starting deletion for org_id={org_id}, recipient={recipient_name}, share={share_name}"
        )

        # --- Validate API Access ---
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        confirmation = typer.prompt("Type DELETE to confirm")
        if confirmation != "DELETE":
            logger.error(f"Aborted deletion for org_id={org_id}")
            raise typer.Exit(code=1)

        conn = None
        cur = None
        try:
            conn = open_aws_dbctx()
            cur = conn.cursor()

            # --- Step 1: Revoke share permissions ---
            revoke_sql = (
                f"REVOKE SELECT ON SHARE {share_name} FROM RECIPIENT {recipient_name}"
            )
            logger.info(
                f"Revoking SELECT privileges on share {share_name} for {recipient_name}"
            )
            cur.execute(revoke_sql)

            # --- Step 2: Drop recipient ---
            drop_recipient_sql = f"DROP RECIPIENT IF EXISTS {recipient_name}"
            logger.info(f"Dropping recipient {recipient_name}")
            cur.execute(drop_recipient_sql)

            # --- Step 3: Drop share ---
            drop_share_sql = f"DROP SHARE IF EXISTS {share_name}"
            logger.info(f"Dropping share {share_name}")
            cur.execute(drop_share_sql)

            # --- Step 4: Delete org record ---
            delete_org_sql = f"""
                DELETE FROM provisioning.bigsnow.org_accounts
                WHERE org_id = '{org_id}'
                AND recipient_name = '{recipient_name}'
            """
            logger.info(
                f"Deleting org account record for org_id={org_id}, recipient={recipient_name}"
            )
            cur.execute(delete_org_sql)

            conn.commit()
            logger.info(
                f"All Databricks resources successfully deleted for org_id={org_id}"
            )
            return f"Successfully deleted recipient and share for org_id={org_id}"

        except Exception as e:
            error_msg = f"Deletion failed for org_id={org_id}: {e}"
            logger.error(error_msg)
            if conn:
                conn.rollback()
            return error_msg

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

    def entitlements_list(self, org_id):
        """
        Retrieve entitlement details for a given organization.
        Args:
        org_id (str): The organization ID, either already in the CXO##### format
            or a numeric string that will be prefixed and padded.
        """
        normalized_org_id = self._validate_org_format(org_id)
        logger.info(f"Fetching entitlements for org_id={normalized_org_id}")
        org_entitlements = {}
        conn = open_aws_dbctx()
        cur = conn.cursor()
        try:
            query = (
                "SELECT org_id, cdu_id, crux_delivery_unit, supplier_acronym "
                "FROM provisioning.bigsnow.org_cdu_entitlements "
                f"WHERE org_id = '{normalized_org_id}'"
            )
            logger.info(f"Executing SQL:\n{format_sql(query)}")
            cur.execute(query)
            for org_id_val, cdu_id, crux_delivery_unit, supplier_acronym in cur:
                org_entitlements[cdu_id] = {
                    "org_id": org_id_val,
                    "cdu_id": cdu_id,
                    "name": crux_delivery_unit,
                    "supplier": supplier_acronym,
                }

            if org_entitlements:
                table = tabulate(
                    [v.values() for v in org_entitlements.values()],
                    headers=["Org ID", "CDU ID", "Crux Delivery Unit", "Supplier"],
                    tablefmt="pretty",
                )
                logger.info(f"Entitlements for {normalized_org_id}:\n{table}")
            else:
                logger.info(f"No entitlements found for org_id={normalized_org_id}")
        finally:
            cur.close()
            conn.close()
        return org_entitlements

    def entitlements_add(self, org_id: str, cdu_id: str) -> List[str]:
        """
        Adds a single CDU entitlement for the given organization.

        Steps:
            1. Fetch org details from Crux API by external org_id.
            2. Fetch all active CDU subscriptions for that org.
            3. Check if the new cdu_id passed is in the subscription list.
            4. Fetch CDU details (name, supplier) for only the new cdu_id passed.
            5. Check if the new CDU_id already exists in the table
            provisioning.bigsnow.org_cdu_entitlements.
            6. Insert only the new CDU_id details if not present.

        Args:
            org_id (str): External organization ID to fetch and update entitlements for.
            cdu_id (str): The CDU ID to add.

        Returns:
            List[str]: List containing the CDU ID if inserted, empty list if skipped.
        """

        # Step 1: Fetch org details
        org_id = self._validate_org_format(org_id)
        cdu_id = cdu_id.upper().replace("CX", "Cx")
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        confirmation = typer.prompt("Type ADD to confirm")
        if confirmation != "ADD":
            typer.echo("Aborted.")
            logger.error(f"{org_id} Add operation cancelled")
            raise typer.Exit(code=1)

        try:
            response = self.crux_v2_api.get_orgs(
                filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
            )
            if not response:
                logger.info(f"No organization found for org_id={org_id}")

        except Exception as e:
            logger.warning(f"Failed to fetch Orgs: {e}")

        org_internal_id = response[0]["id"]

        # Step 2: Fetch all active CDU subscriptions
        active_cdu_ids = []
        try:
            resp = self.crux_v2_api.get_cdu_subscriptions(
                order="cduId",
                filter_query=f"orgId.eq.{org_internal_id},status.eq.Active",
            )
            for sub in resp:
                active_cdu_ids.append(sub["cduId"])
        except Exception as e:
            logger.warning(f"Failed to fetch CDU subscriptions: {e}")

        # Step 3: Check if new cdu_id is in active subscription list
        if cdu_id not in active_cdu_ids:
            logger.info(
                f"CDU {cdu_id} is not an active subscription for org {org_id}. Skipping insert."
            )

        # Step 4: Fetch CDU details for this cdu_id
        try:
            cdu_data = self.crux_v4_api.get_data_products(
                with_deleted=False,
                filter_query=f"cduId.eq.{cdu_id}",
                offset=0,
                limit=1000,
            )
            items = cdu_data.get("items", [])
            if not items:
                logger.warning(f"No CDU details found for cdu_id={cdu_id}")

            first_item = items[0]
            cdu_name = first_item.get("name", "").replace("'", "")
            supplier = first_item.get("ownerOrgCode", "")
        except Exception as e:
            logger.warning(f"Failed to fetch CDU details for cdu_id={cdu_id}: {e}")

        # Step 5: Check if cdu_id already exists in entitlements table
        conn = open_aws_dbctx()
        cur = conn.cursor()
        try:
            inserted_cdu_id = cdu_id.lstrip("Cx").lstrip(
                "CX"
            )  # strip 'Cx' or 'CX' prefix
            check_sql = """
                SELECT COUNT(*)
                FROM provisioning.bigsnow.org_cdu_entitlements
                WHERE org_id = ? AND cdu_id = ?
            """
            cur.execute(check_sql, (org_id, inserted_cdu_id))
            exists = cur.fetchone()[0]
            if exists > 0:
                logger.info(
                    f"CDU {cdu_id} already exists for org {org_id}. Skipping insert."
                )
                return []

            insert_sql = """
                INSERT INTO provisioning.bigsnow.org_cdu_entitlements
                (org_id, cdu_id, crux_delivery_unit, supplier_acronym)
                VALUES (?, ?, ?, ?)
            """
            logger.info(f"Executing SQL:\n{format_sql(insert_sql)}")
            cur.execute(insert_sql, (org_id, inserted_cdu_id, cdu_name, supplier))
            conn.commit()

            logger.info(f"CDU {cdu_id} inserted successfully for org {org_id}")
        except Exception as e:
            logger.error(
                f"Failed to insert CDU {inserted_cdu_id} for org {org_id}: {e}"
            )
            conn.rollback()
        finally:
            cur.close()
            conn.close()

        revised_entitlements_list = self.entitlements_list(org_id)
        return revised_entitlements_list

    def entitlements_delete(self, org_id: str, cdu_id: str):
        """
        Deletes a specific CDU entitlement for a given organization after authorization.

        This method:
            1. Validates and normalizes the organization ID.
            2. Ensures the user has admin access for the org.
            3. Prompts the user to confirm deletion.
            4. Deletes the specified CDU entitlement from the Databricks table.
            5. Returns the updated entitlements list for the organization.

        Args:
            org_id (str): External organization ID (e.g., CXO12345).
            cdu_id (str): CDU ID to delete.

        Returns:
            dict: Updated entitlements for the organization after deletion.
        """

        # Step 1: Normalize org_id and clean CDU ID
        org_id = self._validate_org_format(org_id)
        cdu_id = re.sub(r"cx", "", cdu_id, flags=re.IGNORECASE)

        # Step 2: Verify admin access via Crux API
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        # Step 3: Prompt user for deletion confirmation
        confirmation = typer.prompt("Type DELETE to confirm")
        if confirmation != "DELETE":
            typer.echo("Aborted.")
            logger.error(f"{org_id} Delete operation cancelled")
            raise typer.Exit(code=1)

        logger.info("Deleting entitlement...")

        # Step 4: Execute deletion in Databricks
        conn = open_aws_dbctx()
        cur = conn.cursor()
        try:
            clean_cdu_id = cdu_id.strip("Cx").strip("CX")  # Remove any CX/Cx prefix
            delete_sql = (
                "DELETE FROM provisioning.bigsnow.org_cdu_entitlements "
                f"WHERE org_id = '{org_id}' AND cdu_id = '{clean_cdu_id}'"
            )
            logger.info(f"Executing SQL:\n{format_sql(delete_sql)}")
            cur.execute(delete_sql)
            conn.commit()
        finally:
            cur.close()
            conn.close()

        # Step 5: Retrieve and return revised entitlements
        revised_entitlements_list = self.entitlements_list(org_id)
        logger.info(
            f"Updated entitlements for org {org_id}: {revised_entitlements_list}"
        )

        return revised_entitlements_list

    def provision_list(self, org_id):
        """
        Lists missing curated views for a given organization by comparing expected
        views (from CDU subscriptions) against existing views in Databricks.

        Steps:
            1. Validate and normalize the external org_id.
            2. Fetch org internal ID via Crux API.
            3. Retrieve all Active CDU subscriptions for the org.
            4. Build the expected views catalog from the CDU subscriptions.
            5. Query Databricks to fetch existing curated views.
            6. Identify missing views and print a tabulated report.

        Args:
            org_id (str): External organization ID (e.g., CXO12345).

        Returns:
            None
        """
        # Step 1: Normalize org_id and get API token
        org_id = self._validate_org_format(org_id)
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        # Step 2: Fetch org details
        try:
            response = self.crux_v2_api.get_orgs(
                filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
            )
            if not response:
                logger.info(f"No organization found for org_id={org_id}")
                raise
        except Exception as e:
            logger.error(f"Failed to fetch Orgs: {e}")
            raise

        org_internal_id = response[0]["id"]

        # Step 3: Fetch Active CDU subscriptions
        selected_cdus = []
        try:
            resp = self.crux_v2_api.get_cdu_subscriptions(
                order="cduId",
                filter_query=f"orgId.eq.{org_internal_id},status.eq.Active",
                offset=0,
                limit=1000,
            )
            if not resp:
                logger.error(f"{org_id} doesn't have any Active CDU entitlements.")
                raise
            selected_cdus = [item["cduId"] for item in resp]
        except Exception as e:
            logger.error(f"Failed to fetch CDU subscriptions: {e}")
            raise
        # Step 4: Build expected view catalog from CDU frames
        catalog = self.get_catalog(selected_cdus, token)
        expected_views = set()
        expected_view_dict = {}

        for _, items in catalog.items():
            for frame in items["frames"]:
                frame_view_name = frame["view_name"].lower().replace("--", "__")
                expected_views.add(frame_view_name)
                expected_view_dict[frame_view_name] = frame["view_name"]

        # Step 5: Fetch existing curated views from Databricks
        existing_views = set()
        conn = open_aws_dbctx()
        cur = conn.cursor()
        cur.execute("USE CATALOG cx_data_lake")

        for cdu in selected_cdus:
            cdu_id = cdu.lower()
            sql_query = f"SHOW VIEWS IN cx_data_lake.crux_curated LIKE '{cdu_id}*'"
            logger.info(f"Executing SQL:\n{format_sql(sql_query)}")
            cur.execute(sql_query)
            for data in cur:
                view_name = data["viewName"]
                # Normalize view name to match expected format
                parts = view_name.split("__", 1)
                normalized_name = "__".join(parts) if len(parts) > 1 else parts[0]
                existing_views.add(normalized_name)

        # Step 6: Compare expected vs. existing views
        missing_normalized_views = set(expected_view_dict.keys()) - existing_views
        missing_curated_views = [
            expected_view_dict[v] for v in missing_normalized_views
        ]

        # Report missing curated views
        if missing_curated_views:
            table = tabulate(
                [[v] for v in missing_curated_views],
                headers=["View Name"],
                tablefmt="pretty",
            )
            logger.info(f"MISSING CURATED VIEWS for Org {org_id}:\n{table}")
        else:
            logger.info(f"No MISSING CURATED VIEWS for Org {org_id}")

        cur.close()
        conn.close()
        return

    def provision_add(self, org_id, view_name):
        """
        Adds a curated view for the specified org and view_name in Databricks.

        Steps:
            1. Parse cdu_id and table name from the view_name.
            2. Validate user authorization (must have CruxAdmin role).
            3. Verify that org_id exists and has entitlement to the CDU.
            4. Retrieve table schema from Databricks and build the view DDL.
            5. Execute CREATE VIEW in Databricks if validation passes.
        """
        org_id = self._validate_org_format(org_id)

        # --- Step 1: Parse CDU and table name from view_name ---
        parts = view_name.split("__", 1)
        cdu_id = parts[0].lower()
        table_name = parts[1]

        # --- Step 2: Validate user authorization ---
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        # Prompt for confirmation before proceeding
        confirmation = typer.prompt("Type ADD to confirm")
        if confirmation != "ADD":
            typer.echo("Aborted.")
            logger.error(f"{org_id} You are not Authorized for ADD operation")
            raise typer.Exit(code=1)
        logger.info("Adding entitlements...")

        # --- Step 3: Verify org and entitlements ---
        try:

            response = self.crux_v2_api.get_orgs(
                filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
            )
            if not response:
                logger.warning(f"No organization found for org_id={org_id}")
        except Exception as e:
            logger.error(f"Failed to fetch Orgs: {e}")
            raise
        orgId = response[0]["id"]
        active_cdu_ids = []
        # Check entitlement for the given CDU
        try:
            resp = self.crux_v2_api.get_cdu_subscriptions(
                order="cduId",
                filter_query=f"orgId.eq.{orgId},status.eq.Active",
                offset=0,
                limit=1000,
            )
            subscription_data = resp
            for sub in subscription_data:
                active_cdu_ids.append(sub["cduId"])
        except Exception as e:
            logger.error(f"Failed to fetch CDU subscriptions: {e}")
            raise

        if not resp:
            logger.info(f"{cdu_id} is NOT entitled for {org_id}")
            return

        # --- Step 4: Get curated schema and build view DDL ---
        conn = open_aws_dbctx()
        cur = conn.cursor()

        crux_schemas = self.get_crux_schemas(table_name, cur)
        if not crux_schemas:
            logger.error("Table has NOT been CURATED")
            raise

        cs = crux_schemas.get(table_name)
        ide = {"field_name": "crux_ingestion_dt", "data_type": "DATETIME"}
        sde = {"field_name": "crux_supplier_implied_dt", "data_type": "DATETIME"}
        fields = []
        # Safely iterate over schema fields
        for pos in sorted(cs.get("fields", {}).keys()):
            ce = cs["fields"][pos]
            # Override field definitions for ingestion and supplier dates
            if ce["field_name"] == sde["field_name"]:
                ce = sde
            elif ce["field_name"] == ide["field_name"]:
                ce = ide

            field = f'"{ce["field_name"]}"'

            # Validate field name length
            if len(ce["field_name"]) > 255:
                logger.warning(f"Curated field name too long: {ce['field_name']}")
                break

            if ce["data_type"] == "DATE":
                field = f"cast({ce['field_name']} as {ce['data_type']}) AS {ce['field_name']}"
            elif ce["data_type"] == "DATETIME":
                field = f"cast({ce['field_name']} as TIMESTAMP) AS {ce['field_name']}"
            elif ce["data_type"] == "INTEGER":
                field = f"cast({ce['field_name']} as BIGINT) AS {ce['field_name']}"
            else:
                field = f"cast({ce['field_name']} as {ce['data_type']}) AS {ce['field_name']}"
            fields.append(field)
        table_name = table_name.lower().replace("--", "__")
        vname = "__".join([cdu_id, table_name])
        create_stmt = f"CREATE VIEW IF NOT EXISTS cx_data_lake.crux_curated.{vname} AS"
        select_stmt = (
            f'SELECT {", ".join(fields)} FROM crux_dispatch_prod.{cdu_id}.{table_name}'
        )

        view_ddl = " ".join([create_stmt, select_stmt]) + ";"

        # --- Step 5: Execute CREATE VIEW ---
        try:
            logger.info(f"Executing SQL:\n{format_sql(view_ddl)}")
            cur.execute(view_ddl)
        except Exception as err:
            logger.error(f"Error creating view: {err}")
        else:
            logger.info(f"Created view: CX_DATA_LAKE.CRUX_CURATED.{view_name}")

        cur.close()
        conn.close()
        return

    def provision_delete(self, org_id, view_name):
        """
        Deletes a curated view for the specified org and view_name from Databricks.

        Steps:
            1. Validate user authentication and retrieve roles.
            2. Ensure user has CruxAdmin role and confirm DELETE action.
            3. Connect to Databricks and drop the specified view.
        """
        # --- Step 1: Get API token and validate user ---
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        # --- Step 2: Confirm delete action ---

        confirmation = typer.prompt("Type DELETE to confirm")
        if confirmation != "DELETE":
            typer.echo("Aborted.")
            logger.error(f"{org_id} You are not Authorized for Delete operation")
            raise typer.Exit(code=1)

        print("Deleting entitlements...")

        # --- Step 3: Connect to Databricks and drop view ---
        conn = open_aws_dbctx()
        cur = conn.cursor()

        # Normalize view name (Databricks uses "__" instead of "--")
        normalized_view_name = view_name.lower().replace("--", "__")
        drop_sql = f"DROP VIEW cx_data_lake.crux_curated.{normalized_view_name}"

        try:
            cur.execute(drop_sql)
            logger.info(f"Successfully dropped view: {normalized_view_name}")
        except Exception as err:
            logger.error(f"Error dropping view {normalized_view_name}: {err}")
        finally:
            cur.close()
            conn.close()
        return

    def share_list(self, org_id):
        """
        Lists missing and deprecated view grants for a given organization.

        Steps:
            1. Retrieve API token and org ID from Crux API.
            2. Fetch active CDU subscriptions for the organization.
            3. Build the expected grants list from the Crux catalog.
            4. Query Databricks to retrieve existing shares and grants.
            5. Compare expected vs. existing grants to identify missing and deprecated grants.
            6. Print results for further action.
        """
        # --- Step 1: Fetch API token and org ID ---
        org_id = self._validate_org_format(org_id)
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)
            # Validate org_id format

        try:

            response = self.crux_v2_api.get_orgs(
                filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
            )
            if not response:
                logger.info(f"No organization found for org_id={org_id}")
        except Exception as e:
            logger.error(f"Failed to fetch Orgs: {e}")
            raise
        orgId = response[0]["id"]
        selected_cdus = []

        # --- Step 2: Fetch active CDU subscriptions ---

        try:
            resp = self.crux_v2_api.get_cdu_subscriptions(
                order="cduId",
                filter_query=f"orgId.eq.{orgId},status.eq.Active",
                offset=0,
                limit=1000,
            )
        except Exception as e:
            logger.error(f"Failed to fetch CDU subscriptions: {e}")

        if resp:
            for item in resp:
                selected_cdus.append(item["cduId"])
        else:
            logger.error(f"{org_id} doesn't have any Active CDU entitlements.")
            raise

        # --- Step 3: Build expected grants from catalog ---
        conn = open_aws_dbctx()
        cur = conn.cursor()
        results = self.list(org_id)
        if not results:
            print(f"No accounts found for org {org_id}")

        catalog = self.get_catalog(selected_cdus, token)
        expected_tables = set()
        table_view_map = {}  # table_name → view_name

        for _, items in catalog.items():
            for frame in items["frames"]:
                table_name = frame.get("table_name")
                view_name = frame.get("view_name")
                if table_name:
                    expected_tables.add(table_name)
                    table_view_map[table_name] = view_name
        if not expected_tables:
            logger.info("No expected grants fetched")

        # --- Step 4: Fetch existing shares in Databricks ---
        share_list = []
        existing_tables = set()
        select_sql = f"SHOW SHARES LIKE '{org_id.upper()}*'"
        try:
            logger.info(f"Executing SQL:\n{format_sql(select_sql)}")
            cur.execute(select_sql)
            for data in cur:
                share_list.append(data["share"])
        except Exception as e:
            logger.error(f"Error running query: {e}")
        # Add share names to expected grants (if required)
        for share in share_list:
            expected_share_sql = f"SHOW ALL IN SHARE {share}"
            try:
                logger.info(f"Executing SQL:\n{format_sql(expected_share_sql)}")
                cur.execute(expected_share_sql)
                for data in cur:
                    print(f"data:\n{data}")
                    if data["type"] == "TABLE":
                        view_name = data["name"]  # fallback to raw name
                        existing_tables.add(view_name.strip('"'))
            except Exception as e:
                logger.error(f"Error running query: {e}")
        if not existing_tables:
            logger.warning("No existing grants fetched")
        logger.info(
            f"expected_grants: {expected_tables}\nexisting_grants: {existing_tables}"
        )
        # --- Step 6: Compare expected vs. existing grants ---
        missing_grants = sorted(expected_tables - existing_tables)
        deprecated_grants = sorted(existing_tables - expected_tables)

        if missing_grants:
            missing_data = [
                [table, table_view_map.get(table, "N/A")] for table in missing_grants
            ]
            table_str = tabulate(
                missing_data,
                headers=["Missing Table", "Corresponding View"],
                tablefmt="pretty",
            )
            logger.info(f"MISSING TABLE GRANTS for Org {org_id}:\n{table_str}")
        else:
            logger.info(f"No MISSING TABLE GRANTS for Org {org_id}")

        if deprecated_grants:
            table_str = tabulate(
                [[t] for t in deprecated_grants],
                headers=["Deprecated Tables"],
                tablefmt="pretty",
            )
            logger.info(f"DEPRECATED TABLE GRANTS for Org {org_id}:\n{table_str}")
        else:
            logger.info(f"No DEPRECATED TABLE GRANTS for Org {org_id}")

        # --- Step 7: Close DB connection ---
        cur.close()
        conn.close()

        return

    def share_add(self, org_id, view_name):
        """
        Adds a curated view to all Databricks shares for a given organization,
        ensuring the organization is entitled for the CDU associated with the view.

        Steps:
            1. Normalize and validate the external org_id.
            2. Retrieve the organization internal ID via Crux API.
            3. Fetch active CDU subscriptions for the org and check entitlement for the given CDU.
            4. Query Databricks to fetch all shares for the organization.
            5. Add the specified view to each share.
            6. Print status messages for each operation.

        Args:
            org_id (str): External organization ID (e.g., CXO12345).
            view_name (str): The fully qualified view name in Databricks to add to shares.

        Returns:
            None
        """
        # Step 1: Validate org_id and fetch API token
        org_id = self._validate_org_format(org_id)
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        self._validate_admin_access(org_id=org_id)

        confirmation = typer.prompt("Type ADD to confirm")
        if confirmation != "ADD":
            typer.echo("Aborted.")
            logger.error(f"{org_id} Add operation cancelled")
            raise typer.Exit(code=1)

        # Step 2: Extract CDU ID from view_name
        parts = view_name.split(".", 1)
        cdu_id = parts[0].replace("cx", "Cx")  # Normalize CDU ID casing

        # Step 3: Fetch organization internal ID
        try:
            response = self.crux_v2_api.get_orgs(
                filter_query=f"externalId.eq.{org_id}", offset=0, limit=1000
            )
            if not response:
                logger.error(f"No organization found for org_id={org_id}")
                raise typer.Exit(code=1)
        except Exception as e:
            logger.error(f"Failed to fetch Orgs: {e}")
            raise typer.Exit(code=1)

        orgId = response[0]["id"]
        selected_cdus = []

        # Step 4: Fetch active CDU subscriptions and check entitlement
        try:
            resp = self.crux_v2_api.get_cdu_subscriptions(
                order="cduId",
                filter_query=f"orgId.eq.{orgId},cduId.eq.{cdu_id},status.eq.Active",
                offset=0,
                limit=1000,
            )
            if resp:
                selected_cdus = [item["cduId"] for item in resp]
            else:
                logger.warning(
                    f"{org_id} doesn't have this CDU {cdu_id} set to Active."
                )
                raise typer.Exit(code=1)
        except Exception as e:
            logger.error(f"Failed to fetch CDU subscriptions: {e}")
            raise typer.Exit(code=1)

        if cdu_id not in selected_cdus:
            logger.error(f"{org_id} is NOT entitled for this CDU {cdu_id}")
            raise typer.Exit(code=1)

        # Step 5: Connect to Databricks and fetch all shares for the org
        conn = open_aws_dbctx()
        cur = conn.cursor()
        share_list = []
        select_sql = f"SHOW SHARES LIKE '{org_id.upper()}*' "
        try:
            logger.info(f"Executing SQL:\n{format_sql(select_sql)}")
            cur.execute(select_sql)
            for data in cur:
                share_list.append(data["share"])
        except Exception as e:
            logger.error("Error running query:", e)
            cur.close()
            conn.close()
            raise

        # Step 6: Add the view to each share
        for share in share_list:
            view = ".".join(["crux_dispatch_prod", view_name])
            grant_sql = f"ALTER SHARE {share} ADD TABLE {view}"
            try:
                logger.info(f"Executing SQL:\n{format_sql(grant_sql)}")
                cur.execute(grant_sql)
            except Exception as e:
                logger.error("Error running query:", e)
                cur.close()
                conn.close()
                raise
            else:
                logger.info(f"View {view_name} has been added to share {share}")

        # Step 7: Close DB connections
        cur.close()
        conn.close()
        return

    def share_delete(self, org_id, view_name):
        """
        Removes a curated view from all Databricks shares for a given organization,
        after validating admin access and user confirmation.

        Steps:
            1. Normalize and validate the external org_id.
            2. Verify user authorization via admin access check.
            3. Prompt the user to confirm deletion.
            4. Fetch the CDU ID from the view name and validate via Crux API.
            5. Query Databricks to fetch all shares for the organization.
            6. Remove the specified view from each share.
            7. Close DB connections and print status messages.

        Args:
            org_id (str): External organization ID (e.g., CXO12345).
            view_name (str): The fully qualified view name in Databricks to remove.

        Returns:
            None
        """
        # Step 1: Validate org_id and fetch API token
        org_id = self._validate_org_format(org_id)
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)

        # Step 2: Verify admin access
        self._validate_admin_access(org_id=org_id)

        # Step 3: Confirm deletion from user
        confirmation = typer.prompt("Type DELETE to confirm")
        if confirmation != "DELETE":
            typer.echo("Aborted.")
            logger.error(f"{org_id} Delete operation cancelled")
            raise typer.Exit(code=1)

        logger.info("Deleting entitlements...")

        # Step 4: Open DB connection
        conn = open_aws_dbctx()
        cur = conn.cursor()

        # Step 5: Validate CDU ID from view name
        parts = view_name.split(".", 1)
        cdu_id = parts[0].replace("cx", "Cx")
        try:
            cdu_data = self.crux_v4_api.get_data_products(
                with_deleted=False,
                filter_query=f"cduId.eq.{cdu_id}",
                offset=0,
                limit=1000,
            )
            if not cdu_data or not cdu_data.get("items"):
                logger.error(f"No CDU details found for cdu_id={cdu_id}")
                raise
        except Exception as e:
            logger.error(f"Failed to fetch CDU details for cdu_id={cdu_id}: {e}")
            cur.close()
            conn.close()
            raise

        # Step 6: Fetch all shares for the org
        share_list = []
        select_sql = f"SHOW SHARES LIKE '{org_id.upper()}*'"
        try:
            logger.info(f"Executing SQL:\n{format_sql(select_sql)}")
            cur.execute(select_sql)
            for data in cur:
                share_list.append(data["share"])
        except Exception as e:
            logger.error(f"Error fetching shares: {e}")
            cur.close()
            conn.close()
            raise

        # Step 7: Remove view from each share
        for share in share_list:
            view = ".".join(["crux_dispatch_prod", view_name])
            revoke_sql = f"ALTER SHARE {share} REMOVE TABLE {view}"
            try:
                logger.info(f"Executing SQL:\n{format_sql(revoke_sql)}")
                cur.execute(revoke_sql)
            except Exception as e:
                logger.error(f"Error removing view from share {share}: {e}")
                raise
            else:
                logger.info(f"View {view_name} has been removed from share {share}")

        # Step 8: Close DB connections
        cur.close()
        conn.close()
        return

    def cdu_provision_list(self, cdu_id):
        """
        Lists all missing curated views for the specified CDU(s) in Databricks.

        Steps:
            1. Authenticate with Crux API.
            2. Fetch catalog frames for the specified CDU(s).
            3. Build a set of expected curated views from the catalog.
            4. Query Databricks to retrieve existing curated views.
            5. Compare expected vs. existing views and report missing ones.

        Args:
            cdu_id (str): Single or multiple CDU IDs separated by spaces.

        Returns:
            None
        """
        # Step 1: Authenticate and get token
        profile = get_current_profile()
        token = set_api_token(console, profile)
        if not token:
            raise typer.Exit(1)
        selected_cdus = cdu_id.split()
        catalog = self.get_catalog(selected_cdus, token)

        expected_views = set()
        existing_views = set()
        expected_view_dict = {}

        # Step 2: Build expected views from catalog
        for _, items in catalog.items():
            for frame in items["frames"]:
                frame_view_name = frame["view_name"].lower().replace("--", "__")
                expected_views.add(frame_view_name)
                expected_view_dict[frame_view_name] = frame["view_name"]

        # Step 3: Open DB connection and switch to curated catalog
        conn = open_aws_dbctx()
        cur = conn.cursor()
        cur.execute("USE CATALOG cx_data_lake")

        # Step 4: Fetch existing views for each CDU
        for cdu in selected_cdus:
            sql_query = f"SHOW VIEWS IN cx_data_lake.crux_curated LIKE '{cdu}*'"
            cur.execute(sql_query)
            for data in cur:
                view_name = data["viewName"]
                parts = view_name.split("__", 1)
                name = "__".join(parts) if len(parts) > 1 else parts[0]
                existing_views.add(name)

        # Step 5: Identify missing curated views
        missing_normalized_views = set(expected_view_dict.keys()) - existing_views
        missing_curated_views = [
            expected_view_dict[v] for v in missing_normalized_views
        ]

        # Step 6: Report missing views
        if missing_curated_views:
            logger.info(f"MISSING CURATED VIEWS for CDU {cdu_id}:")
            logger.info(missing_curated_views)
        else:
            logger.info(f"No MISSING CURATED VIEWS for CDU {cdu_id}")

        cur.close()
        conn.close()
        return

    def cdu_provision_add(self, view_name):
        """
        Creates a curated view in Databricks for a specified CDU table.

        Steps:
            1. Authenticate with Crux API and validate admin access.
            2. Prompt user to confirm addition.
            3. Fetch the table schema from the Crux catalog.
            4. Generate a CREATE VIEW statement with proper field casting.
            5. Execute the CREATE VIEW in Databricks.

        Args:
            view_name (str): Fully qualified view name in format <CDU_ID>__<TABLE_NAME>.

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

        # Step 1: Admin validation and user confirmation
        self._validate_admin_access(org_id=None)
        confirmation = typer.prompt("Type ADD to confirm")
        if confirmation != "ADD":
            typer.echo("Aborted.")
            print("You are not authorized for ADD operation")
            raise typer.Exit(code=1)

        # Step 2: Parse CDU ID and table name
        parts = view_name.split("__", 1)
        cdu_id = parts[0].upper()
        table_name = parts[1]

        conn = open_aws_dbctx()
        cur = conn.cursor()

        # Step 3: Fetch schema for table
        crux_schemas = self.get_crux_schemas(table_name, cur)
        if not crux_schemas:
            print(f"Table {table_name} has NOT been CURATED")
            return

        cs = crux_schemas.get(table_name)
        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.get("fields", {}).keys()):
            ce = cs["fields"][pos]

            # Override fields for ingestion and supplier dates
            if ce["field_name"] == sde["field_name"]:
                ce = sde
            elif ce["field_name"] == ide["field_name"]:
                ce = ide

            # Build SELECT fields with appropriate type casting
            field_name = ce["field_name"]
            if len(field_name) > 255:
                print(f"Curated field name too long: {field_name}")
                return

            dtype = ce["data_type"].upper()
            if dtype == "DATE":
                field = f"CAST({field_name} AS {dtype}) AS {field_name}"
            elif dtype == "DATETIME":
                field = f"CAST({field_name} AS TIMESTAMP) AS {field_name}"
            elif dtype == "INTEGER":
                field = f"CAST({field_name} AS BIGINT) AS {field_name}"
            else:
                field = f"CAST({field_name} AS {dtype}) AS {field_name}"
            fields.append(field)

        # Step 4: Create view DDL
        vname = "--".join([cdu_id, table_name.lower()])
        create_stmt = f"CREATE VIEW cx_data_lake.crux_curated.`{vname}` AS"
        select_stmt = f'SELECT {", ".join(fields)} FROM crux_dispatch_prod.{cdu_id}.`{table_name}`'
        view_ddl = f"{create_stmt} {select_stmt};"

        # Step 5: Execute CREATE VIEW
        try:
            cur.execute(view_ddl)
        except Exception as err:
            print(f"Error creating view {view_name}: {err}")
        else:
            print(f"Created view: CX_DATA_LAKE.CRUX_CURATED.{view_name}")

        cur.close()
        conn.close()
        return

    def cdu_provision_delete(self, view_name):
        """
        Deletes a curated view from Databricks for a specified CDU.

        Steps:
            1. Authenticate with Crux API and validate admin access.
            2. Prompt user to confirm deletion.
            3. Construct DROP VIEW statement.
            4. Execute the DROP VIEW in Databricks.
            5. Close DB connection.

        Args:
            view_name (str): Fully qualified view name to drop in Databricks.

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

        # Step 1: Admin validation and user confirmation
        self._validate_admin_access(org_id=None)
        confirmation = typer.prompt("Type DELETE to confirm")
        if confirmation != "DELETE":
            typer.echo("Aborted.")
            print("You are not authorized for DELETE operation")
            raise typer.Exit(code=1)

        conn = open_aws_dbctx()
        cur = conn.cursor()

        # Step 2: Build DROP VIEW statement
        normalized_view_name = view_name.lower()
        drop_sql = f"DROP VIEW cx_data_lake.crux_curated.{normalized_view_name}"

        # Step 3: Execute DROP VIEW
        try:
            cur.execute(drop_sql)
            print(f"Successfully dropped view: {normalized_view_name}")
        except Exception as err:
            print(f"Error dropping view {normalized_view_name}: {err}")
        finally:
            cur.close()
            conn.close()
        return
