#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2026, Cisco Systems
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)

"""Ansible module to manage wireless design operations in Cisco Catalyst Center."""
from __future__ import absolute_import, division, print_function

__metaclass__ = type
__author__ = "Rugvedi Kapse, Madhan Sankaranarayanan"


DOCUMENTATION = r"""
---
module: wireless_design_workflow_manager
short_description: Manage wireless design elements in
  Cisco Catalyst Center.
description:
  - Manage wireless design operations, including creating,
    updating, and deleting - SSID(s) - Interface(s)
    - Power Profile(s) - Access Point Profile(s) - Radio
    Frequency (RF) Profile(s) - Anchor Group(s)
  - Provides APIs for wireless design automation in
    Cisco Catalyst Center.
  - Note - This module supports only the creation, updating,
    and deletion of Wireless Design elements. - To associate
    them with a Wireless Profile, utilize the 'network_wireless_profile_workflow_manager'
    module.
version_added: "6.17.0"
extends_documentation_fragment:
  - cisco.dnac.workflow_manager_params
author:
  - Rugvedi Kapse (@rukapse)
  - Madhan Sankaranarayanan (@madhansansel)
options:
  config_verify:
    description: Set to true to verify the Cisco Catalyst
      Center configuration after applying the playbook
      configuration.
    type: bool
    default: false
  state:
    description: The desired state of Cisco Catalyst
      Center after module execution.
    type: str
    choices: [merged, deleted]
    default: merged
  config:
    description:
      - A list containing configurations for managing
        SSIDs, Interfaces, Power Profiles, RF Profiles,
        AP Profiles, and Anchor Groups in Cisco Catalyst
        Center.
      - Guidelines for Update Operations - SSIDs - No
        need to provide the entire target SSID configuration;
        updates are handled automatically. - Interfaces
        - Must provide the complete configuration for
        the specific interfaces being updated. - Power
        Profiles - Required to specify the exact configuration
        for the power profiles being updated. - AP Profiles
        - No need to provide the full configuration;
        updates are managed as needed. - RF Profiles
        - Similar to AP profiles, full configurations
        are not required for updates. - Anchor Groups
        - Must provide the complete configuration for
        the anchor groups being updated.
    type: list
    elements: dict
    required: true
    suboptions:
      ssids:
        description:
          - Configure SSIDs for Enterprise and Guest
            Wireless Networks.
          - When updating SSIDs, if "passphrase" or
            "mpsk_passphrase" is included, the Cisco
            Catalyst Center returns these values in
            an encrypted format. This prevents comparison
            with existing values. As a result, even
            if no other parameters change, the operation
            is treated as an update to ensure the provided
            "passphrase" or "mpsk_passphrase" is applied.
        type: list
        elements: dict
        suboptions:
          ssid_name:
            description:
              - Specifies the Wireless Network name
                or SSID name.
              - The maximum length of the SSID name
                is 32 characters.
              - Required for creating, updating, or
                deleting SSIDs.
            type: str
            required: true
          ssid_type:
            description:
              - Specifies the type of WLAN.
              - Required in merged state for creating
                or updating SSIDs.
            type: str
            required: true
            choices: ["Enterprise", "Guest"]
          wlan_profile_name:
            description:
              - Optional parameter specifying the WLAN
                Profile Name.
              - If not provided, an autogenerated profile
                name is assigned using `ssid_name`.
              - This profile name is also used as the
                Policy Profile Name.
              - For example, If the "ssid_name" is "entssid",
                the autogenerated WLAN Profile Name
                will be "entssid_profile".
            type: str
          radio_policy:
            description: Configure radio policy settings.
            type: dict
            suboptions:
              radio_bands:
                description:
                  - Defines the frequency bands used
                    for wireless communication.
                  - This parameter is optional and defaults
                    to Triple band operation (2.4GHz,
                    5GHz, and 6GHz).
                  - 6GHz is applicable only for IOS-XE
                    releases 17.7 or later; for other
                    devices/platforms, this will be
                    ignored.
                  - For example, [2.4, 5], [2.4, 6],
                    [5, 6], [2.4], [5], [6], [2.4, 5,
                    6].
                type: list
                elements: float
                default: [2.4, 5, 6]
              2_dot_4_ghz_band_policy:
                description:
                  - Defines the policy for managing
                    the 2.4 GHz frequency band.
                  - Determines how client devices connect
                    and operate on the 2.4 GHz band.
                  - Allowed only when the 2.4 GHz Radio
                    Band is enabled in "radio_bands".
                type: str
                choices: ["802.11-bg", "802.11-g"]
                default: "802.11-bg"
              band_select:
                description:
                  - Enables Band Select to optimize
                    client distribution across frequency
                    bands.
                  - When enabled, dual-band clients
                    are encouraged to connect to the
                    5 GHz or 6 GHz band instead of the
                    more congested 2.4 GHz band.
                  - Can be activated only when the "radio_bands"
                    selected are - [2.4, 5], i.e., 2.4
                    GHz and 5GHz - [2.4, 5, 6], i.e.,
                    Triple band operation (2.4GHz, 5GHz,
                    and 6GHz).
                type: bool
                default: false
              6_ghz_client_steering:
                description:
                  - Enables 6 GHz Client Steering to
                    optimize network performance by
                    directing capable clients to the
                    6 GHz band.
                  - Helps reduce congestion on 2.4 GHz
                    and 5 GHz by prioritizing the use
                    of 6 GHz for supported devices.
                  - Can only be enabled if "band_select"
                    includes the 6 GHz radio band.
                type: bool
                default: false
          fast_lane:
            description:
              - Enables Fast Lane to optimize network
                performance for real-time applications
                like voice and video.
              - Helps improve Quality of Service (QoS)
                by prioritizing latency-sensitive traffic.
              - When "fast_lane" is enabled - For IOS-XE,
                QoS (Egress and Ingress) will be set
                to empty. - For AireOS, QoS (Egress)
                will be set to VoIP (Platinum).
              - By default, "fast_lane" is disabled.
            type: bool
            default: false
          quality_of_service:
            description:
              - Configures Quality of Service (QoS)
                settings to prioritize network traffic
                based on application requirements.
              - QoS selection is ignored when Fast Lane
                is enabled.
              - For AireOS devices, the primary traffic
                type is set to VoIP (Platinum), ensuring
                high priority for voice traffic.
              - For Wireless Controllers, the primary
                traffic type is set to empty, meaning
                no specific prioritization.
            type: dict
            suboptions:
              egress:
                description:
                  - Specifies the Quality of Service
                    (QoS) level for outgoing (egress)
                    traffic.
                  - Determines traffic prioritization
                    for data leaving the network.
                  - Higher priority levels (e.g., PLATINUM)
                    ensure better performance for latency-sensitive
                    applications.
                type: str
                choices: ["PLATINUM", "GOLD", "SILVER", "BRONZE"]
              ingress:
                description:
                  - Specifies the Quality of Service
                    (QoS) level for incoming (ingress)
                    traffic.
                  - Determines how traffic is prioritized
                    when entering the network.
                  - The "-UP" suffix denotes user priority
                    levels for upstream traffic classification.
                type: str
                choices: ["PLATINUM-UP", "GOLD-UP", "SILVER-UP", "BRONZE-UP"]
          ssid_state:
            description:
              - Configure the SSID state settings, which
                define the operational status and visibility
                of the SSID on the wireless network.
            type: dict
            suboptions:
              admin_status:
                description:
                  - Controls whether the SSID is operational.
                  - When set to 'true', the SSID is
                    enabled and available for clients
                    to connect.
                  - When set to 'false', the SSID is
                    disabled, preventing clients from
                    connecting to the network.
                type: bool
                default: false
              broadcast_ssid:
                description:
                  - Controls whether the SSID is visible
                    to client devices when scanning
                    for available networks.
                  - When set to 'true', the SSID will
                    be publicly visible in the list
                    of available networks.
                  - When set to 'false', the SSID will
                    be hidden, requiring clients to
                    manually enter the network name
                    to connect.
                type: bool
                default: false
          l2_security:
            description:
              - Configure the Layer 2 (L2) security
                settings for the SSID.
              - L2 security determines how client devices
                authenticate before gaining network
                access.
            type: dict
            suboptions:
              l2_auth_type:
                description:
                  - Specifies the Layer 2 (L2) authentication
                    type for securing SSID access.
                  - Determines how devices authenticate
                    and connect to the network.
                  - The available L2 authentication
                    types include - WPA2_PERSONAL -
                    Uses a pre-shared key (PSK) for
                    authentication. - Requires at least
                    one WPA encryption, AKM, and passphrase.
                    - AP Beacon Protection cannot be
                    enabled. - WPA2_ENTERPRISE - Uses
                    a RADIUS server for authentication
                    instead of a password. - Requires
                    at least one WPA encryption and
                    AKM. - AP Beacon Protection cannot
                    be enabled. - WPA3_PERSONAL - Uses
                    Simultaneous Authentication of Equals
                    (SAE) instead of PSK. - Provides
                    enhanced security against brute-force
                    attacks. - Requires at least one
                    WPA encryption, AKM, and passphrase.
                    - AP Beacon Protection can be enabled.
                    - WPA3_ENTERPRISE - Uses 192-bit
                    encryption for enhanced security.
                    - Requires at least one WPA encryption
                    and AKM. - AP Beacon Protection
                    can be enabled. - WPA2_WPA3_PERSONAL
                    - Allows devices to connect using
                    either WPA2-Personal or WPA3-Personal.
                    - Requires at least one WPA encryption,
                    AKM, and passphrase. - AP Beacon
                    Protection can be enabled. - WPA2_WPA3_ENTERPRISE
                    - Allows devices to connect using
                    either WPA2-Enterprise or WPA3-Enterprise.
                    - Requires at least one WPA encryption
                    and AKM. - AP Beacon Protection
                    can be enabled. - OPEN-SECURED -
                    Provides encrypted communication
                    but does not require a password
                    for access. - Requires at least
                    one Wi-Fi Protected Access(WPA)
                    encryption and Authentication Key
                    Management (AKM). - AP Beacon Protection
                    cannot be enabled. - OPEN - No authentication
                    or encryption is used. - Anyone
                    can connect without providing credentials.
                    - AP Beacon Protection cannot be
                    enabled.
                  - Notes - If "l2_auth_type" is not
                    "OPEN", then at least one RSN Cipher
                    Suite and the corresponding valid
                    Authentication Key Management (AKM)
                    must be provided. - The WPA3 feature
                    is supported for Wireless Controller
                    versions 8.10 and above, and for
                    Catalyst 9800 Controllers versions
                    16.12 and above. - For 6GHz operation
                    alongside 2.4GHz/5GHz on IOS-XE
                    devices (17.7 to 17.11), WPA3 must
                    be enabled, and WPA2 must be disabled.
                    - For IOS-XE 17.12 and above, all
                    radio bands (2.4GHz, 5GHz, and 6GHz)
                    can be enabled on the same SSID
                    with WPA3 enabled.
                type: str
                choices: ["WPA2_ENTERPRISE", "WPA3_ENTERPRISE",
                "WPA2_WPA3_ENTERPRISE", "WPA2_PERSONAL",
                "WPA3_PERSONAL", "WPA2_WPA3_PERSONAL",
                "OPEN-SECURED", "OPEN"]
              ap_beacon_protection:
                description:
                  - AP Beacon Protection enhances network
                    security by preventing rogue access
                    points from spoofing legitimate
                    beacons.
                  - Can be set to 'true' only when L2
                    Authentication is one of the following
                    - "WPA3_ENTERPRISE" - "WPA2_WPA3_ENTERPRISE"
                    - "WPA3_PERSONAL" - "WPA2_WPA3_PERSONAL"
                  - Protected Management Frame (PMF)
                    must be configured as `Required`
                    along with WPA3 or WPA2 + WPA3 for
                    AP Beacon Protection to be enabled.
                type: bool
                default: false
              open_ssid:
                description:
                  - An Open SSID is a wireless network
                    that does not require authentication
                    or encryption for client connections.
                  - Specifies the name of an existing
                    Open SSID to be used.
                  - Required when using "OPEN-SECURED"
                    L2 Authentication.
                  - All Open SSIDs that are not assigned
                    to any Open Secured SSID must be
                    provided.
                  - For Enterprise SSIDs, L2 security
                    must be set to "OPEN".
                  - For Guest SSIDs, both L2 and L3
                    security must be set to "OPEN".
                type: str
              passphrase_type:
                description:
                  - Defines the format of the passphrase
                    used for authentication.
                  - Applicable only when the L2 Authentication
                    Type is one of the following - "WPA2_PERSONAL"
                    - "WPA3_PERSONAL" - "WPA2_WPA3_PERSONAL"
                  - Supports two formats - "HEX" – Passphrase
                    is specified in hexadecimal format.
                    - "ASCII" – Passphrase is specified
                    in ASCII characters.
                  - The default format is "ASCII".
                type: str
                choices: ["HEX", "ASCII"]
                default: "ASCII"
              passphrase:
                description:
                  - A "passphrase" is a security key
                    used to authenticate devices connecting
                    to the SSID.
                  - Required when the L2 Authentication
                    Type is one of the following - "WPA2_PERSONAL"
                    - "WPA3_PERSONAL" - "WPA2_WPA3_PERSONAL"
                  - Passphrase format requirements -
                    ASCII - Must be between 8 and 63
                    characters. - HEX - Must be exactly
                    64 characters.
                  - During an update operation, if a
                    "passphrase" is provided, the update
                    proceeds even if there are no changes
                    to the existing passphrase.
                  - When updating SSIDs, if "passphrase"
                    is included, the Cisco Catalyst
                    Center returns these values in an
                    encrypted format. This prevents
                    comparison with existing values.
                    As a result, even if no other parameters
                    change, the operation is treated
                    as an update to ensure the provided
                    "passphrase" is applied.
                type: str
              mpsk_settings:
                description:
                  - MPSK (Multiple Pre-Shared Key) allows
                    multiple unique pre-shared keys
                    to be used for the same SSID, enhancing
                    security and flexibility.
                  - This parameter is applicable only
                    when the L2 Authentication Type
                    is "WPA2_PERSONAL".
                  - Not supported on AireOS platforms.
                type: list
                elements: dict
                suboptions:
                  mpsk_priority:
                    description:
                      - Defines the priority level for
                        the Multiple Pre-Shared Key
                        (MPSK).
                      - The priority can be set between
                        0 and 4, where 0 is the highest
                        priority.
                      - If an MPSK key with priority
                        0 is not configured in L3 "central_web_authentication"
                        Flex mode, clients may fail
                        to connect to the WLAN.
                    type: int
                    choices: [0, 1, 2, 3, 4]
                    default: 0
                  mpsk_passphrase_type:
                    description:
                      - Specifies the type of MPSK passphrase.
                      - If set to "ASCII", the passphrase
                        must be between 8 and 63 characters.
                      - If set to "HEX", the passphrase
                        must be exactly 64 characters.
                    type: str
                    default: "ASCII"
                    choices: ["HEX", "ASCII"]
                  mpsk_passphrase:
                    description:
                      - Specifies the MPSK (Multiple
                        Pre-Shared Key) passphrase used
                        for authenticating devices on
                        the SSID.
                      - Required when configuring MPSK.
                      - If "mpsk_passphrase_type" is
                        set to "ASCII", the passphrase
                        must be between 8 and 63 characters.
                      - If "mpsk_passphrase_type" is
                        set to "HEX", the passphrase
                        must be exactly 64 characters.
                      - When updating SSIDs, if "mpsk_passphrase"
                        is included, the Cisco Catalyst
                        Center returns these values
                        in an encrypted format. This
                        prevents comparison with existing
                        values. As a result, even if
                        no other parameters change,
                        the operation is treated as
                        an update to ensure the provided
                        "mpsk_passphrase" is applied.
                    type: str
          fast_transition:
            description:
              - Fast Transition (802.11r) improves roaming
                performance by enabling seamless authentication
                between access points.
              - In AireOS, 802.1X-SHA1 must be configured
                when set to "ADAPTIVE".
              - If disabled, 802.1X-SHA1 or SHA2 must
                be configured in AireOS.
              - If disabled, Fast Transition over the
                Distributed System cannot be enabled.
              - Recommended to disable for WLANs using
                "OPEN" L2 Authentication.
            type: str
            choices: ["ADAPTIVE", "ENABLE", "DISABLE"]
            default: "DISABLE"
          fast_transition_over_the_ds:
            description:
              - Enable Fast Transition over the Distributed
                System when set to true.
              - Fast Transition over the Distributed
                System can be enabled only when Fast
                Transition is set to "ADAPTIVE" or "ENABLE".
            type: bool
            default: false
          wpa_encryption:
            description:
              - Specifies the WPA2/WPA3 encryption protocol
                used for securing wireless communication.
              - GCMP (Galois/Counter Mode Protocol)
                and CCMP (Counter Mode CBC-MAC Protocol)
                are encryption protocols used in WPA2/WPA3
                security.
              - GCMP256 - Uses the Robust Security Network
                (RSN) Cipher Suite with GCMP256 encryption.
              - CCMP256 - Uses the RSN Cipher Suite
                with CCMP256 encryption.
              - CCMP128 - Uses the RSN Cipher Suite
                with CCMP128 encryption.
            type: list
            elements: str
            choices: ["GCMP256", "CCMP256", "GCMP128", "CCMP128"]
          auth_key_management:
            description:
              - Defines the Authentication Key Management
                (AKM) methods available for securing
                wireless connections.
              - AKM determines how clients authenticate
                with the wireless network and establishes
                encryption keys.
              - The available AKM options depend on
                the selected Layer 2 (L2) authentication
                type, encryption protocol, and Fast
                Transition setting.
              - In AireOS, 802.1X-SHA1 must be configured
                when Fast Transition is set to "ADAPTIVE."
              - Required for both Enterprise and Personal
                Layer 2 Authentication Types.
              - On IOS-XE controllers running version
                17.7 and later, 802.1X-SHA1 AKM is not
                supported for WPA3-only SSIDs.
              - Easy-PSK is only supported on IOS-XE.
                Selecting Easy-PSK enables Identity
                PSK (MAC Filtering).
              - SUITE-B-1X - Uses AES-GCMP-128 encryption
                with 802.1X authentication, designed
                for high-security environments.
              - SUITE-B-192X - Uses AES-GCMP-256 encryption
                with 802.1X authentication, providing
                a stronger security level.
              - WPA2_ENTERPRISE - When fast transition
                is ADAPTIVE/DISABLE, AKMs available
                for each RSN Cipher Suite encryption
                protocol are as follows - CCMP128 -
                CCKM/802.1X-SHA1/802.1X-SHA2 - GCMP128
                - SUITE-B-1X - CCMP256 - SUITE-B-192X
                - GCMP256 - SUITE-B-192X - When fast
                transition is ENABLE, AKMs available
                for each RSN Cipher Suite encryption
                protocol are as follows - CCMP128 -
                CCKM/802.1X-SHA1/802.1X-SHA2/FT+802.1x
                - GCMP128 - SUITE-B-1X - CCMP256 - SUITE-B-192X
                - GCMP256 - SUITE-B-192X
              - WPA3_ENTERPRISE - When fast transition
                is ADAPTIVE/DISABLE, AKMs available
                for each RSN Cipher Suite encryption
                protocol are as follows - CCMP128 -
                802.1X-SHA1/802.1X-SHA2 - GCMP128 -
                SUITE-B-1X - GCMP256 - SUITE-B-192X
                - When fast transition is ENABLE, AKMs
                available for each RSN Cipher Suite
                encryption protocol are as follows -
                CCMP128 - 802.1X-SHA1/802.1X-SHA2/FT+802.1x
                - GCMP128 - SUITE-B-1X - GCMP256 - SUITE-B-192X
              - WPA2_WPA3_ENTERPRISE - When fast transition
                is ADAPTIVE/DISABLE, AKMs available
                for each RSN Cipher Suite encryption
                protocol are as follows - CCMP128 -
                CCKM/802.1X-SHA1/802.1X-SHA2 - GCMP128
                - SUITE-B-1X - CCMP256 - SUITE-B-192X
                - GCMP256 - SUITE-B-192X - When fast
                transition is ENABLE, AKMs available
                for each RSN Cipher Suite encryption
                protocol are as follows - CCMP128 -
                CCKM/802.1X-SHA1/802.1X-SHA2/FT+802.1x
                - GCMP128 - SUITE-B-1X - CCMP256 - SUITE-B-192X
                - GCMP256 - SUITE-B-192X
              - WPA2_PERSONAL - When fast transition
                is ADAPTIVE/DISABLE, AKMs available
                for each RSN Cipher Suite encryption
                protocol are as follows - CCMP128 -
                PSK/PSK-SHA2/Easy-PSK - When fast transition
                is ENABLE, AKMs available for each RSN
                Cipher Suite encryption protocol are
                as follows - CCMP128 - PSK/PSK-SHA2/Easy-PSK/FT+PSK
              - WPA3_PERSONAL - When fast transition
                is ENABLE, AKMs available for each RSN
                Cipher Suite encryption protocol are
                as follows - CCMP128 - SAE/SAE-EXT-KEY/FT+SAE/FT+SAE-EXT-KEY
                - GCMP256 - SAE-EXT-KEY/FT+SAE-EXT-KEY
                - When fast transition is DISABLE, AKMs
                available for each RSN Cipher Suite
                encryption protocol are as follows -
                CCMP128 - SAE/SAE-EXT-KEY - GCMP256
                - SAE-EXT-KEY
              - WPA2_WPA3_PERSONAL - When fast transition
                is ENABLE, AKMs available for each RSN
                Cipher Suite encryption protocol are
                as follows - CCMP128 - SAE/SAE-EXT-KEY/PSK/PSK-SHA2/FT+SAE/FT+PSK/FT+SAE-EXT-KEY
                - GCMP256 - SAE-EXT-KEY/FT+SAE-EXT-KEY
                - When fast transition is DISABLE, AKMs
                available for each RSN Cipher Suite
                encryption protocol are as follows -
                CCMP128 - SAE/SAE-EXT-KEY/PSK/PSK-SHA2
                - GCMP256 - SAE-EXT-KEY
              - OPEN-SECURED - AKMs available for each
                RSN Cipher Suite encryption protocol
                and allowed combinations are as follows
                - CCMP128 + OWE - GCMP256 + OWE
              - OPEN - Authentication Management Key
                is not required, any user can associate
                with the network.
            type: list
            elements: str
            choices: ["802.1X-SHA1", "802.1X-SHA2",
            "FT+802.1x", "SUITE-B-1X", "SUITE-B-192X",
            "CCKM", "PSK", "FT+PSK", "Easy-PSK", "PSK-SHA2",
            "SAE", "SAE-EXT-KEY", "FT+SAE", "FT+SAE-EXT-KEY",
            "OWE"]
          cckm_timestamp_tolerance:
            description:
              - Specifies the value of the CCKM Timestamp
                Tolerance.
              - CCKM (Cisco Centralized Key Management)
                is a Cisco-proprietary feature that
                enables fast and secure roaming for
                wireless clients in a network using
                WPA2/WPA3 Enterprise security.
              - It allows clients to roam between access
                points (APs) without requiring a full
                802.1X authentication, reducing latency
                and improving performance for applications
                like VoIP and real-time communication.
              - Parameter "cckm_timestamp_tolerance"
                is not applicable for AireOS platforms.
              - The value of the cckm_timestamp_tolerance
                should be in a range from 1000 to 5000.
            type: int
            default: 0
          l3_security:
            description:
              - Defines Layer 3 security settings for
                an SSID.
              - Required when "ssid_type" is set to
                "Guest".
            type: dict
            suboptions:
              l3_auth_type:
                description:
                  - Required parameter in "l3_security".
                  - If "l3_auth_type" is "OPEN", any
                    user can associate with the network.
                  - If the "l3_auth_type" is "WEB_AUTH",
                    Guest users are redirected to a
                    Web Portal for authentication.
                type: str
                default: "OPEN"
                choices: ["OPEN", "WEB_AUTH"]
              auth_server:
                description:
                  - Specifies the Authentication Server.
                  - Required for Guest SSIDs with "ssid_type"
                    as "Guest" and "l3_auth_type" as
                    "WEB_AUTH".
                  - Available options - "central_web_authentication"
                    Authentication is handled by an
                    external centralized server. - "web_authentication_internal"
                    Authentication is managed by the
                    controllers internal web authentication
                    system. - "web_authentication_external"
                    Authentication is performed through
                    an external web authentication server.
                    - "web_passthrough_internal" Users
                    are granted access without authentication
                    but must acknowledge a captive portal
                    page hosted internally. - "web_passthrough_external"
                    Users are granted access without
                    authentication but must acknowledge
                    a captive portal page hosted externally.
                type: str
                choices: ["central_web_authentication",
                "web_authentication_internal", "web_authentication_external",
                "web_passthrough_internal", "web_passthrough_external"]
                default: "web_authentication_external"
              web_auth_url:
                description:
                  - Defines the external web authentication
                    URL.
                  - Required for SSIDs when "ssid_type"
                    is "Guest", "l3_auth_type" is  "WEB_AUTH"
                    and "auth_server" is "web_authentication_external"
                    or "web_passthrough_external".
                type: str
              enable_sleeping_client:
                description:
                  - Enables timeout settings for clients
                    in sleep mode.
                  - When enabled, inactive clients will
                    be disconnected after the specified
                    timeout period.
                type: bool
                default: false
              sleeping_client_timeout:
                description:
                  - Defines the duration (in minutes)
                    before an inactive (sleeping) client
                    is disconnected from the network.
                  - Applicable only if "enable_sleeping_client"
                    is set to "true".
                type: int
                default: 720
          aaa:
            description:
              - Specifies the Authentication, Authorization,
                and Accounting Configuration.
              - Please associate one or more AAA servers
                with the SSID.
              - If no AAA server is configured, default
                configuration will be pushed under WLAN
                profile for the selected security setting.
              - Catalyst 9800 Controllers versions less
                than 17.9 support only up to 8 Accounting
                Method list configuration.
              - Configuring more than that will result
                in provisioning failure.
              - To ensure the right configuration is
                pushed for this SSID, configure one
                or more AAA/PSN.
              - Can configure a maximum of 6 Servers.
                Can also configure an optional guest
                portal on ISE, by selecting at least
                1 ISE, PSN or VIP.
              - Please note to support ISE Portal, L3
                security should be Web Policy and Authentication
                server should be Central web authentication.
            type: dict
            suboptions:
              auth_servers_ip_address_list:
                description:
                  - List of Authentication/Authorization
                    server IP Addresses.
                  - These servers handle user authentication
                    and policy enforcement.
                  - At least one AAA/PSN server is required
                    with the "auth_server" for "l3_security"
                    as "central_web_authentication".
                type: list
                elements: str
              accounting_servers_ip_address_list:
                description: List of Accounting server
                  IP Addresses.
                type: list
                elements: str
              aaa_override:
                description:
                  - Enables the AAA override feature,
                    allowing RADIUS servers to dynamically
                    assign VLANs, ACLs, and QoS settings
                    based on user authentication.
                  - Requires at least one server in
                    "auth_servers_ip_address_list" to
                    be configured.
                  - When AAA override is disabled, RADIUS
                    NAC (Network Admission Control)
                    will also be disabled.
                type: bool
              mac_filtering:
                description:
                  - Enables MAC filtering when set to
                    true, allowing network access control
                    based on device MAC addresses.
                  - MAC filtering is commonly used for
                    device authentication and access
                    control in enterprise networks.
                  - If "ssid_type" is "Guest" then -
                    mac_filtering is configurable only
                    when "l3_auth_type" is "OPEN". -
                    mac_filtering cannot be enabled
                    if "l3_auth_type" is "WEB_AUTH"
                    and "l2_auth_type" is "WPA2_ENTERPRISE",
                    "WPA3_ENTERPRISE", or "WPA2_WPA3_ENTERPRISE".
                    - mac_filtering is enabled by default
                    if "l3_auth_type" is "WEB_AUTH"
                    and "l2_auth_type" is "WPA2_PERSONAL",
                    "WPA3_PERSONAL", "WPA2_WPA3_PERSONAL",
                    "OPEN-SECURED", or "OPEN".
                type: bool
              deny_rcm_clients:
                description:
                  - Prevents clients using randomized
                    MAC addresses from connecting to
                    the network when set to 'true'.
                  - Randomized MAC addresses (RCM) are
                    used by devices for privacy, but
                    they can interfere with MAC-based
                    security policies.
                  - Supported on Catalyst 9800 Controllers
                    (version 17.5 & later) and Wireless
                    Controllers (version 8.10 MRS &
                    later).
                type: bool
              enable_posture:
                description:
                  - Enables Posturing, also known as
                    ISE Network Access Control (ISE
                    NAC) for Enterprise SSIDs when set
                    to 'true'.
                  - Posturing is a security mechanism
                    that checks endpoint compliance
                    (e.g., antivirus status, OS updates)
                    before granting full network access.
                  - Must be set to 'true' if an ACL
                    needs to be applied to an Enterprise
                    SSID.
                type: bool
              pre_auth_acl_name:
                description:
                  - Name of the Pre-Authentication Access
                    Control List (ACL).
                  - Defines network access rules before
                    authentication is completed.
                  - Required if Posturing is enabled.
                type: str
          mfp_client_protection:
            description:
              - Management Frame Protection (MFP) Client
                Protection helps secure wireless clients
                from forged management frames.
              - Provides protection against deauthentication/disassociation
                attacks.
              - Available options - For "DISABLED" -
                MFP is turned off. - For "OPTIONAL"
                - MFP is enabled but not enforced. -
                For "REQUIRED" - MFP is mandatory for
                all clients.
              - Only "OPTIONAL" is applicable for 6GHz
                SSIDs.
              - Applicable only for AireOS controllers.
            type: str
            choices: ["OPTIONAL", "DISABLED", "REQUIRED"]
            default: "OPTIONAL"
          protected_management_frame:
            description:
              - Protected Management Frames (PMF) enhance
                security by protecting critical management
                frames from spoofing attacks.
              - Prevents deauthentication/disassociation
                attacks by ensuring frames are encrypted.
              - Available options - For "DISABLED" -
                PMF is turned off. - For "OPTIONAL"
                - PMF is enabled but not mandatory.
                - For "REQUIRED" - PMF is enforced for
                all clients.
              - PMF "REQUIRED" is applicable when "l2_auth_type"
                is - "WPA3_PERSONAL", "WPA3_ENTERPRISE",
                or "OPEN-SECURED".
              - PMF "OPTIONAL" or "REQUIRED" is applicable
                when "l2_auth_type" is - "WPA2_WPA3_PERSONAL"
                or "WPA2_WPA3_ENTERPRISE".
            type: str
            choices: ["OPTIONAL", "DISABLED", "REQUIRED"]
            default: "DISABLED"
          11k_neighbor_list:
            description:
              - 802.11k Neighbor List assists client
                devices in faster roaming by providing
                a list of nearby access points.
              - When enabled, the access point sends
                neighbor reports to clients, improving
                roaming efficiency.
              - Set to `true` to activate this feature.
            type: bool
            default: true
          coverage_hole_detection:
            description:
              - Coverage Hole Detection (CHD) helps
                identify areas with weak Wi-Fi signals.
              - When enabled, the controller detects
                and mitigates poor coverage areas by
                adjusting transmit power.
              - Set to 'true' to activate this feature.
            type: bool
            default: false
          wlan_timeouts:
            description:
              - WLAN Timeouts Configuration allows setting
                time limits for user sessions.
              - Specifies which timeout features to
                activate and their respective values.
            type: dict
            suboptions:
              enable_session_timeout:
                description:
                  - Session Timeout Enforcement ensures
                    user sessions are terminated after
                    a specified period.
                  - Set to true to activate this feature.
                type: bool
                default: true
              session_timeout:
                description:
                  - Session Timeout Duration defines
                    the maximum allowed time (in seconds)
                    before an inactive session is automatically
                    terminated.
                  - Accepted range - Generally it should
                    be between 1 to 86400 seconds. -
                    802.1X Security (`auth_key_management`)
                    300 to 86400 seconds. - For Catalyst
                    9800 Controllers it should be between
                    0 to 86400 seconds. - AireOS Controllers
                    0 to 65535 seconds.
                type: int
                default: 1800
              enable_client_exclusion_timeout:
                description:
                  - Client Exclusion Feature prevents
                    clients from reconnecting after
                    multiple failed authentication attempts.
                  - Set to 'true' to activate client
                    exclusion.
                type: bool
                default: true
              client_exclusion_timeout:
                description:
                  - Client Exclusion Timeout Duration
                    defines the period (in seconds)
                    for which a client is blocked from
                    accessing the network after repeated
                    failed attempts.
                  - Value should be between 0 to 2,147,483,647
                    seconds.
                type: int
                default: 180
          bss_transition_support:
            description:
              - Configure Basic Service Set (BSS) Transition
                Support settings.
              - A BSS is a group of wireless devices
                that communicate through a single access
                point (AP).
              - This setting helps manage client transitions
                between access points in an infrastructure
                BSS.
            type: dict
            suboptions:
              bss_max_idle_service:
                description:
                  - Enables the maximum idle service,
                    which determines how long a client
                    can remain inactive before being
                    disconnected from the Basic Service
                    Set (BSS).
                type: bool
                default: true
              bss_idle_client_timeout:
                description:
                  - Defines the inactivity duration
                    (in seconds) after which a client
                    connected to the Basic Service Set
                    (BSS) is considered idle and disconnected.
                  - Value should be between 15 to 100000.
                type: int
                default: 300
              directed_multicast_service:
                description:
                  - Converts multicast traffic into
                    unicast traffic when possible to
                    enhance network efficiency.
                  - Helps reduce packet collisions and
                    improve reliability for multicast
                    transmissions, such as video streaming
                    or voice applications.
                  - The feature becomes operational
                    when set to true.
                type: bool
                default: true
          nas_id:
            description:
              - The Network Access Server (NAS) Identifier
                used for AAA authentication.
              - Can specify up to 4 values from the
                following predefined options - Custom
                Option - A custom NAS ID value can be
                specified. - "AP ETH Mac Address" -
                Uses the Ethernet MAC address of the
                Access Point. - "AP IP Address" - Uses
                the IP address of the Access Point.
                - "AP Location" - Identifies the Access
                Point based on its assigned location.
                - "AP MAC Address" - Uses the MAC address
                of the Access Point. - "AP Name" - Identifies
                the Access Point by its configured name.
                - "AP Policy Tag" - Uses the assigned
                policy tag of the Access Point. - "AP
                Site Tag" - Uses the site tag assigned
                to the Access Point. - "SSID" - Identifies
                the Service Set Identifier of the network.
                - "System IP Address" - Uses the system-wide
                IP address. - "System MAC Address" -
                Uses the system-wide MAC address. -
                "System Name" - Identifies the system
                by its configured hostname.
              - NAS ID with a custom script is supported
                for Catalyst 9800 release 17.7 and above.
              - Only one NAS ID option can be applied
                to AireOS controllers.
              - NAS ID can be overridden at the site
                level.
              - Note - If the NAS ID specified is not
                one of the default options, it will
                be considered as a custom NAS ID.
            type: list
            elements: str
          client_rate_limit:
            description:
              - Defines the maximum data transfer rate
                (in bits per second) allowed for a client.
              - Value must be in multiples of 500.
              - Allowed range 8000 to 100000000000 bps.
              - Applies to all applications running
                on the client device.
              - Platform-specific limits (in bps) -
                For Catalyst 9800-L, 9800-40, 9800-80
                range is 8000 - 67000000000 - For Catalyst
                9800-CL range is 8000 - 10000000000
                - For Embedded Wireless on Access Points
                range is 8000 - 2000000000 - For Embedded
                Wireless on Catalyst 9000 Switches range
                is 8000 - 10000000000
            type: int
            default: 0
          sites_specific_override_settings:
            description:
              - A list of site-specific override settings
                that allow customization of parameters
                for specific site hierarchies.
            type: list
            elements: dict
            suboptions:
              site_name_hierarchy:
                description:
                  - Specifies the site hierarchy where
                    these overrides will apply.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: str
              wlan_profile_name:
                description:
                  - The "wlan_profile_name" parameter
                    is eligible for site-specific overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: str
              l2_security:
                description:
                  - Certain parameters in the  L2 security
                    settings are eligible for site-specific
                    overrides.
                  - For a detailed description, refer
                    to the explanation in the above
                    specified global settings parameters.
                  - Required for creation or update
                    SSID(s) operations.
                type: dict
                suboptions:
                  l2_auth_type:
                    description:
                      - The "l2_auth_type" parameter
                        is eligible for site-specific
                        overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: str
                  open_ssid:
                    description:
                      - The "open_ssid" parameter is
                        eligible for site-specific overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: str
                  passphrase:
                    description:
                      - The "passphrase" parameter is
                        eligible for site-specific overrides.
                      - For update operations, if an
                        "passphrase" is provided, the
                        update will proceed even if
                        there are no changes to the
                        passphrase.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: str
                  mpsk_settings:
                    description:
                      - Certain parameters within "mpsk_settings"
                        are eligible for site-specific
                        overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: list
                    elements: dict
                    suboptions:
                      mpsk_priority:
                        description:
                          - The "mpsk_priority" parameter
                            is eligible for site-specific
                            overrides.
                          - For a detailed description
                            of this parameter, refer
                            to the explanation in the
                            above specified global settings
                            parameters.
                        type: int
                      mpsk_passphrase_type:
                        description:
                          - The "mpsk_passphrase_type"
                            parameter is eligible for
                            site-specific overrides.
                          - For a detailed description
                            of this parameter, refer
                            to the explanation in the
                            above specified global settings
                            parameters.
                        type: str
                      mpsk_passphrase:
                        description:
                          - The "mpsk_passphrase" parameter
                            is eligible for site-specific
                            overrides.
                          - For update operations, if
                            an "mpsk_passphrase" is
                            provided, the update will
                            proceed even if there are
                            no changes to the passphrase.
                          - For a detailed description
                            of this parameter, refer
                            to the explanation in the
                            above specified global settings
                            parameters.
                        type: str
              fast_transition:
                description:
                  - The "fast_transition" parameter
                    is eligible for site-specific overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: str
              fast_transition_over_the_ds:
                description:
                  - The "fast_transition_over_the_ds"
                    parameter is eligible for site-specific
                    overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: bool
              wpa_encryption:
                description:
                  - The "wpa_encryption" parameter is
                    eligible for site-specific overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: list
                elements: str
              aaa:
                description:
                  - Certain arameters within "aaa" are
                    eligible for site-specific overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: dict
                suboptions:
                  auth_servers_ip_address_list:
                    description:
                      - The "auth_servers_ip_address_list"
                        parameter is eligible for site-specific
                        overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: list
                    elements: str
                  accounting_servers_ip_address_list:
                    description:
                      - The "accounting_servers_ip_address_list"
                        parameter is eligible for site-specific
                        overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: list
                    elements: str
                  aaa_override:
                    description:
                      - The "aaa_override" parameter
                        is eligible for site-specific
                        overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: bool
                  mac_filtering:
                    description:
                      - The "mac_filtering" parameter
                        is eligible for site-specific
                        overrides.
                      - For a detailed description of
                        this parameter, refer to the
                        explanation in the above specified
                        global settings parameters.
                    type: bool
              protected_management_frame:
                description:
                  - The "protected_management_frame"
                    parameter is eligible for site-specific
                    overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: str
              nas_id:
                description:
                  - The "nas_id" parameter is eligible
                    for site-specific overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: list
                elements: str
              client_rate_limit:
                description:
                  - The "client_rate_limit" parameter
                    is eligible for site-specific overrides.
                  - For a detailed description of this
                    parameter, refer to the explanation
                    in the above specified global settings
                    parameters.
                type: int
              remove_override_in_hierarchy:
                description:
                  - Controls the removal of site-specific
                    overrides for SSIDs within a hierarchical
                    site structure.
                  - When set to true, this parameter
                    will remove the override from the
                    specified site and also remove any
                    existing overrides for the same
                    SSID at all child sites within the
                    hierarchy.
                  - When set to false, the override
                    will be removed only from the specified
                    site, leaving any existing overrides
                    at child sites unchanged.
                  - By default, when deleting global
                    SSIDs, this parameter is set to
                    true, ensuring that all hierarchical
                    overrides are removed.
                type: bool
      interfaces:
        description: Defines a list of interface configurations
          for creation, update, or deletion.
        type: list
        elements: dict
        suboptions:
          interface_name:
            description:
              - Specifies the name of the Interface.
              - Length should be between  1 to 31 characters.
              - Required for create, update, and delete
                operations.
            type: str
            required: true
          vlan_id:
            description:
              - Specifies the VLAN ID in range is 1
                to 4094.
              - Required for create and update operations.
            type: int
            required: true
      power_profiles:
        description:
          - This API allows the user to create a custom
            Power Profile(s).
          - Create a Power Profile here and attach it
            to AP Profiles.
          - If using as a Regular Power Profile, an
            Access Point receiving less than required
            power will function in a derated state as
            defined by the sequence of rules.
          - If using as a Calendar Power Profile, all
            rules take effect simultaneously in the
            schedule defined in the AP Profile.
          - If only the interface_type is provided,
            then default values for the rules based
            on the interface type are - If the interface_type
            is "RADIO", default values are as follows
            - Default "interface_id" is "6GHZ" - Default
            "parameter_type" is "SPATIALSTREAM" - Default
            "parameter_value" is "FOUR_BY_FOUR" - If
            the interface_type is "ETHERNET", default
            values are as follows - Default "interface_id"
            is "GIGABITETHERNET0" - Default "parameter_type"
            is "SPEED" - Default "parameter_value" is
            "5000MBPS" - If the interface_type is "USB",
            default values are as follows - Default
            "interface_id" is "USB0" - Default "parameter_type"
            is "STATE" - Default "parameter_value" is"DISABLE"
            - Note - Power Profiles associated to a
            Access Point Profile cannot be deleted.
        type: list
        elements: dict
        suboptions:
          power_profile_name:
            description:
              - Name of the Power Profile. Max allowed
                characters is 128.
              - This parameter is required for add/create/update
                power profile(s) operation.
            type: str
            required: true
          power_profile_description:
            description:
              - Description of the Power Profile. Max
                allowed characters is 128.
              - The description must not be an empty
                string or consist solely of whitespace
                characters.
            type: str
          rules:
            description: Sequential Ordered List of
              rules for Power Profile.
            type: list
            elements: dict
            suboptions:
              interface_type:
                description: Specifies the interface
                  type for the rule.
                type: str
                choices: ["ETHERNET", "RADIO", "USB"]
              interface_id:
                description:
                  - Identifies the interface for the
                    rule.
                  - Valid "interface_id" based on the
                    "interface_type" are as follows
                    - ETHERNET - "GIGABITETHERNET0",
                    "GIGABITETHERNET1", "LAN1", "LAN2",
                    "LAN3" - RADIO - "6GHZ", "5GHZ",
                    "SECONDARY_5GHZ", "2_4GHZ" - USB
                    - "USB0"
                type: str
                choices: ["GIGABITETHERNET0", "GIGABITETHERNET1",
                  "LAN1", "LAN2", "LAN3", "6GHZ", "5GHZ",
                  "SECONDARY_5GHZ", "2_4GHZ", "USB0"]
              parameter_type:
                description:
                  - Defines the parameter type for the
                    rule.
                  - Valid "parameter_type" based on
                    the "interface_type" are as follows
                    - ETHERNET - ETHERNET - GIGABITETHERNET0/GIGABITETHERNET1
                    - SPEED - 5000MBPS/2500MBPS/1000MBPS/100MBPS
                    - ETHERNET - LAN1/LAN2/LAN3 - STATE
                    - DISABLE - RADIO - RADIO - 6GHZ/5GHZ/SECONDARY_5GHZ/2_4GHZ
                    - STATE - DISABLE - RADIO - 6GHZ/5GHZ/SECONDARY_5GHZ/2_4GHZ
                    - SPATIALSTREAM - FOUR_BY_FOUR/THREE_BY_THREE/TWO_BY_TWO/ONE_BY_ONE
                    - USB - USB - USB0 - STATE - DISABLE
                choices: ["SPEED", "SPATIALSTREAM", "STATE"]
                type: str
              parameter_value:
                description:
                  - Parameter Value for the rule.
                  - Valid "parameter_value" based on
                    the "interface_type" are as follows
                    - ETHERNET - "5000MBPS", "2500MBPS",
                    "1000MBPS", "100MBPS" - RADIO -
                    "EIGHT_BY_EIGHT", "FOUR_BY_FOUR",
                    "THREE_BY_THREE", "TWO_BY_TWO",
                    "ONE_BY_ONE" - USB - "DISABLE" -
                    The Ethernet Speed Configuration
                    order must be from high to low.
                type: str
                choices: ["5000MBPS", "2500MBPS", "1000MBPS",
                  "100MBPS", "EIGHT_BY_EIGHT", "FOUR_BY_FOUR",
                  "THREE_BY_THREE", "TWO_BY_TWO", "ONE_BY_ONE",
                  "DISABLE"]
      access_point_profiles:
        description:
          - Access Point Profile is used to manage and
            provision access points. AP Profiles can
            be assigned to sites by associating them
            to Wireless Network Profiles.
          - When verifying config uses "config_verify"
            module does not verify password changes,
            which would include "dot1x_password", "management_password",
            "management_enable_password".
        type: list
        elements: dict
        suboptions:
          access_point_profile_name:
            description:
              - Name of the Access Point profile.
              - Max length is 32 characters.
              - This parameter required for create/update/delete
                Access Point profile(s) operation.
            type: str
            required: true
          access_point_profile_description:
            description:
              - Description of the AP profile. Max length
                is 241 characters.
            type: str
          remote_teleworker:
            description:
              - Enable if this AP Profile is for Remote
                Teleworker APs or OEAPs.
              - Indicates if remote worker mode is enabled
                on the AP.
              - Remote teleworker enabled profile cannot
                support security features like aWIPS,
                Forensic Capture Enablement, Rogue Detection,
                and Rogue Containment.
              - Remote teleworker updates are not allowed.
            type: bool
            default: false
          management_settings:
            description:
              - These settings apply during the PnP
                claim process and Day-N authentication
                of APs.
              - Modifying these settings will impact
                service for PnP-onboarded APs and will
                require a factory reset for those APs.
              - Enables SSH and Telnet for adding credentials
                to manage the device.
              - EAP-TLS (Extensible Authentication Protocol
                - Transport Layer Security) TLS uses
                certificate-based authentication.
              - EAP-PEAP (Protected Extensible Authentication
                Protocol) Enter the username and password,
                and a certificate will be generated
                and applied during the PnP claim process.
              - EAP-FAST (Flexible Authentication via
                Secure Tunneling) Enter the username
                and password to be applied during the
                PnP claim process.
            type: dict
            suboptions:
              access_point_authentication:
                description:
                  - Authentication type used in the
                    AP profile.
                  - These settings are applicable during
                    PnP claim and for day-N authentication
                    of AP.
                  - Changing these settings will be
                    service impacting for the PnP onboarded
                    APs and will need a factory-reset
                    for those APs.
                  - The "access_point_authentication"
                    must be "NO-AUTH" when "remote_teleworker"
                    is true.
                type: str
                default: "NO-AUTH"
                choices: ["NO-AUTH", "EAP-TLS", "EAP-PEAP", "EAP-FAST"]
              dot1x_username:
                description:
                  - Username for 802.1X authentication.
                    "dot1x_username" must have a minimum
                    of 1 character and a maximum of
                    32 characters.
                type: str
              dot1x_password:
                description:
                  - Password for 802.1X authentication.
                    AP "dot1x_password" length should
                    not exceed 120 characters.
                  - Length must be between 8 and 120
                    characters.
                  - In an update operation, the update
                    will happen even if the password
                    is not changed.
                  - For update operations, if an "dot1x_password"
                    is provided, the update will proceed
                    even if there are no changes to
                    the password.
                type: str
              ssh_enabled:
                description:
                  - Indicates if SSH is enabled on the
                    AP. Enable SSH to add credentials
                    for device management.
                type: bool
                default: false
              telnet_enabled:
                description:
                  - Indicates if Telnet is enabled on
                    the AP. Enable Telnet to add credentials
                    for device management.
                type: bool
                default: false
              management_username:
                description:
                  - Management username must have a
                    minimum of 1 character and a maximum
                    of 32 characters.
                type: str
              management_password:
                description:
                  - Management password for the AP.
                    Length must be 8 to 120 characters.
                  - For update operations, if an "management_password"
                    is provided, the update will proceed
                    even if there are no changes to
                    the password.
                  - The following password policies
                    are recommended, though not mandatory
                    - Password length should be between
                    8 and 120 characters. - Must contain
                    at least one uppercase character.
                    - Must contain at least one lowercase
                    character. - Must contain at least
                    one digit.
                  - What is Not Allowed - Default passwords
                    (For example, "Cisco") and reverse
                    passwords (For example, "Ocsic")
                    - Alphabets repeated more than twice
                    in sequence (For example, "ccc")
                    - Digits repeated more than twice
                    in sequence (For example, "111")
                    - Sequential alphabets (For example,
                    "abc") - Sequential digits (For
                    example, "123")
                  - In an update operation, the update
                    will happen even if the password
                    is not changed.
                type: str
              management_enable_password:
                description:
                  - Enable password for managing the
                    AP. Length must be 8 to 120 characters.
                  - For update operations, if an "management_enable_password"
                    is provided, the update will proceed
                    even if there are no changes to
                    the password.
                  - The following password policies
                    are recommended, though not mandatory
                    - Password length should be between
                    8 and 120 characters. - Must contain
                    at least one uppercase character.
                    - Must contain at least one lowercase
                    character. - Must contain at least
                    one digit.
                  - What is Not Allowed - Default passwords
                    (For example, "Cisco") and reverse
                    passwords (For example, "Ocsic")
                    - Alphabets repeated more than twice
                    in sequence (For example, "ccc")
                    - Digits repeated more than twice
                    in sequence (For example, "111")
                    - Sequential alphabets (For example,
                    "abc") - Sequential digits (For
                    example, "123")
                  - In an update operation, the update
                    will happen even if the password
                    is not changed.
                type: str
              cdp_state:
                description:
                  - Indicates whether CDP (Cisco Discovery
                    Protocol) is enabled on the AP.
                  - Enabling CDP allows Cisco Access
                    Points to be discovered by neighboring
                    devices and vice versa.
                type: bool
                default: false
          security_settings:
            description:
              - Configure security settings for the
                Access Point.
            type: dict
            suboptions:
              awips:
                description:
                  - aWIPS (Adaptive Wireless Intrusion
                    Prevention System) is supported
                    from IOS-XE version 17.3.1 and above.
                  - Indicates whether aWIPS is enabled
                    on the AP.
                type: bool
                default: false
              awips_forensic:
                description:
                  - Forensic Capture is supported from
                    IOS-XE version 17.4 and above.
                  - Forensic Capture can be activated
                    only if aWIPS is enabled.
                  - Indicates if AWIPS forensic is enabled
                    on the AP.
                type: bool
                default: false
              rogue_detection_enabled:
                description:
                  - Detects unauthorized Access Points
                    (rogues) that are installed on a
                    secure network without explicit
                    authorization from a system administrator.
                  - Configures the rogue detection parameters.
                  - Indicates whether rogue detection
                    is enabled on the AP.
                type: bool
                default: false
              minimum_rssi:
                description:
                  - Specifies the minimum RSSI (Received
                    Signal Strength Indicator) for rogue
                    detection.
                  - Value should be between -128 to
                    -70 decibel milliwatts.
                type: int
                default: -90
              transient_interval:
                description:
                  - Specifies the transient interval
                    for rogue detection.
                  - This is the duration for which a
                    detected rogue access point is considered
                    transient (temporary) before it
                    is either cleared or confirmed as
                    rogue.
                type: int
                default: 0
              report_interval:
                description:
                  - Specifies the report interval for
                    rogue detection.
                  - This defines how often rogue access
                    points are reported after detection.
                  - The value should be between 10 and
                    300 seconds.
                type: int
                default: 10
              pmf_denial:
                description:
                  - Indicates whether PMF (Protected
                    Management Frames) denial is active
                    on the AP.
                  - PMF is a security feature that protects
                    management frames such as authentication
                    and association frames in a wireless
                    network.
                  - When PMF Denial is enabled, the
                    AP blocks PMF-protected frames,
                    which can help mitigate certain
                    types of network attacks but may
                    limit some security features or
                    device compatibility.
                  - PMF Denial is supported from IOS-XE
                    version 17.12 and above.
                type: bool
                default: false
          mesh_enabled:
            description:
              - This setting indicates whether mesh
                networking is enabled on the Access
                Point (AP).
              - When mesh networking is enabled, a custom
                mesh profile with the configured parameters
                will be created and mapped to the AP
                join profile on the device.
              - If mesh networking is disabled, any
                existing custom mesh profile will be
                deleted from the device, and the AP
                join profile will revert to the default
                mesh profile.
              - Mesh networking allows APs to form a
                wireless mesh to extend coverage and
                connect devices in areas where wired
                connections may not be available.
            type: bool
            default: false
          mesh_settings:
            description:
              - Settings specific to mesh networking
                configuration. Mesh networking allows
                Access Points (APs) to communicate with
                each other wirelessly, extending coverage
                and improving network performance.
              - The MAC address of Access Points (APs)
                in mesh mode must be added to the AP
                Authorization list to ensure proper
                communication.
            type: dict
            suboptions:
              range:
                description:
                  - The range of the mesh network, which
                    defines the coverage area.
                  - The value should be between 150
                    and 132000 meters.
                type: int
                default: 12000
              backhaul_client_access:
                description:
                  - Indicates whether backhaul client
                    access is enabled on the AP.
                  - Enabling this allows clients to
                    access the backhaul network.
                type: bool
                default: false
              rap_downlink_backhaul:
                description:
                  - Specifies the type of downlink backhaul
                    used by the Root Access Point (RAP)
                    to communicate with other Access
                    Points (APs) in a mesh network.
                  - Options - "5 GHz" Utilizes the 5
                    GHz frequency band for backhaul
                    communication, providing higher
                    data rates and generally less interference.
                    - "2.4 GHz" Uses the 2.4 GHz frequency
                    band for backhaul communication.
                    It is typically used when 5 GHz
                    is not available or for longer-range
                    connectivity.
                  - The choice of backhaul impacts the
                    network's overall performance, range,
                    and data throughput. The 5 GHz backhaul
                    is generally preferred for its higher
                    capacity and reduced interference
                    compared to the 2.4 GHz band.
                type: str
                default: "5 GHz"
                choices: ["5 GHz", "2.4 GHz"]
              ghz_5_backhaul_data_rates:
                description:
                  - Specifies the available data rates
                    for the 5 GHz backhaul.
                  - Defines the communication speed
                    between mesh access points over
                    the 5 GHz frequency band.
                type: str
                choices: ["auto", "802.11abg", "802.12ac", "802.11ax", "802.11n"]
                default: "auto"
              ghz_2_4_backhaul_data_rates:
                description:
                  - Specifies the available data rates
                    for the 2.4 GHz backhaul.
                  - Defines the communication speed
                    between mesh access points over
                    the 2.4 GHz frequency band.
                type: str
                choices: ["auto", "802.11abg", "802.11ax", "802.11n"]
                default: "auto"
              bridge_group_name:
                description:
                  - Name of the bridge group for mesh
                    settings.
                  - The bridge group is responsible
                    for connecting the APs in the mesh
                    network.
                  - If not configured, the "Default"
                    Bridge group name will be used.
                type: str
                default: "default"
          power_settings:
            description:
              - Configure power settings for the Access
                Point.
            type: dict
            suboptions:
              ap_power_profile_name:
                description:
                  - Name of the existing AP power profile.
                  - Select the AP Power Profile that
                    should be applied to Access Points.
                  - If an Access Point does not receive
                    the required power, it will function
                    in a derated state as defined by
                    the sequence of rules in the Power
                    Profile.
                  - Only Power profiles with rules will
                    be listed below.
                  - This setting is applicable only
                    for IOS-XE based Wireless Controllers
                    running 17.10.1 and above.
                type: str
              calendar_power_profiles:
                description:
                  - Select when you would like the Power
                    Profile to be applied to Access
                    Points.
                  - All rules defined in the Power Profile
                    take effect simultaneously.
                  - The Start Time and End Time configured
                    below will be applicable based on
                    Time Zone settings configured in
                    the Additional Tab of AP Profile.
                  - Represents a calendar-based power
                    profile setting.
                  - You can map multiple Power Profiles
                    to different calendar schedules
                    based on your requirement.
                  - Select the AP Power Profile that
                    should be applied to Access Points
                    in power save mode.
                  - You can map multiple Power Profiles
                    to different calendar schedules
                    based on your requirement.
                  - All rules defined in the Power Profile
                    take effect simultaneously in the
                    configured schedule.
                type: list
                elements: dict
                suboptions:
                  ap_power_profile_name:
                    description:
                      - Select the Power Profile that
                        you would like to use.
                      - Name of the existing AP power
                        profile to be mapped to the
                        calendar power profile.
                    type: str
                  scheduler_type:
                    description:
                      - Type of the scheduler (when
                        the power profile should be
                        applied).
                    type: str
                    choices: ["DAILY", "WEEKLY", "MONTHLY"]
                  scheduler_start_time:
                    description:
                      - Start time of the duration setting.
                      - Supported format is 12-hour
                        clock (AM/PM).
                      - For example, "12:00 PM", "6:00
                        AM".
                    type: str
                  scheduler_end_time:
                    description:
                      - End time of the duration setting.
                      - Supported format is 12-hour
                        clock (AM/PM).
                      - For example, "02:00 PM", "9:00
                        AM".
                    type: str
                  scheduler_days_list:
                    description: Applies every week
                      on the selected days.
                    type: list
                    elements: str
                    choices: ["sunday", "saturday",
                      "tuesday", "wednesday", "thursday",
                      "friday", "monday"]
                  scheduler_dates_list:
                    description:
                      - Dates of the duration setting,
                        applicable for MONTHLY schedulers.
                      - For example, ["2", "9", "28"]
                    type: list
                    elements: str
          country_code:
            description:
              - Set the country code for ROW Access
                Points that have no country code configured
                already.
              - This setting will not impact the Access
                Points that already have a country code
                configured.
            type: str
            choices: ["Afghanistan", "Albania", "Algeria",
              "Angola", "Argentina", "Australia", "Austria",
              "Bahamas", "Bahrain", "Bangladesh", "Barbados",
              "Belarus", "Belgium", "Bhutan", "Bolivia",
              "Bosnia", "Botswana", "Brazil", "Brunei",
              "Bulgaria", "Burundi", "Cambodia", "Cameroon",
              "Canada", "Chile", "China", "Colombia",
              "Costa Rica", "Croatia", "Cuba", "Cyprus",
              "Czech Republic", "Democratic Republic
                of the Congo", "Denmark", "Dominican
                Republic", "Ecuador", "Egypt", "El Salvador",
              "Estonia", "Ethiopia", "Fiji", "Finland",
              "France", "Gabon", "Georgia", "Germany",
              "Ghana", "Gibraltar", "Greece", "Guatemala",
              "Honduras", "Hong Kong", "Hungary", "Iceland",
              "India", "Indonesia", "Iraq", "Ireland",
              "Isle of Man", "Israel", "Israel (Outdoor)",
              "Italy", "Ivory Coast (Cote dIvoire)",
              "Jamaica", "Japan 2(P)", "Japan 4(Q)",
              "Jersey", "Jordan", "Kazakhstan", "Kenya",
              "Korea Extended (CK)", "Kosovo", "Kuwait",
              "Laos", "Latvia", "Lebanon", "Libya",
              "Liechtenstein", "Lithuania", "Luxembourg",
              "Macao", "Macedonia", "Malaysia", "Malta",
              "Mauritius", "Mexico", "Moldova", "Monaco",
              "Mongolia", "Montenegro", "Morocco", "Myanmar",
              "Namibia", "Nepal", "Netherlands", "New
                Zealand", "Nicaragua", "Nigeria", "Norway",
              "Oman", "Pakistan", "Panama", "Paraguay",
              "Peru", "Philippines", "Poland", "Portugal",
              "Puerto Rico", "Qatar", "Romania", "Russian
                Federation", "San Marino", "Saudi Arabia",
              "Serbia", "Singapore", "Slovak Republic",
              "Slovenia", "South Africa", "Spain", "Sri
                Lanka", "Sudan", "Sweden", "Switzerland",
              "Taiwan", "Thailand", "Trinidad", "Tunisia",
              "Turkey", "Uganda", "Ukraine", "United
                Arab Emirates", "United Kingdom", "United
                Republic of Tanzania", "United States",
              "Uruguay", "Uzbekistan", "Vatican City
                State", "Venezuela", "Vietnam", "Yemen",
              "Zambia", "Zimbabwe"]
          time_zone:
            description:
              - In the Time Zone area, choose one of
                the following options. - Not Configured
                - APs operate in the UTC time zone.
                - Controller - APs operate in the Cisco
                Wireless Controller time zone. - Delta
                from Controller - APs operate in the
                offset time from the wireless controller
                time zone.
              - Time zone is supported from IOS-XE version
                17.6 and above.
              - When updating "time_zone" from "DELTA
                FROM CONTROLLER" to "NOT CONFIGURED"
                or "CONTROLLER", make sure to set "time_zone_offset_hour"
                and "time_zone_offset_minutes" to 0.
            type: str
            choices: ["NOT CONFIGURED", "CONTROLLER", "DELTA FROM CONTROLLER"]
            default: "NOT CONFIGURED"
          time_zone_offset_hour:
            description:
              - Enter the hour value (HH) for the time
                zone offset.
              - The value should be between -12 and
                14
            type: int
            default: 0
          time_zone_offset_minutes:
            description:
              - Enter the minute value (MM) for the
                time zone offset.
              - The value should be between 0 and 59.
            type: int
            default: 0
          maximum_client_limit:
            description:
              - The maximum number of clients that can
                be supported.
              - The value should be between 0 and 1200.
            type: int
            default: 0
      radio_frequency_profiles:
        description:
          - A list of configurations to create, update,
            or delete Radio Frequency (RF) profiles.
          - Each profile contains configuration settings
            for different radio bands (e.g., 2.4 GHz,
            5 GHz).
          - Useful for managing and optimizing wireless
            network performance, ensuring proper channel
            allocation and interference management.
          - The RF profile will be provisioned on the
            Wireless LAN Controller (WLC) during Access
            Point (AP) network provisioning or AP Plug
            and Play (PnP) onboarding.
          - The RF profile will also be pushed to the
            WLC when network provisioning occurs and
            when the RF profile is associated with a
            network profile configured under advanced
            settings for AireOS controllers.
        type: list
        elements: dict
        suboptions:
          radio_frequency_profile_name:
            description:
              - Name of the radio frequency profile.
              - Unique identifier for the profile.
              - Required for profile create/update/delete
                radio frequency profile operations.
            type: str
            required: true
          default_rf_profile:
            description:
              - Indicates if this is the default RF
                profile.
              - Boolean value where "true" sets this
                as default.
              - Optional, default is "false".
              - True if RF Profile is default, else
                false.
              - Only one RF profile can be marked as
                default at any given time.
            type: bool
          radio_bands:
            description:
              - List of radio bands supported by the
                RF profile.
              - Includes configuration settings for
                each band to be enabled for the RF profile.
              - Radio bands can be selected based on
                the AP's capabilities and deployment
                requirements.
            type: list
            elements: int
            choices: [2.4, 5, 6]
          radio_bands_2_4ghz_settings:
            description:
              - Settings specific to the 2.4 GHz radio
                band.
              - Includes channels, data rates, and other
                parameters.
            type: dict
            suboptions:
              parent_profile:
                description:
                  - Parent profile from which settings
                    are inherited.
                  - Defines baseline configurations
                    for the 2.4 GHz radio band.
                type: str
                choices: ["HIGH", "TYPICAL", "LOW", "CUSTOM"]
                default: "CUSTOM"
              dca_channels_list:
                description:
                  - List of DCA (Dynamic Channel Assignment)
                    channels for the 2.4 GHz band.
                  - Channels are dynamically assigned
                    based on network conditions to minimize
                    interference.
                  - DCA ensures that the selected channels
                    have minimal overlap and congestion,
                    improving network performance.
                  - In the 2.4 GHz band, typically only
                    channels 1, 6, and 11 are non-overlapping
                    and preferred.
                  - The list includes channels 1 through
                    14, but the dynamic assignment process
                    helps optimize the usage of these
                    channels based on the wireless environment.
                type: list
                elements: int
                choices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
                default: [1, 6, 11]
              supported_data_rates_list:
                description:
                  - List of supported data rates for
                    the radio band.
                  - Data rates are specified in Mbps
                    and represent the maximum transmission
                    speed.
                  - Higher data rates allow for faster
                    communication but require better
                    signal quality.
                  - Note - If a lower data rate (e.g.,
                    1 Mbps) is selected, all higher
                    data rates (e.g., 2 Mbps, 5.5 Mbps,
                    6 Mbps, etc.) should also be selected
                    to allow fallback as signal quality
                    varies.
                type: list
                elements: float
                choices: [1, 2, 5.5, 6, 9, 11, 12, 18,
                24, 36, 48, 54]
                default: [9, 11, 12, 18, 24, 36, 48, 54]
              mandatory_data_rates_list:
                description:
                  - List of mandatory data rates for
                    the 2.4 GHz band.
                  - Must be a subset of supported data
                    rates.
                  - Valid value are [1, 2, 5.5, 6, 9,
                    11, 12, 18, 24, 36, 48, 54]
                type: list
                elements: float
                default: [9]
              minimum_power_level:
                description:
                  - Minimum power level for the 2.4
                    GHz band.
                  - Specified in dBm, affects coverage
                    area.
                  - Value should be between -10 to 30.
                type: int
                default: 7
              maximum_power_level:
                description:
                  - Maximum power level for the 2.4
                    GHz band.
                  - Specified in dBm, affects coverage
                    area.
                  - Value should be between -10 to 30.
                type: int
                default: 30
              rx_sop_threshold:
                description:
                  - The RX-SOP (Receiver Start of Packet)
                    threshold defines the signal strength
                    required for the access point (AP)
                    to begin processing received packets.
                  - It impacts how sensitive the AP
                    is to weak signals, which in turn
                    affects coverage and interference
                    management.
                  - This threshold is used to control
                    the signal strength sensitivity
                    of the AP's receiver, helping to
                    determine whether a received signal
                    is valid or should be ignored.
                  - A higher RX-SOP threshold means
                    the AP will ignore weaker signals,
                    which can help avoid interference
                    from unwanted, weak signals.
                  - A lower RX-SOP threshold makes the
                    AP more sensitive to weaker signals,
                    which can improve coverage in low-signal
                    areas but may increase the risk
                    of processing noise or interference.
                  - The threshold can be set to one
                    of several predefined values, or
                    customized with a specific signal
                    strength value (in dBm).
                  - The threshold setting applies to
                    the 2.4 GHz radio band and impacts
                    how packets are received and processed,
                    particularly in environments with
                    varying signal conditions.
                type: str
                choices: ["HIGH", "MEDIUM", "LOW", "AUTO", "CUSTOM"]
                default: "MEDIUM"
              custom_rx_sop_threshold:
                description:
                  - The custom RX-SOP threshold allows
                    defining a specific signal strength
                    value (in dBm) when the 'rx_sop_threshold'
                    is set to "CUSTOM".
                  - This setting enables fine-tuned
                    control over the APs sensitivity
                    to signals, overriding the predefined
                    values of "HIGH", "MEDIUM", "LOW",
                    or "AUTO".
                  - The value must be within the range
                    of -85 dBm to -60 dBm.
                  - A lower value (e.g., closer to -85
                    dBm) increases sensitivity, while
                    a higher value (e.g., closer to
                    -60 dBm) reduces sensitivity to
                    weak.
                type: int
              tpc_power_threshold:
                description:
                  - The TPC power threshold defines
                    the minimum signal strength (in
                    dBm) required for Transmit Power
                    Control (TPC) to adjust the power
                    levels for the 2.4 GHz band.
                  - TPC is used to manage transmit power,
                    ensuring optimal coverage and interference
                    mitigation.
                  - The value must be between -80 dBm
                    and -50 dBm.
                  - A value closer to -50 dBm will allow
                    higher transmit power, while a value
                    closer to -80 dBm will reduce the
                    transmit power.
                type: int
                default: -70
              coverage_hole_detection:
                description:
                  - Coverage hole detection is used
                    to monitor and identify areas with
                    insufficient wireless coverage within
                    the 2.4 GHz band.
                  - This feature ensures that coverage
                    holes, where signal strength is
                    inadequate, are detected and can
                    be addressed.
                  - The settings help to adjust network
                    performance by detecting gaps in
                    coverage, particularly for client
                    devices and voice calls.
                type: dict
                suboptions:
                  minimum_client_level:
                    description:
                      - Defines the minimum number of
                        clients required for a coverage
                        hole to be detected.
                      - If fewer clients are connected,
                        the detection mechanism will
                        not trigger, as the system assumes
                        coverage is sufficient.
                      - Value should be between 1 to
                        200 clients.
                    type: int
                    default: 3
                  data_rssi_threshold:
                    description:
                      - Data RSSI (Received Signal Strength
                        Indicator) threshold determines
                        the signal strength level below
                        which the network considers
                        the coverage to be insufficient
                        for data traffic.
                      - The value should be between
                        -90 to -60 dBm.
                      - If the RSSI for data traffic
                        falls below this threshold,
                        a coverage hole is detected.
                    type: int
                    default: -80
                  voice_rssi_threshold:
                    description:
                      - Voice RSSI threshold determines
                        the signal strength level below
                        which coverage is considered
                        inadequate for voice traffic.
                      - The value should be between
                        -90 to -60 dBm.
                      - If the RSSI for voice traffic
                        falls below this threshold,
                        a coverage hole is detected.
                    type: int
                    default: -80
                  exception_level:
                    description:
                      - The exception level sets the
                        percentage of coverage hole
                        occurrences that are allowed
                        before the system triggers an
                        alert.
                      - This helps avoid false positives
                        or over-sensitivity in detecting
                        coverage gaps.
                      - The value should be between
                        0 to 100.
                      - A higher value indicates stricter
                        detection of coverage issues.
                    type: int
                    default: 25
              client_limit:
                description:
                  - Maximum number of clients for the
                    2.4 GHz band.
                  - Value should be between 0 to 500.
                type: int
                default: 200
              spatial_reuse:
                description:
                  - Spatial reuse settings for the 2.4
                    GHz band are used to improve the
                    efficiency of frequency use in areas
                    with multiple devices.
                  - It helps to manage interference
                    by defining thresholds for non-SRG
                    (Non-Same Radio Group) and SRG (Same
                    Radio Group) OBSS (Overlapping Basic
                    Service Set) PD (Physical Layer)
                    values.
                  - Proper configuration can improve
                    network performance by reducing
                    interference and optimizing spectrum
                    usage in dense environments.
                type: dict
                suboptions:
                  non_srg_obss_pd:
                    description:
                      - Defines whether Non-SRG OBSS
                        PD (Overlapping BSS Power Distribution)
                        is enabled or disabled for 2.4
                        GHz band.
                      - Non-SRG OBSS PD controls interference
                        from other devices operating
                        on different channels.
                      - When enabled, the access points
                        consider power distribution
                        to minimize interference from
                        devices in adjacent BSSs.
                    type: bool
                    default: false
                  non_srg_obss_pd_max_threshold:
                    description:
                      - Sets the maximum threshold for
                        Non-SRG OBSS PD for 2.4 GHz
                        band.
                      - The value should be between
                        -82 to -62 dBm.
                      - This threshold determines how
                        much power is allowed for non-SRG
                        OBSS PD to operate without causing
                        excessive interference.
                    type: int
                    default: -62
                  srg_obss_pd:
                    description:
                      - Defines whether SRG OBSS PD
                        (Same Radio Group OBSS PD) is
                        enabled or disabled for 2.4
                        GHz band.
                      - SRG OBSS PD ensures that devices
                        in the same radio group do not
                        interfere with each other, improving
                        overall network efficiency.
                    type: bool
                    default: false
                  srg_obss_pd_min_threshold:
                    description:
                      - Sets the minimum threshold for
                        SRG OBSS PD for 2.4 GHz band.
                      - The value should be between
                        -82 to -62 dB.
                      - This threshold ensures that
                        interference is minimized within
                        the same radio group by controlling
                        how much power is allowed for
                        transmission.
                    type: int
                    default: -82
                  srg_obss_pd_max_threshold:
                    description:
                      - Sets the maximum threshold for
                        SRG OBSS PD for 2.4 GHz band.
                      - The value should be between
                        -82 to -62 dBm.
                      - This threshold limits the amount
                        of power allowed for SRG OBSS
                        PD to avoid excessive interference
                        within the same radio group.
                    type: int
                    default: -62
          radio_bands_5ghz_settings:
            description:
              - 5 GHz radio band settings that include
                configurations for channel width, data
                rates, and other related parameters.
              - Used to manage the wireless performance
                and optimize the spectrum usage for
                devices operating on the 5 GHz band.
            type: dict
            suboptions:
              parent_profile:
                description:
                  - Defines the baseline configuration
                    settings inherited by this profile.
                type: str
                default: "CUSTOM"
                choices: ["HIGH", "TYPICAL", "LOW", "CUSTOM"]
              channel_width:
                description:
                  - Defines the channel width for the
                    5 GHz band.
                  - Channel width impacts available
                    bandwidth, affecting both throughput
                    and interference.
                  - Options include 20 MHz, 40 MHz,
                    80 MHz, 160 MHz, and "best," which
                    automatically selects the most optimal
                    width.
                type: str
                default: "best"
                choices: ["20", "40", "80", "160", "best"]
              preamble_puncturing:
                description:
                  - Preamble puncturing reduces interference
                    by removing parts of the preamble
                    in the transmitted signal.
                  - Defines whether preamble puncturing
                    is enabled or disabled.
                  - Applicable to Wi-Fi 7 configurations
                    and supported only by IOS devices
                    with version 17.15 or higher.
                type: bool
                default: false
              zero_wait_dfs:
                description:
                  - Defines whether zero wait DFS is
                    enabled or disabled.
                  - DFS (Dynamic Frequency Selection)
                    allows wireless devices to automatically
                    select channels to avoid interference
                    with radar systems.
                  - Enabling zero wait DFS eliminates
                    the wait time required after detecting
                    radar, allowing for faster channel
                    switching.
                type: bool
                default: false
              dca_channels_list:
                description:
                  - List of DCA channels for the 5 GHz
                    band.
                  - Channels are specified in a list
                    format.
                  - For channel_width 20 MHz, any combination
                    works.
                  - For channel_width 40 MHz  [36, 40],
                    [44, 48], [52, 56], [60, 64], [100,
                    104], [108, 112], [116, 120], [124,
                    128], [132, 136], [140, 144], [149,
                    153], [157, 161], [169, 173].
                  - For channel_width 80 MHz [36, 40,
                    44, 48], [52, 56, 60, 64], [100,
                    104, 108, 112], [116, 120, 124,
                    128], [132, 136, 140, 144], [149,
                    153, 157, 161], [165, 169, 173,
                    177].
                  - For channel_width 160 MHz [36, 40,
                    44, 48, 52, 56, 60, 64], [100, 104,
                    108, 112, 116, 120, 124, 128], [36,
                    40, 44, 48, 52, 56, 60, 64, 100,
                    104, 108, 112, 116, 120, 124, 128].
                  - For channel_width best, any combination
                    works.
                type: list
                elements: int
                default: [36, 40, 44, 48, 52, 56, 60, 64, 149, 153, 157, 161]
                choices: [36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
                124, 128, 132, 136, 140, 144, 149,
                153, 157, 161, 165, 169, 173]
              supported_data_rates_list:
                description:
                  - List of supported data rates for
                    the 5 GHz band.
                  - Rates are specified in Mbps.
                type: list
                elements: int
                default: [6, 9, 12, 18, 24, 36, 48, 54]
                choices: [6, 9, 12, 18, 24, 36, 48, 54]
              mandatory_data_rates_list:
                description:
                  - List of mandatory data rates for
                    the 5 GHz band.
                  - Must be a subset of supported data
                    rates.
                  - Max 2 allowed.
                  - Valid values are [6, 9, 12, 18,
                    24, 36, 48, 54].
                type: list
                elements: int
                default: [6]
              minimum_power_level:
                description:
                  - Minimum power level for the 5 GHz
                    band.
                  - Specified in dBm, affects coverage
                    area.
                  - Value should be between -10 to 30.
                type: int
                default: -10
              maximum_power_level:
                description:
                  - Maximum power level for the 5 GHz
                    band.
                  - Specified in dBm, affects coverage
                    area.
                  - Value should be between -10 to 30.
                type: int
                default: 30
              rx_sop_threshold:
                description:
                  - RX-SOP threshold setting for the
                    5 GHz band.
                  - Defines sensitivity to interference.
                type: str
                default: "LOW"
                choices: ["HIGH", "MEDIUM", "LOW", "AUTO", "CUSTOM"]
              custom_rx_sop_threshold:
                description:
                  - RX-SOP threshold custom configuration.
                  - Value should be between -85 to -60.
                type: int
              tpc_power_threshold:
                description:
                  - TPC power threshold for the 5 GHz
                    band.
                  - Used for transmit power control.
                  - Value should be between -80 to -50.
                type: int
                default: -60
              coverage_hole_detection:
                description:
                  - Coverage hole detection settings
                    for the 5 GHz band.
                  - Includes thresholds and exception
                    levels.
                type: dict
                suboptions:
                  minimum_client_level:
                    description:
                      - Minimum number of clients for
                        coverage hole detection.
                      - Value should be between 1 to
                        200.
                    type: int
                    default: 3
                  data_rssi_threshold:
                    description:
                      - Data RSSI threshold for coverage
                        hole detection.
                      - Value should be between -90
                        to -60.
                    type: int
                    default: -80
                  voice_rssi_threshold:
                    description:
                      - Voice RSSI threshold for coverage
                        hole detection.
                      - Value should be between -90
                        to -60.
                    type: int
                    default: -80
                  exception_level:
                    description:
                      - Exception level percentage for
                        coverage hole detection.
                      - Value should be between 0 to
                        100.
                    type: int
                    default: 25
              client_limit:
                description:
                  - Maximum number of clients for the
                    5 GHz band.
                  - Value should be between 1 to 200.
                type: int
                default: 200
              flexible_radio_assignment:
                description:
                  - Flexible radio assignment settings
                    for the 5 GHz band.
                  - Includes client awareness and selection.
                type: dict
                suboptions:
                  client_aware:
                    description: Enable or disable client
                      awareness.
                    type: bool
                    default: false
                  client_select:
                    description:
                      - Client selection percentage.
                      - Value should be between 0 to
                        100.
                    type: int
                    default: 50
                  client_reset:
                    description:
                      - Client reset percentage.
                      - Value should be between 0 to
                        100.
                    type: int
                    default: 5
              spatial_reuse:
                description:
                  - Spatial reuse settings for the 5
                    GHz band are used to improve the
                    efficiency of frequency use in areas
                    with multiple devices.
                type: dict
                suboptions:
                  non_srg_obss_pd:
                    description:
                      - Defines whether Non-SRG OBSS
                        PD (Overlapping BSS Power Distribution)
                        is enabled or disabled for 5
                        GHz band.
                    type: bool
                    default: false
                  non_srg_obss_pd_max_threshold:
                    description:
                      - Sets the maximum threshold for
                        Non-SRG OBSS PD for 5 GHz band.
                      - The value should be between
                        -82 to -62 dBm.
                    type: int
                    default: -62
                  srg_obss_pd:
                    description:
                      - Defines whether SRG OBSS PD
                        (Same Radio Group OBSS PD) is
                        enabled or disabled for 5 GHz
                        band.
                      - SRG OBSS PD ensures that devices
                        in the same radio group do not
                        interfere with each other, improving
                        overall network efficiency.
                    type: bool
                    default: false
                  srg_obss_pd_min_threshold:
                    description:
                      - Sets the minimum threshold for
                        SRG OBSS PD for 5 GHz band.
                      - The value should be between
                        -82 to -62 dB.
                      - This threshold ensures that
                        interference is minimized within
                        the same radio group by controlling
                        how much power is allowed for
                        transmission.
                    type: int
                    default: -82
                  srg_obss_pd_max_threshold:
                    description:
                      - Sets the maximum threshold for
                        SRG OBSS PD for 5 GHz band.
                      - The value should be between
                        -82 to -62 dBm.
                    type: int
                    default: -62
          radio_bands_6ghz_settings:
            description:
              - Settings specific to the 6 GHz radio
                band.
              - Includes channel width, data rates,
                and more.
            type: dict
            suboptions:
              parent_profile:
                description: Parent profile of 6 GHz
                  radio band.
                type: str
                default: "CUSTOM"
                choices: ["CUSTOM"]
              minimum_dbs_channel_width:
                description:
                  - Minimum DBS Width.
                type: int
                default: 20
                choices: [20, 40, 80, 160, 320]
              maximum_dbs_channel_width:
                description: Maximum DBS Width.
                type: int
                default: 80
                choices: [20, 40, 80, 160, 320]
              preamble_puncturing:
                description:
                  - Enable or Disable Preamble Puncturing.
                  - This Wifi 7 configuration is applicable
                    to wireless IOS devices supporting
                    17.15 and higher.
                type: bool
                default: false
              psc_enforcing_enabled:
                description: PSC Enforcing Enable for
                  6 GHz radio band.
                type: bool
                default: false
              dca_channels_list:
                description:
                  - DCA channels of 6 GHz radio band.
                  - Valid values are [1, 5, 9, 13, 17,
                    21, 25, 29, 33, 37, 41, 45, 49,
                    53, 57, 61, 65, 69, 73, 77, 81,
                    85, 89, 93, 97, 101, 105, 109, 113,
                    117, 121, 125, 129, 133, 137, 141,
                    145, 149, 153, 157, 161, 165, 169,
                    173, 177, 181, 185, 189, 193, 197,
                    201, 205, 209, 213, 217, 221, 225,
                    229, 233]
                type: list
                elements: int
                default: [5, 21, 37, 53, 69, 85, 101, 117, 133, 149, 165, 181, 197, 213,
                229]
              supported_data_rates_list:
                description:
                  - Data rates of 6 GHz radio band.
                  - Valid values [6, 9, 12, 18, 24,
                    36, 48, 54]
                type: list
                elements: int
                default: [6, 9, 12, 18, 24, 36, 48, 54]
              mandatory_data_rates_list:
                description:
                  - Mandatory data rates of 6 GHz radio
                    band.
                  - Must be a subset of selected data
                    rates.
                  - Maximum of 2 values.
                  - Valid values [6, 9, 12, 18, 24,
                    36, 48, 54].
                type: list
                elements: int
                default: [6]
              standard_power_service:
                description: True if Standard Power
                  Service is enabled, else false.
                type: bool
                default: false
              minimum_power_level:
                description:
                  - Minimum power level of 6 GHz radio
                    band.
                  - Value should be between -10 to 30.
                type: int
                default: -10
              maximum_power_level:
                description:
                  - Maximum power level of 6 GHz radio
                    band.
                  - Value should be between -10 to 30.
                type: int
                default: 30
              rx_sop_threshold:
                description: RX-SOP threshold of 6 GHz
                  radio band.
                type: str
                default: "AUTO"
                choices: ["HIGH", "MEDIUM", "LOW", "AUTO", "CUSTOM"]
              custom_rx_sop_threshold:
                description:
                  - RX-SOP threshold custom configuration.
                  - Value should be between -85 to -60.
                type: int
              tpc_power_threshold:
                description:
                  - Specifies the TPC Power threshold
                    of 6 GHz radio band.
                  - Value should be between -80 to -50.
                type: int
                default: -70
              coverage_hole_detection:
                description:
                  - Coverage hole detection settings
                    for the 6 GHz band.
                  - Includes thresholds and exception
                    levels.
                type: dict
                suboptions:
                  minimum_client_level:
                    description:
                      - Minimum number of clients for
                        coverage hole detection.
                      - Value should be between 1 to
                        200.
                    type: int
                    default: 3
                  data_rssi_threshold:
                    description:
                      - Data RSSI threshold for coverage
                        hole detection.
                      - Value should be between -90
                        to -60.
                    type: int
                    default: -80
                  voice_rssi_threshold:
                    description:
                      - Voice RSSI threshold for coverage
                        hole detection.
                      - Value should be between -90
                        to -60.
                    type: int
                    default: -80
                  exception_level:
                    description:
                      - Exception level percentage for
                        coverage hole detection.
                      - Value should be between 0 to
                        100.
                    type: int
                    default: 25
              client_limit:
                description:
                  - Specifies Client Limit of 6 GHz
                    radio band.
                  - Value should be between 0 to 500.
                type: int
                default: 200
              flexible_radio_assignment:
                description: Configure Flexible Radio
                  Assignment 6 GHz Properties.
                type: dict
                suboptions:
                  client_reset_count:
                    description:
                      - Specifies the Client Reset Count
                        of 6 GHz radio band.
                      - Value should be between 1 to
                        10.
                    type: int
                    default: 1
                  client_utilization_threshold:
                    description:
                      - Specifies the Client Utilization
                        Threshold of 6 GHz radio band.
                      - Value should be between 1 to
                        100.
                    type: int
                    default: 5
              discovery_frames_6ghz:
                description: Discovery Frames of 6 GHz
                  radio band.
                type: str
                default: "None"
                choices: ["None", "Broadcast Probe Response", "FILS Discovery"]
              broadcast_probe_response_interval:
                description:
                  - Specifies the Broadcast Probe Response
                    Interval of 6 GHz radio band.
                  - Value should be between 5 to 25.
                type: int
                default: 20
              multi_bssid:
                description: Multi Bssid Properties.
                type: dict
                suboptions:
                  dot_11ax_parameters:
                    description: 802.11ax Parameters.
                    type: dict
                    suboptions:
                      ofdma_downlink:
                        description: Set "ofdma_downlink"
                          to true to activate OFDMA
                          Downlink.
                        type: bool
                        default: false
                      ofdma_uplink:
                        description: Set "ofdma_uplink"
                          to true to activate OFDMA
                          Uplink.
                        type: bool
                        default: false
                      mu_mimo_downlink:
                        description: Set "mu_mimo_downlink"
                          to true to activate MU-MIMO
                          Uplink.
                        type: bool
                        default: true
                      mu_mimo_uplink:
                        description: Set "mu_mimo_uplink"
                          to true to activate MU-MIMO
                          Downlink.
                        type: bool
                        default: true
                  dot_11be_parameters:
                    description: 802.11be Parameters.
                    type: dict
                    suboptions:
                      ofdma_downlink:
                        description: Set "ofdma_downlink"
                          to true to activate OFDMA
                          Downlink.
                        type: bool
                        default: false
                      ofdma_uplink:
                        description: Set "ofdma_uplink"
                          to true to activate OFDMA
                          Uplink.
                        type: bool
                        default: false
                      mu_mimo_downlink:
                        description: Set "mu_mimo_downlink"
                          to true to activate MU-MIMO
                          Uplink.
                        type: bool
                        default: true
                      mu_mimo_uplink:
                        description: Set "mu_mimo_uplink"
                          to true to activate MU-MIMO
                          Downlink.
                        type: bool
                        default: true
                      ofdma_multi_ru:
                        description: Set "ofdma_multi_ru"
                          to true to activate OFDMA
                          Multi-RU.
                        type: bool
                        default: false
                  target_waketime:
                    description: Set "target_waketime"
                      to true to activate Target Wake
                      Time.
                    type: bool
                    default: false
                  twt_broadcast_support:
                    description: Set "twt_broadcast_support"
                      to true to activate TWT Broadcast
                      Support.
                    type: bool
                    default: false
              spatial_reuse:
                description:
                  - Spatial reuse settings for the 6
                    GHz band are used to improve the
                    efficiency of frequency use in areas
                    with multiple devices.
                type: dict
                suboptions:
                  non_srg_obss_pd:
                    description:
                      - Defines whether Non-SRG OBSS
                        PD (Overlapping BSS Power Distribution)
                        is enabled or disabled for 6
                        GHz band.
                    type: bool
                    default: false
                  non_srg_obss_pd_max_threshold:
                    description:
                      - Sets the maximum threshold for
                        Non-SRG OBSS PD for 6 GHz band.
                      - The value should be between
                        -82 to -62 dBm.
                    type: int
                    default: -62
                  srg_obss_pd:
                    description:
                      - Defines whether SRG OBSS PD
                        (Same Radio Group OBSS PD) is
                        enabled or disabled for 6 GHz
                        band.
                      - SRG OBSS PD ensures that devices
                        in the same radio group do not
                        interfere with each other, improving
                        overall network efficiency.
                    type: bool
                    default: false
                  srg_obss_pd_min_threshold:
                    description:
                      - Sets the minimum threshold for
                        SRG OBSS PD for 6 GHz band.
                      - The value should be between
                        -82 to -62 dB.
                      - This threshold ensures that
                        interference is minimized within
                        the same radio group by controlling
                        how much power is allowed for
                        transmission.
                    type: int
                    default: -82
                  srg_obss_pd_max_threshold:
                    description:
                      - Sets the maximum threshold for
                        SRG OBSS PD for 6 GHz band.
                      - The value should be between
                        -82 to -62 dBm.
                    type: int
                    default: -62
      anchor_groups:
        description:
          - Allows the user to define Anchor Groups
            and their associated Mobility Anchors.
          - An Anchor Group represents a collection
            of mobility anchors, enabling seamless roaming
            and mobility between different network segments.
          - Bulk operations such as create, update, or delete
            for anchor groups are not supported in Catalyst
            Center versions 2.3.7.9 and below.
        type: list
        elements: dict
        suboptions:
          anchor_group_name:
            description:
              - Anchor Group Name.
              - Must be a string with a minimum length
                of 1 and maximum length is 32 characters.
              - Required parameter for anchor groups
                operations.
            type: str
            required: true
          mobility_anchors:
            description:
              - List of Mobility Anchors associated
                with the Anchor Group.
              - This is a required parameter for create
                or update Anchor Group(s) operation.
              - If it is a Managed Device i.e., "managed_device"
                is set to true then required parameters
                are - "device_name", "device_ip_address",
                "device_priority" and "managed_device"
              - If it is a not Managed Device i.e.,
                "managed_device" is set to false then
                required parameters are - "device_name",
                "device_ip_address", "device_priority",
                "managed_device", "device_type"
            type: list
            elements: dict
            required: true
            suboptions:
              device_name:
                description: Peer Host Name.
                type: str
              device_ip_address:
                description:
                  - Indicates the Mobility public IP
                    address.
                  - Allowed formats are "192.168.0.1",
                    "10.0.0.1", "255.255.255.255".
                type: str
              device_mac_address:
                description: Peer Device mobility MAC
                  address.
                type: str
              device_type:
                description: Indicates whether the peer
                  device mobility belongs to the AireOS
                  or IOS-XE family.
                type: str
                choices: ["IOS-XE", "AIREOS"]
              device_priority:
                description:
                  - Indicates anchor priority.
                  - Priority values range from 1 (high)
                    to 3 (low).
                  - Only one priority value is allowed
                    per anchor WLC.
                type: int
              device_nat_ip_address:
                description:
                  - Indicates the private management
                    IP address.
                  - Allowed formats are "192.168.0.1",
                    "10.0.0.1", "255.255.255.255".
                type: str
              mobility_group_name:
                description:
                  - Peer Device mobility group Name.
                  - Must be alphanumeric without special
                    characters {{!,<,space,?/'}}.
                  - Maximum of 31 characters.
                type: str
              managed_device:
                description:
                  - Indicates whether the Wireless LAN
                    Controller supporting the Anchor
                    is managed by the Network Controller.
                  - True means it is managed by the
                    Network Controller.
                type: bool
      feature_template_config:
        description:
          - Configuration for wireless feature templates in Cisco Catalyst Center.
          - Enables advanced wireless features and policies for specific design requirements.
          - Each feature template can be applied to different designs and radio bands.
          - Supports both creation and deletion of feature template configurations.
        type: list
        elements: dict
        required: false
        suboptions:
          aaa_radius_attribute:
            description:
              - Configuration for AAA RADIUS attributes for wireless authentication.
              - Defines RADIUS attributes sent during wireless client authentication.
              - Used to customize authentication behavior and client identification.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply AAA RADIUS attribute configuration.
                  - Must match an existing design in Cisco Catalyst Center.
                  - Design name is case-sensitive and must be unique.
                type: str
                required: true
              called_station_id:
                description:
                  - RADIUS Called-Station-Id attribute value.
                  - Identifies the access point or SSID that the client is connecting to.
                  - Commonly used for accounting and policy enforcement.
                  - Format typically includes AP MAC address and SSID name.
                type: str
                required: true
              unlocked_attributes:
                description:
                  - Set to true to unlock attributes for manual configuration.
                  - When false, attributes are locked and managed by the template.
                  - Allows flexibility in attribute configuration when needed.
                type: bool
                default: false
          advanced_ssid:
            description:
              - Advanced SSID configuration parameters for enhanced wireless features.
              - Provides fine-grained control over SSID behavior and performance.
              - Includes settings for client management, power optimization, and advanced protocols.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply advanced SSID configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                type: str
                required: true
              feature_attributes:
                description:
                  - Collection of advanced SSID feature settings and parameters.
                  - Controls various aspects of SSID operation and client behavior.
                type: dict
                required: false
                suboptions:
                  peer2peer_blocking:
                    description:
                      - Controls peer-to-peer communication between wireless clients.
                      - DISABLE allows all peer-to-peer traffic.
                      - DROP blocks all peer-to-peer communication.
                      - FORWARD_UP forwards traffic to the wired network.
                      - ALLOW_PVT_GROUP allows communication within private groups.
                    type: str
                    choices: ["DISABLE", "DROP", "FORWARD_UP", "ALLOW_PVT_GROUP"]
                  passive_client:
                    description:
                      - Enable passive client detection for better roaming experience.
                      - Helps identify clients that don't actively probe for networks.
                    type: bool
                    default: false
                  prediction_optimization:
                    description:
                      - Enable predictive optimization for client connectivity.
                      - Improves roaming decisions based on client behavior patterns.
                    type: bool
                    default: false
                  dual_band_neighbor_list:
                    description:
                      - Enable dual-band neighbor list for 802.11k support.
                      - Helps clients make better roaming decisions across bands.
                    type: bool
                    default: false
                  radius_nac_state:
                    description:
                      - Enable RADIUS Network Access Control (NAC) state tracking.
                      - Provides enhanced security and compliance monitoring.
                    type: bool
                    default: false
                  dhcp_required:
                    description:
                      - Require DHCP for client IP address assignment.
                      - Prevents clients from using static IP addresses.
                    type: bool
                    default: false
                  dhcp_server:
                    description:
                      - IP address of the DHCP server for client assignments.
                      - Must be reachable from the wireless infrastructure.
                    type: str
                  flex_local_auth:
                    description:
                      - Enable local authentication for FlexConnect deployments.
                      - Allows authentication when WAN connectivity is unavailable.
                    type: bool
                    default: false
                  target_wakeup_time:
                    description:
                      - Enable Target Wake Time (TWT) for power management.
                      - Reduces power consumption for Wi-Fi 6 capable devices.
                    type: bool
                    default: false
                  downlink_ofdma:
                    description:
                      - Enable downlink Orthogonal Frequency Division Multiple Access.
                      - Improves efficiency in dense client environments (Wi-Fi 6).
                    type: bool
                    default: false
                  uplink_ofdma:
                    description:
                      - Enable uplink OFDMA for improved upload performance.
                      - Reduces latency and improves spectral efficiency (Wi-Fi 6).
                    type: bool
                    default: false
                  downlink_mu_mimo:
                    description:
                      - Enable downlink Multi-User Multiple-Input Multiple-Output.
                      - Allows simultaneous transmission to multiple clients.
                    type: bool
                    default: false
                  uplink_mu_mimo:
                    description:
                      - Enable uplink MU-MIMO for improved upload capacity.
                      - Multiple clients can transmit simultaneously.
                    type: bool
                    default: false
                  dot11ax:
                    description:
                      - Enable 802.11ax (Wi-Fi 6) features and optimizations.
                      - Activates Wi-Fi 6 specific enhancements.
                    type: bool
                    default: false
                  aironet_ie_support:
                    description:
                      - Enable Cisco Aironet Information Element support.
                      - Provides enhanced Cisco-specific wireless features.
                    type: bool
                    default: false
                  load_balancing:
                    description:
                      - Enable client load balancing across access points.
                      - Distributes clients evenly for optimal performance.
                    type: bool
                    default: false
                  dtim_period_5ghz:
                    description:
                      - Delivery Traffic Indication Message period for 5GHz band.
                      - Controls power save behavior for multicast/broadcast traffic.
                      - Range typically 1-255 beacon intervals.
                    type: int
                  dtim_period_24ghz:
                    description:
                      - DTIM period for 2.4GHz band operations.
                      - Lower values provide better responsiveness but higher power consumption.
                    type: int
                  scan_defer_time:
                    description:
                      - Time in milliseconds to defer scanning operations.
                      - Helps reduce interference during active data transmission.
                    type: int
                  max_clients:
                    description:
                      - Maximum number of clients allowed on this SSID.
                      - Helps control resource utilization and performance.
                    type: int
                  max_clients_per_radio:
                    description:
                      - Maximum clients allowed per radio interface.
                      - Provides per-radio client density control.
                    type: int
                  max_clients_per_ap:
                    description:
                      - Maximum clients allowed per access point.
                      - Overall client limit regardless of radio configuration.
                    type: int
                  wmm_policy:
                    description:
                      - Wi-Fi Multimedia (WMM) quality of service policy.
                      - DISABLED turns off QoS prioritization.
                      - ALLOWED permits QoS-capable clients to use prioritization.
                      - REQUIRED mandates QoS support for client connectivity.
                    type: str
                    choices: ["DISABLED", "ALLOWED", "REQUIRED"]
                  multicast_buffer:
                    description:
                      - Enable multicast frame buffering for power-save clients.
                      - Improves delivery of multicast traffic to sleeping clients.
                    type: bool
                    default: false
                  multicast_buffer_value:
                    description:
                      - Size of multicast buffer in number of frames.
                      - Higher values improve delivery but use more memory.
                    type: int
                  media_stream_multicast_direct:
                    description:
                      - Enable direct multicast delivery for media streams.
                      - Optimizes video and audio multicast performance.
                    type: bool
                    default: false
                  mu_mimo_11ac:
                    description:
                      - Enable MU-MIMO support for 802.11ac clients.
                      - Allows multiple 11ac clients to be served simultaneously.
                    type: bool
                    default: false
                  wifi_to_cellular_steering:
                    description:
                      - Enable steering between Wi-Fi and cellular networks.
                      - Helps optimize connectivity based on network conditions.
                    type: bool
                    default: false
                  wifi_alliance_agile_multiband:
                    description:
                      - Enable Wi-Fi Alliance Agile Multiband features.
                      - Improves band steering and roaming performance.
                    type: bool
                    default: false
                  fastlane_asr:
                    description:
                      - Enable FastLane Automatic Speech Recognition optimization.
                      - Prioritizes voice traffic for better call quality.
                    type: bool
                    default: false
                  dot11v_bss_max_idle_protected:
                    description:
                      - Enable 802.11v BSS Max Idle Period protection.
                      - Prevents premature client disconnection during idle periods.
                    type: bool
                    default: false
                  universal_ap_admin:
                    description:
                      - Enable Universal AP Admin access capabilities.
                      - Provides enhanced management access to access points.
                    type: bool
                    default: false
                  opportunistic_key_caching:
                    description:
                      - Enable Opportunistic Key Caching for faster roaming.
                      - Reduces authentication time during client roaming.
                    type: bool
                    default: false
                  ip_source_guard:
                    description:
                      - Enable IP Source Guard to prevent IP spoofing.
                      - Validates source IP addresses against DHCP bindings.
                    type: bool
                    default: false
                  dhcp_opt82_remote_id_sub_option:
                    description:
                      - Enable DHCP Option 82 Remote ID sub-option.
                      - Provides location information in DHCP requests.
                    type: bool
                    default: false
                  vlan_central_switching:
                    description:
                      - Enable VLAN-based central switching mode.
                      - Routes traffic through the wireless controller.
                    type: bool
                    default: false
                  call_snooping:
                    description:
                      - Enable call snooping for voice optimization.
                      - Monitors voice calls for quality improvements.
                    type: bool
                    default: false
                  send_disassociate:
                    description:
                      - Send disassociate messages for call management.
                      - Helps manage client connections during voice calls.
                    type: bool
                    default: false
                  sent_486_busy:
                    description:
                      - Send SIP 486 Busy response for call control.
                      - Manages voice call admission control.
                    type: bool
                    default: false
                  ip_mac_binding:
                    description:
                      - Enable IP-MAC address binding for security.
                      - Prevents MAC address spoofing attacks.
                    type: bool
                    default: false
                  idle_threshold:
                    description:
                      - Idle timeout threshold in seconds for client management.
                      - Disconnects inactive clients to free resources.
                    type: int
                  defer_priority_0:
                    description:
                      - Enable traffic deferral for priority 0 (background) traffic.
                      - Delays low-priority traffic during congestion.
                    type: bool
                    default: false
                  defer_priority_1:
                    description:
                      - Enable traffic deferral for priority 1 (best effort) traffic.
                    type: bool
                    default: false
                  defer_priority_2:
                    description:
                      - Enable traffic deferral for priority 2 (excellent effort) traffic.
                    type: bool
                    default: false
                  defer_priority_3:
                    description:
                      - Enable traffic deferral for priority 3 (critical applications) traffic.
                    type: bool
                    default: false
                  defer_priority_4:
                    description:
                      - Enable traffic deferral for priority 4 (video) traffic.
                    type: bool
                    default: false
                  defer_priority_5:
                    description:
                      - Enable traffic deferral for priority 5 (video) traffic.
                    type: bool
                    default: false
                  defer_priority_6:
                    description:
                      - Enable traffic deferral for priority 6 (voice) traffic.
                    type: bool
                    default: false
                  defer_priority_7:
                    description:
                      - Enable traffic deferral for priority 7 (network control) traffic.
                    type: bool
                    default: false
                  share_data_with_client:
                    description:
                      - Enable sharing of network analytics data with clients.
                      - Provides performance insights to client applications.
                    type: bool
                    default: false
                  advertise_support:
                    description:
                      - Advertise support for advanced wireless features.
                      - Informs clients about available capabilities.
                    type: bool
                    default: false
                  advertise_pc_analytics_support:
                    description:
                      - Advertise support for PC analytics capabilities.
                      - Enables enhanced analytics for computer clients.
                    type: bool
                    default: false
                  send_beacon_on_association:
                    description:
                      - Send beacon frames immediately upon client association.
                      - Improves initial connectivity experience.
                    type: bool
                    default: false
                  send_beacon_on_roam:
                    description:
                      - Send beacon frames when clients roam between APs.
                      - Facilitates faster roaming completion.
                    type: bool
                    default: false
                  fast_transition_reassociation_timeout:
                    description:
                      - Timeout in milliseconds for fast transition reassociation.
                      - Controls how long to wait for roaming completion.
                    type: int
                  mdns_mode:
                    description:
                      - Multicast DNS (mDNS) handling mode for service discovery.
                      - MDNS_SD_BRIDGING bridges mDNS across network segments.
                      - MDNS_SD_DROP blocks mDNS traffic.
                      - MDNS_SD_GATEWAY provides mDNS gateway functionality.
                    type: str
                    choices: ["MDNS_SD_BRIDGING", "MDNS_SD_DROP", "MDNS_SD_GATEWAY"]
              unlocked_attributes:
                description:
                  - List of attribute names that should be unlocked for manual configuration.
                  - Attributes not in this list will be managed by the template.
                  - Allows selective override of template-managed settings.
                type: list
                elements: str
                required: false
          clean_air_configuration:
            description:
              - Configuration for Cisco CleanAir spectrum intelligence and interference detection.
              - Provides automatic RF interference detection and mitigation.
              - Helps maintain optimal wireless performance in challenging RF environments.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply CleanAir configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                type: str
                required: true
              feature_attributes:
                description:
                  - CleanAir feature settings and interference detection parameters.
                  - Controls spectrum monitoring and interference reporting behavior.
                type: dict
                required: false
                suboptions:
                  radio_band:
                    description:
                      - Radio frequency band for CleanAir monitoring and interference detection.
                      - 2_4GHZ monitors 2.4 GHz spectrum for interference sources.
                      - 5GHZ monitors 5 GHz spectrum for interference sources.
                      - 6GHZ monitors 6 GHz spectrum for interference sources.
                    type: str
                    required: true
                    choices: ["2_4GHZ", "5GHZ", "6GHZ"]
                  clean_air:
                    description:
                      - Enable CleanAir spectrum intelligence functionality.
                      - Activates automatic interference detection and reporting.
                    type: bool
                    default: False
                  clean_air_device_reporting:
                    description:
                      - Enable reporting of detected interference devices.
                      - Provides detailed information about interference sources.
                    type: bool
                    default: False
                  persistent_device_propagation:
                    description:
                      - Enable propagation of persistent interference device information.
                      - Shares interference data across the wireless infrastructure.
                    type: bool
                    default: False
                  description:
                    description:
                      - Optional description for the CleanAir configuration.
                      - Provides context about the monitoring setup and purpose.
                    type: str
                  interferers_features:
                    description:
                      - Specific interference source detection and classification settings.
                      - Controls which types of interference sources are monitored.
                      - "IMPORTANT - Band Compatibility Requirements:"
                      - "6GHz band - Supports only continuous_transmitter"
                      - "5GHz band - Supports these interferers:"
                      - "  * continuous_transmitter, generic_dect, generic_tdd, jammer"
                      - "  * motorola_canopy, si_fhss, spectrum80211_non_standard_channel"
                      - "  * spectrum_inverted, super_ag, video_camera, wimax_fixed, wimax_mobile"
                      - "2.4GHz band - Supports all interferers:"
                      - "  * ble_beacon, bluetooth_paging_inquiry, bluetooth_sco_acl"
                      - "  * continuous_transmitter, generic_dect, generic_tdd, jammer"
                      - "  * microwave_oven, motorola_canopy, si_fhss, spectrum80211_fh"
                      - "  * spectrum80211_non_standard_channel, spectrum802154"
                      - "  * spectrum_inverted, super_ag, video_camera"
                      - "  * wimax_fixed, wimax_mobile, xbox"
                      - "The module will validate and reject invalid interferer/band combinations."
                    type: dict
                    required: false
                    suboptions:
                      ble_beacon:
                        description:
                          - Enable detection of Bluetooth Low Energy beacon interference.
                        type: bool
                        default: False
                      bluetooth_paging_inquiry:
                        description:
                          - Enable detection of Bluetooth paging and inquiry interference.
                        type: bool
                        default: False
                      bluetooth_sco_acl:
                        description:
                          - Enable detection of Bluetooth SCO/ACL traffic interference.
                        type: bool
                        default: False
                      continuous_transmitter:
                        description:
                          - Enable detection of continuous transmitter interference.
                        type: bool
                        default: False
                      generic_dect:
                        description:
                          - Enable detection of generic DECT phone interference.
                        type: bool
                        default: False
                      generic_tdd:
                        description:
                          - Enable detection of generic Time Division Duplex interference.
                        type: bool
                        default: False
                      jammer:
                        description:
                          - Enable detection of intentional jamming devices.
                        type: bool
                        default: False
                      microwave_oven:
                        description:
                          - Enable detection of microwave oven interference.
                        type: bool
                        default: False
                      motorola_canopy:
                        description:
                          - Enable detection of Motorola Canopy interference.
                        type: bool
                        default: False
                      si_fhss:
                        description:
                          - Enable detection of Frequency Hopping Spread Spectrum interference.
                        type: bool
                        default: False
                      spectrum80211_fh:
                        description:
                          - Enable detection of 802.11 frequency hopping interference.
                        type: bool
                        default: False
                      spectrum80211_non_standard_channel:
                        description:
                          - Enable detection of 802.11 non-standard channel interference.
                        type: bool
                        default: False
                      spectrum802154:
                        description:
                          - Enable detection of 802.15.4 (ZigBee) interference.
                        type: bool
                        default: False
                      spectrum_inverted:
                        description:
                          - Enable detection of spectrum inverted interference.
                        type: bool
                        default: False
                      super_ag:
                        description:
                          - Enable detection of Super AG interference.
                        type: bool
                        default: False
                      video_camera:
                        description:
                          - Enable detection of wireless video camera interference.
                        type: bool
                        default: False
                      wimax_fixed:
                        description:
                          - Enable detection of fixed WiMAX interference.
                        type: bool
                        default: False
                      wimax_mobile:
                        description:
                          - Enable detection of mobile WiMAX interference.
                        type: bool
                        default: False
                      xbox:
                        description:
                          - Enable detection of Xbox gaming console interference.
                        type: bool
                        default: False
              unlocked_attributes:
                description:
                  - List of CleanAir attribute names unlocked for manual configuration.
                  - Allows override of template-managed interference detection settings.
                type: list
                elements: str
                required: false
          dot11ax_configuration:
            description:
              - Configuration for 802.11ax (Wi-Fi 6) specific features and optimizations.
              - Enables advanced Wi-Fi 6 capabilities for improved performance and efficiency.
              - Includes settings for spatial reuse, BSS coloring, and target wake time.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply 802.11ax configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                type: str
                required: true
              feature_attributes:
                description:
                  - 802.11ax specific feature settings and optimization parameters.
                  - Controls Wi-Fi 6 enhancements for better spectral efficiency.
                type: dict
                required: false
                suboptions:
                  radio_band:
                    description:
                      - Radio frequency band for 802.11ax feature application.
                      - Supported values are 2_4GHZ, 5GHZ, 6GHZ.
                      - 6GHZ is supported only on Cisco IOS-XE based Wireless Controllers running 17.7.1 and above.
                    type: str
                  bss_color:
                    description:
                      - Enable BSS (Basic Service Set) coloring for spatial reuse.
                      - Helps distinguish between overlapping BSSs to reduce interference.
                      - Supported on Cisco IOS-XE based Wireless Controllers running 17.1 and above.
                      - Supported radio bands are 2_4GHZ, 5GHZ, 6GHZ.
                    type: bool
                    default: False
                  target_waketime_broadcast:
                    description:
                      - Enable broadcast Target Wake Time (TWT) announcements.
                      - Coordinates sleep schedules for multiple clients simultaneously.
                      - Supported on Cisco IOS-XE based Wireless Controllers running 17.3.1 and above.
                      - Supported radio bands are 2_4GHZ, 5GHZ, 6GHZ.
                    type: bool
                    default: False
                  non_srg_obss_pd_max_threshold:
                    description:
                      - Maximum threshold for non-SRG OBSS Packet Detection in dBm.
                      - Controls sensitivity for detecting overlapping BSS transmissions.
                      - Valid range is between -82 to -62 dBm.
                      - Supported on Cisco IOS-XE based Wireless Controllers running 17.4 and above.
                      - Supported radio bands are 2_4GHZ and 5GHZ only.
                      - Not supported for 6GHZ.
                    type: int
                  target_wakeup_time_11ax:
                    description:
                      - Enable Target Wake Time feature for 802.11ax clients.
                      - Allows clients to negotiate sleep schedules to save power.
                      - Supported on Cisco IOS-XE based Wireless Controllers running 17.1 and above.
                      - Supported radio bands are 2_4GHZ, 5GHZ, 6GHZ.
                    type: bool
                    default: False
                  obss_pd:
                    description:
                      - Enable Overlapping BSS Packet Detection for spatial reuse.
                      - Improves spectrum efficiency in dense deployments.
                      - Supported on Cisco IOS-XE based Wireless Controllers running 17.4 and above.
                      - Supported radio bands are 2_4GHZ and 5GHZ only.
                      - Not supported for 6GHZ.
                    type: bool
                    default: False
                  multiple_bssid:
                    description:
                      - Enable Multiple BSSID feature for 802.11ax.
                      - Allows transmission of multiple SSID beacons efficiently.
                      - Supported on Cisco IOS-XE based Wireless Controllers running 17.7.1 and above.
                      - Supported radio bands are 6GHZ only.
                    type: bool
                    default: False
              unlocked_attributes:
                description:
                  - List of 802.11ax attribute names unlocked for manual configuration.
                  - Allows selective override of Wi-Fi 6 template settings.
                  - Only attributes defined under feature_attributes are allowed.
                type: list
                elements: str
                required: false
          dot11be_configuration:
            description:
              - Configuration for 802.11be (Wi-Fi 7) features and capabilities.
              - Enables next-generation Wi-Fi 7 enhancements for maximum performance.
              - Provides settings for Wi-Fi 7 operation on different frequency bands.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply 802.11be configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                type: str
                required: true
              feature_attributes:
                description:
                  - 802.11be specific feature settings and optimization parameters.
                  - Controls Wi-Fi 7 operation and performance enhancements.
                type: dict
                required: false
                suboptions:
                  dot11be_status:
                    description:
                      - Enable or disable 802.11be (Wi-Fi 7) operation.
                      - Activates Wi-Fi 7 features and protocol enhancements.
                    type: bool
                    default: False
                  radio_band:
                    description:
                      - Radio frequency band for 802.11be operation.
                      - Specifies which band should support Wi-Fi 7 features.
                      - 6GHz band provides optimal Wi-Fi 7 performance.
                    type: str
                    choices: ["2_4GHZ", "5GHZ", "6GHZ"]
              unlocked_attributes:
                description:
                  - List of 802.11be attribute names unlocked for manual configuration.
                  - Allows override of Wi-Fi 7 template-managed settings.
                type: list
                elements: str
                required: false
          event_driven_rrm_configuration:
            description:
              - Configuration for Event-Driven Radio Resource Management (RRM).
              - Enables dynamic RF optimization based on real-time network conditions.
              - Provides automatic channel and power adjustments for optimal performance.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply Event-Driven RRM configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                type: str
                required: true
              feature_attributes:
                description:
                  - Event-Driven RRM feature settings and threshold parameters.
                  - Controls automatic RF optimization behavior and sensitivity.
                type: dict
                required: true
                suboptions:
                  radio_band:
                    description:
                      - Radio frequency band for Event-Driven RRM operation.
                      - RRM algorithms will monitor and optimize this band.
                      - Supported values are 2_4GHZ and 5GHZ only.
                      - Note - 6 GHz band is not supported for Event-Driven RRM.
                    type: str
                    required: true
                    choices: ["2_4GHZ", "5GHZ"]
                  event_driven_rrm_enable:
                    description:
                      - Enable Event-Driven RRM for automatic RF optimization.
                      - When enabled, system responds to interference and performance changes.
                    type: bool
                    default: False
                    required: false
                  event_driven_rrm_threshold_level:
                    description:
                      - Sensitivity level for triggering Event-Driven RRM actions.
                      - LOW requires significant changes before RRM activation.
                      - MEDIUM provides balanced sensitivity to network changes.
                      - HIGH triggers RRM with minor network condition changes.
                      - CUSTOM allows specification of custom threshold values.
                    type: str
                    required: false
                    choices: ["LOW", "MEDIUM", "HIGH", "CUSTOM"]
                  event_driven_rrm_custom_threshold_val:
                    description:
                      - Custom threshold value when event_driven_rrm_threshold_level is set to CUSTOM.
                      - Only valid when event_driven_rrm_threshold_level is set to CUSTOM.
                      - Will be ignored for LOW/MEDIUM/HIGH threshold levels.
                      - Defines the specific sensitivity level for RRM activation.
                      - Higher values require more significant changes to trigger RRM.
                    type: int
                    required: false
              unlocked_attributes:
                description:
                  - List of Event-Driven RRM attribute names unlocked for manual configuration.
                  - Allows override of template-managed RRM settings.
                type: list
                elements: str
                required: false
          flexconnect_configuration:
            description:
              - Configuration for FlexConnect deployment and local switching capabilities.
              - Enables branch office wireless deployments with local data switching.
              - Provides settings for overlay IP functionality and local survivability.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply FlexConnect configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                type: str
                required: true
              feature_attributes:
                description:
                  - FlexConnect feature settings and operational parameters.
                  - Controls local switching and overlay IP capabilities.
                type: dict
                required: false
                suboptions:
                  overlap_ip_enable:
                    description:
                      - Enable overlapping IP address support for FlexConnect.
                      - Allows multiple sites to use the same IP address ranges.
                      - Useful for branch deployments with NAT or VPN connectivity.
                    type: bool
                    default: False
              unlocked_attributes:
                description:
                  - List of FlexConnect attribute names unlocked for manual configuration.
                  - Allows override of template-managed FlexConnect settings.
                type: list
                elements: str
                required: false
          multicast_configuration:
            description:
              - Configuration for wireless multicast traffic handling and optimization.
              - Controls how multicast and broadcast traffic is processed and delivered.
              - Includes settings for both IPv4 and IPv6 multicast operations.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply multicast configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                  - Design name must not exceed 64 characters in length.
                type: str
                required: true
              feature_attributes:
                description:
                  - Multicast feature settings and delivery parameters.
                  - Controls global multicast behavior and protocol-specific settings.
                type: dict
                required: true
                suboptions:
                  global_multicast_enabled:
                    description:
                      - Enable global multicast functionality for the wireless network.
                      - Must be enabled for any multicast traffic processing.
                    type: bool
                    required: true
                  multicast_ipv4_mode:
                    description:
                      - Delivery mode for IPv4 multicast traffic handling.
                      - UNICAST converts multicast to unicast for individual delivery.
                      - MULTICAST preserves native multicast delivery method.
                    type: str
                    required: false
                    choices: ["UNICAST", "MULTICAST"]
                  multicast_ipv4_address:
                    description:
                      - IPv4 multicast group address for multicast operations.
                      - Must be a valid IPv4 multicast address (224.0.0.0 to 239.255.255.255).
                      - Used when multicast_ipv4_mode is set to MULTICAST.
                    type: str
                    required: false
                  multicast_ipv6_mode:
                    description:
                      - Delivery mode for IPv6 multicast traffic handling.
                      - UNICAST converts multicast to unicast for individual delivery.
                      - MULTICAST preserves native multicast delivery method.
                    type: str
                    required: false
                    choices: ["UNICAST", "MULTICAST"]
                  multicast_ipv6_address:
                    description:
                      - IPv6 multicast group address for multicast operations.
                      - Must be a valid IPv6 multicast address (FF00::/8 prefix).
                      - Used when multicast_ipv6_mode is set to MULTICAST.
                    type: str
                    required: false
              unlocked_attributes:
                description:
                  - List of multicast attribute names unlocked for manual configuration.
                  - Allows override of template-managed multicast settings.
                type: list
                elements: str
                required: false
          rrm_fra_configuration:
            description:
              - Configuration for Radio Resource Management Flexible Radio Assignment (RRM-FRA).
              - Enables dynamic radio role assignment for optimal spectrum utilization.
              - Provides automatic adaptation to changing RF conditions and client demands.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply RRM-FRA configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                  - Design name must not exceed 64 characters in length.
                type: str
                required: true
              feature_attributes:
                description:
                  - RRM-FRA feature settings and operational parameters.
                  - Controls flexible radio assignment behavior and sensitivity.
                type: dict
                required: true
                suboptions:
                  radio_band:
                    description:
                      - Radio band combination for FRA operation.
                      - 2_4GHZ_5GHZ enables FRA between 2.4GHz and 5GHz bands.
                      - 5GHZ_6GHZ enables FRA between 5GHz and 6GHz bands.
                    type: str
                    required: true
                    choices: ["2_4GHZ_5GHZ", "5GHZ_6GHZ"]
                  fra_freeze:
                    description:
                      - Freeze FRA decisions to prevent automatic changes.
                      - When enabled, current radio assignments are maintained.
                    type: bool
                    default: false
                    required: false
                  fra_status:
                    description:
                      - Enable or disable Flexible Radio Assignment functionality.
                      - Controls whether radios can dynamically change their band assignment.
                    type: bool
                    default: false
                    required: false
                  fra_interval:
                    description:
                      - Interval in hours for FRA evaluation and potential changes.
                      - Determines how frequently the system considers radio reassignment.
                      - Value range is typically 1 to 24 hours.
                      - Units are in hours.
                    type: int
                    required: false
                  fra_sensitivity:
                    description:
                      - Sensitivity level for triggering FRA band changes.
                      - LOW requires significant load differences for reassignment.
                      - MEDIUM provides balanced sensitivity to load changes.
                      - HIGH responds quickly to minor load variations.
                      - HIGHER, EVEN_HIGHER, SUPER_HIGH provide progressively more sensitive responses.
                    type: str
                    required: false
                    choices: ["LOW", "MEDIUM", "HIGH", "HIGHER", "EVEN_HIGHER", "SUPER_HIGH"]
              unlocked_attributes:
                description:
                  - List of RRM-FRA attribute names unlocked for manual configuration.
                  - Allows override of template-managed FRA settings.
                type: list
                elements: str
                required: false
          rrm_general_configuration:
            description:
              - Configuration for general Radio Resource Management (RRM) parameters.
              - Controls overall RF optimization behavior and monitoring settings.
              - Includes channel monitoring, neighbor discovery, and coverage optimization.
            type: list
            elements: dict
            required: false
            suboptions:
              design_name:
                description:
                  - Name of the wireless design to apply RRM general configuration.
                  - Must correspond to an existing design in Cisco Catalyst Center.
                  - Design name must not exceed 64 characters in length.
                type: str
                required: true
              feature_attributes:
                description:
                  - General RRM feature settings and optimization parameters.
                  - Controls channel monitoring, neighbor discovery, and performance thresholds.
                type: dict
                required: true
                suboptions:
                  radio_band:
                    description:
                      - Radio frequency band for RRM general configuration.
                      - RRM algorithms will monitor and optimize the specified band.
                    type: str
                    required: true
                    choices: ["2_4GHZ", "5GHZ", "6GHZ"]
                  monitoring_channels:
                    description:
                      - Channel monitoring scope for RRM analysis and optimization.
                      - MONITORING_CHANNELS_ALL monitors all available channels.
                      - MONITORING_CHANNELS_COUNTRY monitors country-specific channels.
                      - MONITORING_CHANNELS_DCA monitors Dynamic Channel Assignment channels.
                    type: str
                    required: false
                    choices: [
                      "MONITORING_CHANNELS_ALL",
                      "MONITORING_CHANNELS_COUNTRY",
                      "MONITORING_CHANNELS_DCA"
                    ]
                  neighbor_discover_type:
                    description:
                      - Method for discovering neighboring access points and interference.
                      - NEIGHBOR_DISCOVER_TYPE_TRANSPARENT uses passive monitoring.
                      - NEIGHBOR_DISCOVER_TYPE_PROTECTED uses active discovery with protection.
                    type: str
                    required: false
                    choices: [
                      "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT",
                      "NEIGHBOR_DISCOVER_TYPE_PROTECTED"
                    ]
                  throughput_threshold:
                    description:
                      - Minimum throughput threshold in Mbps for RRM optimization triggers.
                      - RRM considers optimization when throughput falls below this value.
                    type: int
                    required: false
                  coverage_hole_detection:
                    description:
                      - Enable automatic detection and mitigation of coverage holes.
                      - Identifies areas with poor signal coverage and attempts optimization.
                    type: bool
                    required: false
                    default: false
              unlocked_attributes:
                description:
                  - List of RRM general attribute names unlocked for manual configuration.
                  - Allows override of template-managed RRM settings.
                type: list
                elements: str
                required: false
      802_11_be_profiles:
        description:
          - Configuration for 802.11be (Wi-Fi 7) profile settings.
          - Enables advanced Wi-Fi 7 features for improved performance and efficiency.
          - Controls OFDMA and MU-MIMO settings for both downlink and uplink transmission.
        type: list
        elements: dict
        required: false
        suboptions:
          profile_name:
            description:
              - Name of the 802.11be profile.
              - Must correspond to an existing design in Cisco Catalyst Center.
              - Profile name must not exceed 64 characters in length.
              - profile name cannot be updated once created.
            type: str
            required: true
          ofdma_up_link:
            description:
              - Enable or disable OFDMA for uplink transmission.
              - When enabled, multiple clients can transmit simultaneously on different subcarriers.
              - Reduces latency and improves efficiency for uplink traffic.
              - Particularly beneficial for applications requiring low latency and high reliability.
            type: bool
            default: true
            required: false
          ofdma_down_link:
            description:
              - Enable or disable OFDMA (Orthogonal Frequency Division Multiple Access) for downlink transmission.
              - When enabled, allows multiple clients to be served simultaneously on different subcarriers.
              - Improves spectral efficiency and network capacity in high-density environments.
              - OFDMA downlink is a Wi-Fi 7 feature that enhances performance for multiple concurrent users.
            type: bool
            default: true
            required: false
          mu_mimo_up_link:
            description:
              - Enable or disable MU-MIMO for uplink transmission.
              - When enabled, multiple clients can transmit to the access point simultaneously.
              - Improves upload capacity and reduces contention in high-density environments.
              - Enhances performance for applications with heavy uplink traffic requirements.
            type: bool
            default: false
            required: false
          mu_mimo_down_link:
            description:
              - Enable or disable Multi-User Multiple-Input Multiple-Output for downlink transmission.
              - When enabled, allows simultaneous transmission to multiple clients.
              - Increases overall network throughput and spectral efficiency.
              - MU-MIMO downlink works in conjunction with OFDMA for optimal Wi-Fi 7 performance.
            type: bool
            default: false
            required: false
          ofdma_multi_ru:
            description:
              - Enable or disable OFDMA Multi-RU (Resource Unit) allocation.
              - Multi-RU allows a single client to use multiple non-contiguous resource units.
              - Provides more flexible spectrum utilization and improved throughput for individual clients.
              - This is an advanced Wi-Fi 7 feature that optimizes resource allocation dynamically.
              - When enabled, clients can benefit from aggregated bandwidth across multiple RUs.
            type: bool
            default: false
            required: false
      flex_connect_configuration:
        description:
          - Configuration for FlexConnect Native VLAN settings at site level.
          - Allows updating the Native VLAN ID for FlexConnect-enabled access points.
          - FlexConnect configurations can only be UPDATED or DELETED, not created.
          - The Native VLAN setting controls which VLAN is used for untagged traffic on FlexConnect APs.
          - Changes apply to the specified site and optionally to child sites in the hierarchy.
        type: list
        elements: dict
        required: false
        suboptions:
          site_name_hierarchy:
            description:
              - Full hierarchical path of the site where FlexConnect Native VLAN should be configured.
              - Must be an existing site in Cisco Catalyst Center (e.g., "Global/USA/San Jose/Building1").
              - The site must have FlexConnect capability enabled.
              - FlexConnect settings are inherited from parent sites if not overridden.
              - Required for both update and delete operations.
            type: str
            required: true
          vlan_id:
            description:
              - Native VLAN ID to be configured for FlexConnect at the specified site.
              - Must be a valid VLAN ID between 1 and 4094.
              - This VLAN is used for untagged traffic on FlexConnect access points.
              - Changes to this value update the existing FlexConnect configuration.
              - Required for update operations only (not needed for delete).
            type: int
            required: false
          remove_override_in_hierarchy:
            description:
              - Controls whether to remove FlexConnect overrides from child sites during deletion.
              - When set to true, removes the Native VLAN override from the specified site AND all child sites.
              - When set to false, only removes the override from the specified site, leaving child site overrides intact.
              - Applies only to delete operations (state=deleted).
              - If not specified, defaults to true to maintain consistency across the site hierarchy.
              - Has no effect during update operations (state=merged).
            type: bool
            default: true
            required: false

requirements:
  - dnacentersdk >= 2.10.3
  - python >= 3.9
notes:
  - SDK Methods used are - sites.Sites.get_site - site_design.SiteDesigns.get_sites
    - wirelesss.Wireless.create_ssid - wirelesss.Wireless.update_ssid
    - wirelesss.Wireless.update_or_overridessid - wirelesss.Wireless.delete_ssid
    - wirelesss.Wireless.get_interfaces - wirelesss.Wireless.create_interface
    - wirelesss.Wireless.update_interface - wirelesss.Wireless.delete_interface
    - wirelesss.Wireless.get_power_profiles - wirelesss.Wireless.create_power_profile
    - wirelesss.Wireless.update_power_profile_by_id
    - wirelesss.Wireless.delete_power_profile_by_id
    - wirelesss.Wireless.get_ap_profiles - wirelesss.Wireless.create_ap_profile
    - wirelesss.Wireless.update_ap_profile_by_id - wirelesss.Wireless.delete_ap_profile_by_id
    - wirelesss.Wireless.get_rf_profiles - wirelesss.Wireless.create_rf_profile
    - wirelesss.Wireless.update_rf_profile - wirelesss.Wireless.delete_rf_profile
    - wirelesss.Wireless.get_anchor_groups - wirelesss.Wireless.create_anchor_group
    - wirelesss.Wireless.update_anchor_group - wirelesss.Wireless.delete_anchor_group_by_id
  - Paths used are
    - GET /dna/intent/api/v1/sites -
    GET /dna/intent/api/v1/sites/${siteId}/wirelessSettings/ssids
    - POST /dna/intent/api/v1/sites/${siteId}/wirelessSettings/ssids
    - PUT /dna/intent/api/v1/sites/${siteId}/wirelessSettings/ssids/${id}
    - POST /dna/intent/api/v1/sites/${siteId}/wirelessSettings/ssids/${id}/update
    - DELETE /dna/intent/api/v1/sites/${siteId}/wirelessSettings/ssids/${id}
    - GET /dna/intent/api/v1/wirelessSettings/interfaces
    - POST /dna/intent/api/v1/wirelessSettings/interfaces
    - PUT /dna/intent/api/v1/wirelessSettings/interfaces/${id}
    - DELETE /dna/intent/api/v1/wirelessSettings/interfaces/${id}
    - GET /dna/intent/api/v1/wirelessSettings/powerProfiles
    - POST /dna/intent/api/v1/wirelessSettings/powerProfiles
    - PUT /dna/intent/api/v1/wirelessSettings/powerProfiles/${id}
    - DELETE /dna/intent/api/v1/wirelessSettings/powerProfiles/${id}
    - GET /dna/intent/api/v1/wirelessSettings/apProfiles
    - POST /dna/intent/api/v1/wirelessSettings/apProfiles
    - PUT /dna/intent/api/v1/wirelessSettings/apProfiles/${id}
    - DELETE /dna/intent/api/v1/wirelessSettings/apProfiles/${id}
    - GET /dna/intent/api/v1/wirelessSettings/rfProfiles
    - POST /dna/intent/api/v1/wirelessSettings/rfProfiles
    - PUT /dna/intent/api/v1/wirelessSettings/rfProfiles/${id}
    - DELETE /dna/intent/api/v1/wirelessSettings/rfProfiles/${id}
    - GET /dna/intent/api/v1/wirelessSettings/anchorGroups
    - POST /dna/intent/api/v1/wirelessSettings/anchorGroups
    - PUT /dna/intent/api/v1/wirelessSettings/anchorGroups/${id}
    - DELETE /dna/intent/api/v1/wirelessSettings/anchorGroups/${id}
"""

EXAMPLES = r"""
---
- name: Add dot11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11be_configuration:
              - design_name: "dot11be_24ghz_design"
                feature_attributes:
                  dot11be_status: true
                  radio_band: "2_4GHZ"
                unlocked_attributes:
                  - "dot11be_status"
                  - "radio_band"
- name: Update dot11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11be_configuration:
              - design_name: "dot11be_24ghz_design"
                feature_attributes:
                  dot11be_status: false
                  radio_band: "2_4GHZ"
                unlocked_attributes:
                  - "dot11be_status"
                  - "radio_band"
- name: Delete dot11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - dot11be_configuration:
              - design_name: "dot11be_24ghz_design"
- name: Add dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_24ghz_design"
                feature_attributes:
                  radio_band: "2_4GHZ"
                  bss_color: true
                  target_waketime_broadcast: true
                  non_srg_obss_pd_max_threshold: -78
                  target_wakeup_time_11ax: true
                  obss_pd: true
                unlocked_attributes:
                  - "radio_band"
                  - "bss_color"
                  - "target_waketime_broadcast"
                  - "non_srg_obss_pd_max_threshold"
                  - "target_wakeup_time_11ax"
                  - "obss_pd"
- name: Update dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_24ghz_design"
                feature_attributes:
                  radio_band: "2_4GHZ"
                  bss_color: true
                  target_waketime_broadcast: true
                  non_srg_obss_pd_max_threshold: -78
                  target_wakeup_time_11ax: false
                  obss_pd: true
                unlocked_attributes:
                  - "radio_band"
                  - "bss_color"
                  - "target_waketime_broadcast"
                  - "non_srg_obss_pd_max_threshold"
                  - "target_wakeup_time_11ax"
                  - "obss_pd"
- name: Delete dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_24ghz_design"
- name: Add dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_5ghz_design"
                feature_attributes:
                  radio_band: "5GHZ"
                  bss_color: true
                  target_waketime_broadcast: true
                  non_srg_obss_pd_max_threshold: -75
                  target_wakeup_time_11ax: true
                  obss_pd: true
                unlocked_attributes:
                  - "radio_band"
                  - "bss_color"
                  - "target_waketime_broadcast"
                  - "non_srg_obss_pd_max_threshold"
                  - "target_wakeup_time_11ax"
                  - "obss_pd"
- name: Update dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_5ghz_design"
                feature_attributes:
                  radio_band: "5GHZ"
                  bss_color: false
                  target_waketime_broadcast: false
                  non_srg_obss_pd_max_threshold: -75
                  target_wakeup_time_11ax: true
                  obss_pd: true
                unlocked_attributes:
                  - "radio_band"
                  - "bss_color"
                  - "target_waketime_broadcast"
                  - "non_srg_obss_pd_max_threshold"
                  - "target_wakeup_time_11ax"
                  - "obss_pd"
- name: Delete dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_5ghz_design"

- name: Add dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_6ghz_design"
                feature_attributes:
                  radio_band: "6GHZ"
                  bss_color: true
                  target_waketime_broadcast: true
                  multiple_bssid: true
                  target_wakeup_time_11ax: true
                unlocked_attributes:
                  - "radio_band"
                  - "bss_color"
                  - "target_waketime_broadcast"
                  - "multiple_bssid"
                  - "target_wakeup_time_11ax"
- name: Update dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_6ghz_design"
                feature_attributes:
                  radio_band: "6GHZ"
                  bss_color: true
                  target_waketime_broadcast: false
                  multiple_bssid: true
                  target_wakeup_time_11ax: true
                unlocked_attributes:
                  - "radio_band"
                  - "bss_color"
                  - "target_waketime_broadcast"
                  - "multiple_bssid"
                  - "target_wakeup_time_11ax"
- name: Delete dot11ax profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - dot11ax_configuration:
              - design_name: "dot11ax_6ghz_design"

- name: Add cleanair profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - clean_air_configuration:
              - design_name: "sample_cleanair_design_24ghz"
                feature_attributes:
                  radio_band: 2_4GHZ   # enum: 2_4GHZ, 5GHZ, 6GHZ
                  clean_air: true
                  clean_air_device_reporting: true
                  persistent_device_propagation: false
                  description: "CleanAir profile for 2.4GHz office deployment"
                  # Interferers Features (map)
                  interferers_features:
                    ble_beacon: true                     # Only applicable for 2_4GHZ
                    bluetooth_paging_inquiry: false     # Only applicable for 2_4GHZ
                    bluetooth_sco_acl: false            # Only applicable for 2_4GHZ
                    continuous_transmitter: true       # Applicable for 2_4GHZ, 5GHZ, 6GHZ
                    generic_dect: false                 # 2_4GHZ and 5GHZ
                    generic_tdd: false                  # Only 2_4GHZ
                    jammer: false                       # 2_4GHZ and 5GHZ
                    microwave_oven: true                # Only 2_4GHZ
                    motorola_canopy: false              # 2_4GHZ and 5GHZ
                    si_fhss: false                      # 2_4GHZ and 5GHZ
                    spectrum80211_fh: false             # 2_4GHZ only
                    spectrum80211_non_standard_channel: false  # 2_4GHZ and 5GHZ
                    spectrum802154: false               # 2_4GHZ only
                    spectrum_inverted: false            # 2_4GHZ and 5GHZ
                    super_ag: false                     # 2_4GHZ and 5GHZ
                    video_camera: false                 # 2_4GHZ and 5GHZ
                    wimax_fixed: false                  # 2_4GHZ and 5GHZ
                    wimax_mobile: false                 # 2_4GHZ and 5GHZ
                    xbox: false                         # 2_4GHZ only
                unlocked_attributes:
                  - "clean_air"
                  - "clean_air_device_reporting"
                  - "persistent_device_propagation"
                  - "description"
                  - "interferers_features.ble_beacon"
                  - "interferers_features.continuous_transmitter"
- name: Update cleanair profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - clean_air_configuration:
              - design_name: "sample_cleanair_design_24ghz"
                feature_attributes:
                  radio_band: 2_4GHZ   # enum: 2_4GHZ, 5GHZ, 6GHZ
                  clean_air: true
                  clean_air_device_reporting: true
                  persistent_device_propagation: false
                  description: "CleanAir profile for 2.4GHz office deployment"
                  # Interferers Features (map)
                  interferers_features:
                    ble_beacon: true                     # Only applicable for 2_4GHZ
                    bluetooth_paging_inquiry: false     # Only applicable for 2_4GHZ
                    bluetooth_sco_acl: false            # Only applicable for 2_4GHZ
                    continuous_transmitter: true       # Applicable for 2_4GHZ, 5GHZ, 6GHZ
                    generic_dect: false                 # 2_4GHZ and 5GHZ
                    generic_tdd: false                  # Only 2_4GHZ
                    jammer: false                       # 2_4GHZ and 5GHZ
                    microwave_oven: true                # Only 2_4GHZ
                    motorola_canopy: false              # 2_4GHZ and 5GHZ
                    si_fhss: false                      # 2_4GHZ and 5GHZ
                    spectrum80211_fh: false             # 2_4GHZ only
                    spectrum80211_non_standard_channel: false  # 2_4GHZ and 5GHZ
                    spectrum802154: false               # 2_4GHZ only
                    spectrum_inverted: false            # 2_4GHZ and 5GHZ
                    super_ag: true                     # 2_4GHZ and 5GHZ
                    video_camera: false                 # 2_4GHZ and 5GHZ
                    wimax_fixed: false                  # 2_4GHZ and 5GHZ
                    wimax_mobile: false                 # 2_4GHZ and 5GHZ
                    xbox: false                         # 2_4GHZ only
                unlocked_attributes:
                  - "clean_air"
                  - "clean_air_device_reporting"
                  - "persistent_device_propagation"
                  - "description"
                  - "interferers_features.ble_beacon"
                  - "interferers_features.continuous_transmitter"
- name: Delete cleanair profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - clean_air_configuration:
              - design_name: "sample_cleanair_design_24ghz"

- name: Add event driven rrm profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - event_driven_rrm_configuration:
              - design_name: "edrrm_2_4ghz_design"
                feature_attributes:
                  radio_band: "2_4GHZ"  # 2_4GHZ, 5GHZ
                  event_driven_rrm_enable: true
                  event_driven_rrm_threshold_level: "HIGH"   # LOW, MEDIUM, HIGH, CUSTOM
                  # event_driven_rrm_custom_threshold_val: 50   # must be between 1–99
                unlocked_attributes:
                  - "event_driven_rrm_enable"
                  - "event_driven_rrm_threshold_level"
                  - "event_driven_rrm_custom_threshold_val"
- name: Update event driven rrm profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - event_driven_rrm_configuration:
              - design_name: "edrrm_2_4ghz_design"
                feature_attributes:
                  radio_band: "2_4GHZ"  # 2_4GHZ, 5GHZ
                  event_driven_rrm_enable: false
                  event_driven_rrm_threshold_level: "HIGH"   # LOW, MEDIUM, HIGH, CUSTOM
                  # event_driven_rrm_custom_threshold_val: 50   # must be between 1–99
                unlocked_attributes:
                  - "event_driven_rrm_enable"
                  - "event_driven_rrm_threshold_level"
                  - "event_driven_rrm_custom_threshold_val"
- name: Delete event driven rrm profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - event_driven_rrm_configuration:
              - design_name: "edrrm_2_4ghz_design"

- name: Add multicast profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - multicast_configuration:
              - design_name: "multicast_office_profile_1"
                feature_attributes:
                  global_multicast_enabled: true
                  multicast_ipv4_mode: "MULTICAST"     # UNICAST or MULTICAST
                  multicast_ipv4_address: "239.1.1.25"  # must be in 224.0.0.0 - 239.255.255.255 if mode=MULTICAST
                  multicast_ipv6_mode: "MULTICAST"    # UNICAST or MULTICAST
                  multicast_ipv6_address: "FF05::1"   # must follow FF[0/1][1-5,8,E] rule if mode=MULTICAST
                unlocked_attributes:
                  - "global_multicast_enabled"
                  - "multicast_ipv4_mode"
                  - "multicast_ipv4_address"
                  - "multicast_ipv6_mode"
                  - "multicast_ipv6_address"
- name: Update multicast profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - multicast_configuration:
              - design_name: "multicast_office_profile_1"
                feature_attributes:
                  global_multicast_enabled: false
                  multicast_ipv4_mode: "MULTICAST"     # UNICAST or MULTICAST
                  multicast_ipv4_address: "239.1.1.25"   # must be in 224.0.0.0 - 239.255.255.255 if mode=MULTICAST
                  multicast_ipv6_mode: "MULTICAST"    # UNICAST or MULTICAST
                  multicast_ipv6_address: "FF05::1"   # must follow FF[0/1][1-5,8,E] rule if mode=MULTICAST
                unlocked_attributes:
                  - "global_multicast_enabled"
                  - "multicast_ipv4_mode"
                  - "multicast_ipv4_address"
                  - "multicast_ipv6_mode"
                  - "multicast_ipv6_address"
- name: Delete multicast profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - multicast_configuration:
              - design_name: "multicast_office_profile_1"

- name: Add rrm fra profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_fra_configuration:
              - design_name: "fra_design_1"
                feature_attributes:
                  radio_band: "2_4GHZ_5GHZ"
                  fra_freeze: false
                  fra_status: false
                  fra_interval: 12
                  fra_sensitivity: "HIGH"
                unlocked_attributes:
                  - fra_freeze
                  - fra_status
                  - fra_interval
                  - fra_sensitivity
- name: Update rrm fra profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_fra_configuration:
              - design_name: "fra_design_1"
                feature_attributes:
                  radio_band: "2_4GHZ_5GHZ"
                  fra_freeze: true
                  fra_status: false
                  fra_interval: 12
                  fra_sensitivity: "HIGH"
                unlocked_attributes:
                  - fra_freeze
                  - fra_status
                  - fra_interval
                  - fra_sensitivity
- name: Delete rrm fra profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - rrm_fra_configuration:
              - design_name: "fra_design_1"

- name: Add rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_24ghz_country_scope"
                feature_attributes:
                  radio_band: "2_4GHZ"                          # supported only on IOS-XE >= 17.9.1
                  monitoring_channels: "MONITORING_CHANNELS_COUNTRY"
                  neighbor_discover_type: "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT"
                  throughput_threshold: 150000
                  coverage_hole_detection: true
                unlocked_attributes:
                  - "monitoring_channels"
                  - "coverage_hole_detection"
- name: Update rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_24ghz_country_scope"
                feature_attributes:
                  radio_band: "2_4GHZ"                          # supported only on IOS-XE >= 17.9.1
                  monitoring_channels: "MONITORING_CHANNELS_COUNTRY"
                  neighbor_discover_type: "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT"
                  throughput_threshold: 150000
                  coverage_hole_detection: false
                unlocked_attributes:
                  - "monitoring_channels"
                  - "coverage_hole_detection"
- name: Delete rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_24ghz_country_scope"

- name: Add rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_5ghz_default"
                feature_attributes:
                  radio_band: "5GHZ"                           # enum: 2_4GHZ, 5GHZ, 6GHZ
                  monitoring_channels: "MONITORING_CHANNELS_DCA"   # enum: MONITORING_CHANNELS_ALL, MONITORING_CHANNELS_COUNTRY, MONITORING_CHANNELS_DCA
                  neighbor_discover_type: "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT"  # enum: NEIGHBOR_DISCOVER_TYPE_TRANSPARENT, NEIGHBOR_DISCOVER_TYPE_PROTECTED
                  throughput_threshold: 500000                  # 1000..10000000
                  coverage_hole_detection: true
                unlocked_attributes:
                  - "monitoring_channels"
                  - "neighbor_discover_type"
                  - "throughput_threshold"
                  - "coverage_hole_detection"
- name: Update rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_5ghz_default"
                feature_attributes:
                  radio_band: "5GHZ"                           # enum: 2_4GHZ, 5GHZ, 6GHZ
                  monitoring_channels: "MONITORING_CHANNELS_DCA"   # enum: MONITORING_CHANNELS_ALL, MONITORING_CHANNELS_COUNTRY, MONITORING_CHANNELS_DCA
                  neighbor_discover_type: "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT"  # enum: NEIGHBOR_DISCOVER_TYPE_TRANSPARENT, NEIGHBOR_DISCOVER_TYPE_PROTECTED
                  throughput_threshold: 500000                  # 1000..10000000
                  coverage_hole_detection: false
                unlocked_attributes:
                  - "monitoring_channels"
                  - "neighbor_discover_type"
                  - "throughput_threshold"
                  - "coverage_hole_detection"
- name: Delete rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_5ghz_default"

- name: Add rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_6ghz_high_thr"
                feature_attributes:
                  radio_band: "6GHZ"
                  monitoring_channels: "MONITORING_CHANNELS_ALL"
                  neighbor_discover_type: "NEIGHBOR_DISCOVER_TYPE_PROTECTED"
                  throughput_threshold: 2500000
                  coverage_hole_detection: false
                unlocked_attributes:
                  - "throughput_threshold"
- name: Update rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_6ghz_high_thr"
                feature_attributes:
                  radio_band: "6GHZ"
                  monitoring_channels: "MONITORING_CHANNELS_ALL"
                  neighbor_discover_type: "NEIGHBOR_DISCOVER_TYPE_PROTECTED"
                  throughput_threshold: 2500000
                  coverage_hole_detection: true
                unlocked_attributes:
                  - "throughput_threshold"
- name: Delete rrm general profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - rrm_general_configuration:
              - design_name: "rrm_general_6ghz_high_thr"

- name: Add flexconnect profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - flexconnect_configuration:
              - design_name: "flexconnect_branch_office"
                feature_attributes:
                  overlap_ip_enable: true
                unlocked_attributes:
                  - "overlap_ip_enable"
- name: Update flexconnect profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - flexconnect_configuration:
              - design_name: "flexconnect_branch_office"
                feature_attributes:
                  overlap_ip_enable: false
                unlocked_attributes:
                  - "overlap_ip_enable"
- name: Delete flexconnect profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - flexconnect_configuration:
              - design_name: "flexconnect_branch_office"

- name: Add aaa radius profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - aaa_radius_attribute:
              - design_name: "sample_design"
                called_station_id: "sample_id"
                unlocked_attributes:
                  - "calledStationId"
- name: Update aaa radius profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - aaa_radius_attribute:
              - design_name: "sample_designnn"
                called_station_id: "sample_id"
                unlocked_attributes:
                  - "calledStationId"
- name: Delete aaa radius profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - feature_template_config:
          - aaa_radius_attribute:
              - design_name: "sample_designnn"

- name: Add advanced ssid profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - feature_template_config:
          - advanced_ssid:
              - design_name: "sample_advanced_ssid_design"
                feature_attributes:
                  peer2peer_blocking: "DISABLE"   # enum: DROP, FORWARD_UP, ALLOW_PVT_GROUP, DISABLE
                  passive_client: false
                  prediction_optimization: false
                  dual_band_neighbor_list: false
                  radius_nac_state: true
                  dhcp_required: true
                  dhcp_server: "10.10.10.5"
                  flex_local_auth: false
                  target_wakeup_time: true
                  downlink_ofdma: true
                  uplink_ofdma: true
                  downlink_mu_mimo: true
                  uplink_mu_mimo: true
                  dot11ax: true
                  aironet_ie_support: true
                  load_balancing: false
                  dtim_period_5ghz: 2   # 1-255
                  dtim_period_24ghz: 2  # 1-255
                  scan_defer_time: 100
                  max_clients: 200
                  max_clients_per_radio: 100   # 0-500
                  max_clients_per_ap: 300      # 0-1200
                  wmm_policy: "ALLOWED"        # DISABLED, REQUIRED, ALLOWED
                  multicast_buffer: true
                  multicast_buffer_value: 50
                  media_stream_multicast_direct: true
                  mu_mimo_11ac: true
                  wifi_to_cellular_steering: false
                  wifi_alliance_agile_multiband: false
                  fastlane_asr: false
                  dot11v_bss_max_idle_protected: false
                  universal_ap_admin: false
                  opportunistic_key_caching: false
                  ip_source_guard: false
                  dhcp_opt82_remote_id_sub_option: false
                  vlan_central_switching: false
                  call_snooping: false
                  send_disassociate: false
                  sent_486_busy: false
                  ip_mac_binding: false
                  idle_threshold: 300
                  defer_priority_0: false
                  defer_priority_1: false
                  defer_priority_2: false
                  defer_priority_3: false
                  defer_priority_4: false
                  defer_priority_5: false
                  defer_priority_6: false
                  defer_priority_7: false
                  share_data_with_client: false
                  advertise_support: false
                  advertise_pc_analytics_support: false
                  send_beacon_on_association: false
                  send_beacon_on_roam: false
                  fast_transition_reassociation_timeout: 200
                  mdns_mode: "MDNS_SD_BRIDGING"  # MDNS_SD_BRIDGING, MDNS_SD_DROP, MDNS_SD_GATEWAY
                unlocked_attributes:
                  - "peer2peer_blocking"
                  - "passive_client"
                  - "dot11ax"
                  - "load_balancing"
                  - "max_clients"
                  - "max_clients_per_radio"
                  - "max_clients_per_ap"
                  - "wmm_policy"
                  - "dtim_period_5ghz"
                  - "dtim_period_24ghz"
                  - "scan_defer_time"
                  - "mdns_mode"
- name: Update advanced ssid profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
  config:
    - feature_template_config:
        - advanced_ssid:
            - design_name: "sample_advanced_ssid_design"
              feature_attributes:
                peer2peer_blocking: "DISABLE"   # enum: DROP, FORWARD_UP, ALLOW_PVT_GROUP, DISABLE
                passive_client: false
                prediction_optimization: false
                dual_band_neighbor_list: false
                radius_nac_state: true
                dhcp_required: true
                dhcp_server: "10.10.10.5"
                flex_local_auth: false
                target_wakeup_time: true
                downlink_ofdma: true
                uplink_ofdma: true
                downlink_mu_mimo: true
                uplink_mu_mimo: true
                dot11ax: true
                aironet_ie_support: true
                load_balancing: false
                dtim_period_5ghz: 2   # 1-255
                dtim_period_24ghz: 2  # 1-255
                scan_defer_time: 100
                max_clients: 200
                max_clients_per_radio: 100   # 0-500
                max_clients_per_ap: 300      # 0-1200
                wmm_policy: "ALLOWED"        # DISABLED, REQUIRED, ALLOWED
                multicast_buffer: true
                multicast_buffer_value: 50
                media_stream_multicast_direct: true
                mu_mimo_11ac: true
                wifi_to_cellular_steering: false
                wifi_alliance_agile_multiband: false
                fastlane_asr: false
                dot11v_bss_max_idle_protected: false
                universal_ap_admin: false
                opportunistic_key_caching: false
                ip_source_guard: false
                dhcp_opt82_remote_id_sub_option: true
                vlan_central_switching: true
                call_snooping: false
                send_disassociate: false
                sent_486_busy: false
                ip_mac_binding: false
                idle_threshold: 300
                defer_priority_0: false
                defer_priority_1: false
                defer_priority_2: false
                defer_priority_3: false
                defer_priority_4: false
                defer_priority_5: false
                defer_priority_6: false
                defer_priority_7: false
                share_data_with_client: false
                advertise_support: false
                advertise_pc_analytics_support: false
                send_beacon_on_association: false
                send_beacon_on_roam: false
                fast_transition_reassociation_timeout: 200
                mdns_mode: "MDNS_SD_BRIDGING"  # MDNS_SD_BRIDGING, MDNS_SD_DROP, MDNS_SD_GATEWAY
              unlocked_attributes:
                - "peer2peer_blocking"
                - "passive_client"
                - "dot11ax"
                - "load_balancing"
                - "max_clients"
                - "max_clients_per_radio"
                - "max_clients_per_ap"
                - "wmm_policy"
                - "dtim_period_5ghz"
                - "dtim_period_24ghz"
                - "scan_defer_time"
                - "mdns_mode"
- name: Delete advanced ssid profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
  config:
    - feature_template_config:
        - advanced_ssid:
            - design_name: "sample_advanced_ssid_design"

- name: Add SSIDs
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - ssids:
          - ssid_name: "corp_wifi"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "OPEN"
          - ssid_name: "guest_wifi"
            ssid_type: "Guest"
            l2_security:
              l2_auth_type: "OPEN"
            l3_security:
              l3_auth_type: "OPEN"
          - ssid_name: "staff_wifi"
            ssid_type: "Enterprise"
            wlan_profile_name: "staff_wifi_profile"
            radio_policy:
              radio_bands: [2.4, 5, 6]
              2_dot_4_ghz_band_policy: "802.11-bg"
              band_select: true
              6_ghz_client_steering: true
            fast_lane: true
            ssid_state:
              admin_status: true
              broadcast_ssid: true
            l2_security:
              l2_auth_type: "WPA2_WPA3_PERSONAL"
              ap_beacon_protection: true
              passphrase_type: "ASCII"
              passphrase: "password123"
            fast_transition: "ENABLE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["CCMP128"]
            auth_key_management: ["PSK", "SAE"]
            aaa:
              aaa_override: false
              mac_filtering: true
              deny_rcm_clients: false
            mfp_client_protection: "OPTIONAL"
            protected_management_frame: "REQUIRED"
            11k_neighbor_list: true
            coverage_hole_detection: true
            wlan_timeouts:
              enable_session_timeout: true
              session_timeout: 3600
              enable_client_exclusion_timeout: true
              client_exclusion_timeout: 1800
            bss_transition_support:
              bss_max_idle_service: true
              bss_idle_client_timeout: 300
              directed_multicast_service: true
            nas_id: ["AP Location"]
            client_rate_limit: 90000
          - ssid_name: "iot_network"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA2_ENTERPRISE"
            fast_transition: "ADAPTIVE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["CCMP128"]
            auth_key_management: ["CCKM", "802.1X-SHA1", "802.1X-SHA2"]
            cckm_timestamp_tolerance: 1000
          - ssid_name: "secure_psk"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA2_ENTERPRISE"
            fast_transition: "ADAPTIVE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["GCMP256"]
            auth_key_management: ["SUITE-B-192X"]
          - ssid_name: "lab_wifi"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA2_ENTERPRISE"
            fast_transition: "DISABLE"
            wpa_encryption: ["GCMP256"]
            auth_key_management: ["SUITE-B-192X"]
          - ssid_name: "vip_wifi"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA2_ENTERPRISE"
            fast_transition: "ENABLE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["CCMP128"]
            auth_key_management: ["CCKM", "802.1X-SHA1", "802.1X-SHA2", "FT+802.1x"]
            cckm_timestamp_tolerance: 3000
          - ssid_name: "enterprise_secure"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA3_ENTERPRISE"
            fast_transition: "ENABLE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["CCMP128"]
            auth_key_management: ["802.1X-SHA1", "802.1X-SHA2", "FT+802.1x"]
          - ssid_name: "branch_office_wifi"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA3_ENTERPRISE"
            fast_transition: "ADAPTIVE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["GCMP128"]
            auth_key_management: ["SUITE-B-1X"]
          - ssid_name: "conference_wifi"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA3_ENTERPRISE"
            fast_transition: "DISABLE"
            wpa_encryption: ["GCMP256"]
            auth_key_management: ["SUITE-B-192X"]
          - ssid_name: "floor1_wifi"
            ssid_type: "Enterprise"
            wlan_profile_name: "floor1_profile"
            radio_policy:
              radio_bands: [2.4, 5, 6]
              2_dot_4_ghz_band_policy: "802.11-bg"
              band_select: true
              6_ghz_client_steering: true
            fast_lane: true
            ssid_state:
              admin_status: true
              broadcast_ssid: true
            l2_security:
              l2_auth_type: "OPEN"
            fast_transition: "DISABLE"
            aaa:
              aaa_override: false
              mac_filtering: true
              deny_rcm_clients: false
            mfp_client_protection: "OPTIONAL"
            protected_management_frame: "REQUIRED"
            11k_neighbor_list: true
            coverage_hole_detection: true
            wlan_timeouts:
              enable_session_timeout: true
              session_timeout: 3600
              enable_client_exclusion_timeout: true
              client_exclusion_timeout: 1800
            bss_transition_support:
              bss_max_idle_service: true
              bss_idle_client_timeout: 300
              directed_multicast_service: true
            nas_id: ["AP Location"]
            client_rate_limit: 90000
            sites_specific_override_settings:
              - site_name_hierarchy: "Global/USA/San
                  Jose"
                l2_security:
                  l2_auth_type: "WPA2_PERSONAL"
                  passphrase: "password456"
                fast_transition: "ENABLE"
                wpa_encryption: ["CCMP128"]
                auth_key_management: ["PSK"]
              - site_name_hierarchy: "Global/USA/San
                  Jose/BLDG23"
                fast_transition: "DISABLE"
                client_rate_limit: 9000
- name: Update SSIDs
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - ssids:
          - ssid_name: "enterprise_secure"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA2_WPA3_ENTERPRISE"
              ap_beacon_protection: true
            fast_transition: "ENABLE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["CCMP128"]
            auth_key_management: ["CCKM", "802.1X-SHA1", "802.1X-SHA2", "FT+802.1x"]
            cckm_timestamp_tolerance: 3000
            protected_management_frame: "REQUIRED"
          - ssid_name: "branch_office_wifi"
            ssid_type: "Enterprise"
            l2_security:
              l2_auth_type: "WPA2_WPA3_ENTERPRISE"
              ap_beacon_protection: true
            fast_transition: "DISABLE"
            fast_transition_over_the_ds: true
            wpa_encryption: ["GCMP128", "CCMP256", "GCMP256"]
            auth_key_management: ["SUITE-B-1X", "SUITE-B-192X"]
            protected_management_frame: "REQUIRED"
          - ssid_name: "guest_wifi"
            ssid_type: "Guest"
            sites_specific_override_settings:
              - site_name_hierarchy: "Global/USA/San
                  Jose"
                l2_security:
                  l2_auth_type: "WPA2_PERSONAL"
                  passphrase: "password456"
                fast_transition: "ENABLE"
                wpa_encryption: ["CCMP128"]
                auth_key_management: ["PSK"]
- name: Delete SSIDs
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - ssids:
          - ssid_name: "corp_wifi"
          - ssid_name: "guest_wifi"
          - ssid_name: "staff_wifi"
          - ssid_name: "iot_network"
          - ssid_name: "secure_psk"
          - ssid_name: "lab_wifi"
          - ssid_name: "vip_wifi"
          - ssid_name: "enterprise_secure"
          - ssid_name: "branch_office_wifi"
          - ssid_name: "conference_wifi"
          - ssid_name: "floor1_wifi"
            sites_specific_override_settings:
              - site_name_hierarchy: "Global/USA/San
                  Jose"
                remove_override_in_hierarchy: true
- name: Add Interfaces
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - interfaces:
          - interface_name: "data"
            vlan_id: 1
          - interface_name: "voice"
            vlan_id: 2
          - interface_name: "guest_access"
            vlan_id: 3
          - interface_name: "iot_network"
            vlan_id: 4
          - interface_name: "secure_vlan"
            vlan_id: 5
          - interface_name: "corporate"
            vlan_id: 6
- name: Update Interfaces
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - interfaces:
          - interface_name: "data"
            vlan_id: 7
          - interface_name: "voice"
            vlan_id: 8
          - interface_name: "guest_access"
            vlan_id: 9
          - interface_name: "corporate"
            vlan_id: 10
- name: Delete Interfaces
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - interfaces:
          - interface_name: "data"
          - interface_name: "voice"
          - interface_name: "guest_access"
          - interface_name: "iot_network"
          - interface_name: "secure_vlan"
          - interface_name: "corporate"
- name: Add Power Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - power_profiles:
          - power_profile_name: "default"
            rules:
              - interface_type: "USB"
              - interface_type: "RADIO"
              - interface_type: "ETHERNET"
          - power_profile_name: "EthernetSpeeds"
            power_profile_description: "Profile for
              all Ethernet speed settings."
            rules:
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET0"
                parameter_type: "SPEED"
                parameter_value: "5000MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET0"
                parameter_type: "SPEED"
                parameter_value: "2500MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET0"
                parameter_type: "SPEED"
                parameter_value: "1000MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET0"
                parameter_type: "SPEED"
                parameter_value: "100MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET1"
                parameter_type: "SPEED"
                parameter_value: "5000MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET1"
                parameter_type: "SPEED"
                parameter_value: "2500MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET1"
                parameter_type: "SPEED"
                parameter_value: "1000MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET1"
                parameter_type: "SPEED"
                parameter_value: "100MBPS"
          - power_profile_name: "EthernetState"
            power_profile_description: "Profile for
              Ethernet state settings."
            rules:
              - interface_type: "ETHERNET"
                interface_id: "LAN1"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "ETHERNET"
                interface_id: "LAN2"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "ETHERNET"
                interface_id: "LAN3"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
          - power_profile_name: "RadioState"
            power_profile_description: "Profile for
              radio state settings."
            rules:
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "RADIO"
                interface_id: "SECONDARY_5GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "RADIO"
                interface_id: "2_4GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
          - power_profile_name: "RadioSpatialStream"
            power_profile_description: "Profile for
              radio spatial stream settings."
            rules:
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "FOUR_BY_FOUR"
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "THREE_BY_THREE"
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "TWO_BY_TWO"
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "ONE_BY_ONE"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "FOUR_BY_FOUR"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "THREE_BY_THREE"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "TWO_BY_TWO"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "ONE_BY_ONE"
              - interface_type: "RADIO"
                interface_id: "SECONDARY_5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "FOUR_BY_FOUR"
              - interface_type: "RADIO"
                interface_id: "2_4GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "FOUR_BY_FOUR"
          - power_profile_name: "UsbState"
            power_profile_description: "Profile for
              USB state settings."
            rules:
              - interface_type: "USB"
                interface_id: "USB0"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
- name: Update Power Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - power_profiles:
          - power_profile_name: "default"
            rules:
              - interface_type: "RADIO"
              - interface_type: "ETHERNET"
              - interface_type: "USB"
          - power_profile_name: "EthernetSpeeds"
            rules:
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET1"
                parameter_type: "SPEED"
                parameter_value: "2500MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET0"
                parameter_type: "SPEED"
                parameter_value: "1000MBPS"
              - interface_type: "ETHERNET"
                interface_id: "GIGABITETHERNET1"
                parameter_type: "SPEED"
                parameter_value: "100MBPS"
          - power_profile_name: "EthernetState"
            power_profile_description: "Updated profile
              for Ethernet state settings."
            rules:
              - interface_type: "ETHERNET"
                interface_id: "LAN3"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "ETHERNET"
                interface_id: "LAN1"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "ETHERNET"
                interface_id: "LAN2"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
          - power_profile_name: "RadioState"
            rules:
              - interface_type: "RADIO"
                interface_id: "2_4GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "RADIO"
                interface_id: "SECONDARY_5GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
          - power_profile_name: "RadioSpatialStream"
            power_profile_description: "Updated profile
              for radio spatial stream settings."
            rules:
              - interface_type: "RADIO"
                interface_id: "6GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "TWO_BY_TWO"
              - interface_type: "RADIO"
                interface_id: "5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "ONE_BY_ONE"
              - interface_type: "RADIO"
                interface_id: "2_4GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "FOUR_BY_FOUR"
              - interface_type: "RADIO"
                interface_id: "SECONDARY_5GHZ"
                parameter_type: "SPATIALSTREAM"
                parameter_value: "FOUR_BY_FOUR"
          - power_profile_name: "UsbState"
            power_profile_description: "Updated profile
              for USB state settings."
            rules:
              - interface_type: "USB"
                interface_id: "USB0"
                parameter_type: "STATE"
                parameter_value: "DISABLE"
          - power_profile_name: "UsbState"
            rules:
              - interface_type: "USB"
- name: Delete Power Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - power_profiles:
          - power_profile_name: "default"
          - power_profile_name: "EthernetSpeeds"
          - power_profile_name: "EthernetState"
          - power_profile_name: "RadioState"
          - power_profile_name: "RadioSpatialStream"
          - power_profile_name: "UsbState"
- name: Add Access Point Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - access_point_profiles:
          - access_point_profile_name: "Corporate-Office-AP"
          - access_point_profile_name: "Guest-WiFi-AP"
            access_point_profile_description: "Main
              office for guest network"
          - access_point_profile_name: "Remote-Worker-AP"
            access_point_profile_description: "Main
              office AP profile 3"
            remote_teleworker: true
          - access_point_profile_name: "Branch-Office-AP"
            remote_teleworker: true
          - access_point_profile_name: "Warehouse-AP"
            remote_teleworker: true
            management_settings:
              access_point_authentication: "NO-AUTH"
          - access_point_profile_name: "Manufacturing-Plant-AP"
            remote_teleworker: false
            management_settings:
              access_point_authentication: "EAP-TLS"
          - access_point_profile_name: "Development-AP"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
          - access_point_profile_name: "Conference-Room-AP"
            management_settings:
              access_point_authentication: "EAP-FAST"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
          - access_point_profile_name: "Lobby-AP"
            remote_teleworker: true
            management_settings:
              access_point_authentication: "NO-AUTH"
              ssh_enabled: true
              telnet_enabled: false
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
          - access_point_profile_name: "Cafeteria-AP"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: true
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
          - access_point_profile_name: "Parking-Lot-AP"
            management_settings:
              access_point_authentication: "EAP-TLS"
              ssh_enabled: false
              telnet_enabled: false
          - access_point_profile_name: "Outdoor-AP"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
          - access_point_profile_name: "Training-Room-AP"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
          - access_point_profile_name: "Executive-Office-AP"
            security_settings:
              awips: true
              awips_forensic: true
          - access_point_profile_name: "Meeting-Room-AP"
            remote_teleworker: false
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
          - access_point_profile_name: "Reception-AP"
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "5 GHz"
              ghz_5_backhaul_data_rates: "802.11ax"
          - access_point_profile_name: "Server-Room-AP"
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
          - access_point_profile_name: "IT-Department-AP"
            access_point_profile_description: "Main
              office AP profile 17"
            remote_teleworker: false
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePasfsdfs"
              management_enable_password: "adflmlsdsfdfdf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
          - access_point_profile_name: "HR-Department-AP"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
          - access_point_profile_name: "Finance-Department-AP"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "DAILY"
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
          - access_point_profile_name: "Marketing-Department-AP"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "WEEKLY"
                  scheduler_days_list: ["monday", "tuesday"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
          - access_point_profile_name: "Sales-Department-AP"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "MONTHLY"
                  scheduler_dates_list: ["2", "9", "28"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
          - access_point_profile_name: "Engineering-Department-AP"
            access_point_profile_description: "Main
              office AP profile 22"
            remote_teleworker: false
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePasfsdfs"
              management_enable_password: "adflmlsdsfdfdf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "DAILY"
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
          - access_point_profile_name: "Support-Department-AP"
            access_point_profile_description: "Main
              office AP profile 23"
            remote_teleworker: false
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePasfsdfs"
              management_enable_password: "adflmlsdsfdfdf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "WEEKLY"
                  scheduler_days_list: ["monday", "tuesday"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
          - access_point_profile_name: "R&D-Department-AP"
            access_point_profile_description: "Main
              office AP profile 24"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePasfsdfs"
              management_enable_password: "adflmlsdsfdfdf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "MONTHLY"
                  scheduler_dates_list: ["2", "9", "28"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
          - access_point_profile_name: "Production-AP"
            country_code: "India"
          - access_point_profile_name: "Quality-Control-AP"
            country_code: "Australia"
            time_zone: "NOT CONFIGURED"
            maximum_client_limit: 500
          - access_point_profile_name: "Logistics-AP"
            time_zone: "CONTROLLER"
            maximum_client_limit: 1100
          - access_point_profile_name: "Security-AP"
            time_zone: "DELTA FROM CONTROLLER"
            time_zone_offset_hour: -11
            time_zone_offset_minutes: 30
            maximum_client_limit: 900
          - access_point_profile_name: "Maintenance-AP"
            access_point_profile_description: "Main
              office AP profile 29"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "DAILY"
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
            country_code: "Australia"
            time_zone: "NOT CONFIGURED"
            maximum_client_limit: 500
          - access_point_profile_name: "Backup-AP"
            access_point_profile_description: "Main
              office AP profile 30"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "WEEKLY"
                  scheduler_days_list: ["monday", "tuesday"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
            time_zone: "CONTROLLER"
            maximum_client_limit: 1100
          - access_point_profile_name: "Testing-AP"
            access_point_profile_description: "Main
              office AP profile 31"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "MONTHLY"
                  scheduler_dates_list: ["2", "9", "28"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
            time_zone: "DELTA FROM CONTROLLER"
            time_zone_offset_hour: -11
            time_zone_offset_minutes: 30
            maximum_client_limit: 900
          - access_point_profile_name: "Staging-AP"
            access_point_profile_description: "Main
              office AP profile 31"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: false
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "Low-Power-Mode"
              calendar_power_profiles:
                - ap_power_profile_name: "Schedule-Mode"
                  scheduler_type: "MONTHLY"
                  scheduler_dates_list: ["2", "9", "28"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
            time_zone: "DELTA FROM CONTROLLER"
            time_zone_offset_hour: -11
            time_zone_offset_minutes: 30
            maximum_client_limit: 900
- name: Update Access Point Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - access_point_profiles:
          - access_point_profile_name: "Corporate-Office-AP"
            access_point_profile_description: "Main
              office AP profile 1"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePass"
              management_enable_password: "adflmlssf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "ada"
              calendar_power_profiles:
                - ap_power_profile_name: "sdfd"
                  scheduler_type: "MONTHLY"
                  scheduler_dates_list: ["2", "9", "28"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
            time_zone: "DELTA FROM CONTROLLER"
            time_zone_offset_hour: -11
            time_zone_offset_minutes: 30
            maximum_client_limit: 900
          - access_point_profile_name: "R&D-Department-AP"
            access_point_profile_description: "Main
              office AP profile 24"
            management_settings:
              access_point_authentication: "EAP-PEAP"
              dot1x_username: "admin"
              dot1x_password: "asdfasdfasdfsdf"
              ssh_enabled: false
              telnet_enabled: true
              management_username: "admin"
              management_password: "securePasfsdfs"
              management_enable_password: "adflmlsdsfdfdf"
            security_settings:
              awips: true
              awips_forensic: false
              rogue_detection_enabled: true
              minimum_rssi: -71
              transient_interval: 300
              report_interval: 60
              pmf_denial: false
            mesh_enabled: true
            mesh_settings:
              range: 1000
              backhaul_client_access: true
              rap_downlink_backhaul: "2.4 GHz"
              ghz_2_4_backhaul_data_rates: "802.11n"
              bridge_group_name: "Bridge1"
            power_settings:
              ap_power_profile_name: "ada"
              calendar_power_profiles:
                - ap_power_profile_name: "sdfd"
                  scheduler_type: "MONTHLY"
                  scheduler_dates_list: ["2", "9", "28"]
                  scheduler_start_time: "08:00 AM"
                  scheduler_end_time: "6:00 PM"
      - access_point_profile_name: "Guest-WiFi-AP"
        access_point_profile_description: "Updated Main
          office AP profile 2"
      - access_point_profile_name: "Remote-Worker-AP"
        access_point_profile_description: "Updated Main
          office AP profile 3"
        management_settings:
          access_point_authentication: "NO-AUTH"
      - access_point_profile_name: "Warehouse-AP"
        power_settings:
          calendar_power_profiles:
            - ap_power_profile_name: "Low-Power-Mode"
              scheduler_type: "DAILY"
              scheduler_start_time: "10:00 AM"
              scheduler_end_time: "6:00 PM"
      - access_point_profile_name: "Manufacturing-Plant-AP"
        time_zone: "CONTROLLER"
        time_zone_offset_hour: 0
        time_zone_offset_minutes: 0
        maximum_client_limit: 900
- name: Delete Access Point Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - access_point_profiles:
          - access_point_profile_name: "Corporate-Office-AP"
          - access_point_profile_name: "Guest-WiFi-AP"
          - access_point_profile_name: "Remote-Worker-AP"
          - access_point_profile_name: "Branch-Office-AP"
          - access_point_profile_name: "Warehouse-AP"
          - access_point_profile_name: "Manufacturing-Plant-AP"
          - access_point_profile_name: "Conference-Room-AP"
          - access_point_profile_name: "Lobby-AP"
          - access_point_profile_name: "Cafeteria-AP"
          - access_point_profile_name: "Parking-Lot-AP"
          - access_point_profile_name: "Outdoor-AP"
          - access_point_profile_name: "Training-Room-AP"
          - access_point_profile_name: "Executive-Office-AP"
          - access_point_profile_name: "Meeting-Room-AP"
          - access_point_profile_name: "Reception-AP"
          - access_point_profile_name: "Server-Room-AP"
          - access_point_profile_name: "IT-Department-AP"
          - access_point_profile_name: "HR-Department-AP"
          - access_point_profile_name: "Finance-Department-AP"
          - access_point_profile_name: "Marketing-Department-AP"
          - access_point_profile_name: "Sales-Department-AP"
          - access_point_profile_name: "Engineering-Department-AP"
          - access_point_profile_name: "Support-Department-AP"
          - access_point_profile_name: "R&D-Department-AP"
          - access_point_profile_name: "Production-AP"
          - access_point_profile_name: "Quality-Control-AP"
          - access_point_profile_name: "Logistics-AP"
          - access_point_profile_name: "Security-AP"
          - access_point_profile_name: "Maintenance-AP"
          - access_point_profile_name: "Backup-AP"
          - access_point_profile_name: "Testing-AP"
          - access_point_profile_name: "Development-AP"
          - access_point_profile_name: "Staging-AP"
- name: Add Radio Frequency Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - radio_frequency_profiles:
          - radio_frequency_profile_name: "rf_profile_2_4ghz_basic"
            default_rf_profile: false
            radio_bands: [2.4]
          - radio_frequency_profile_name: "rf_profile_5ghz_basic"
            default_rf_profile: false
            radio_bands: [5]
          - radio_frequency_profile_name: "rf_profile_6ghz_basic"
            default_rf_profile: false
            radio_bands: [6]
          - radio_frequency_profile_name: "rf_profile_2_4ghz_high_parent"
            default_rf_profile: false
            radio_bands: [2.4]
            radio_bands_2_4ghz_settings:
              parent_profile: "HIGH"
              dca_channels_list: [1, 6, 11]
              supported_data_rates_list: [11, 12, 18, 2, 24, 36, 48, 5.5, 54, 6, 9]
              mandatory_data_rates_list: [2, 11]
          - radio_frequency_profile_name: "rf_profile_5ghz_160mhz_typical"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "TYPICAL"
              channel_width: "160"
              dca_channels_list: [36, 40, 44, 48, 52,
                                  56, 60, 64, 100, 104, 108, 112, 116,
                                  120, 124, 128]
              supported_data_rates_list: [12, 18, 24, 36, 48, 54]
              mandatory_data_rates_list: [24]
          - radio_frequency_profile_name: "rf_profile_6ghz_custom"
            default_rf_profile: false
            radio_bands: [6]
            radio_bands_6ghz_settings:
              dca_channels_list: [13, 17, 21, 25, 29, 33, 37, 41]
              supported_data_rates_list: [6, 9, 12, 18, 24, 36, 48, 54]
              mandatory_data_rates_list: [6, 9]
              parent_profile: "CUSTOM"
              minimum_dbs_channel_width: 20
              maximum_dbs_channel_width: 160
          - radio_frequency_profile_name: "rf_profile_2_4ghz_custom_power"
            default_rf_profile: false
            radio_bands: [2.4]
            radio_bands_2_4ghz_settings:
              parent_profile: "CUSTOM"
              minimum_power_level: 5
              maximum_power_level: 20
              rx_sop_threshold: "MEDIUM"
          - radio_frequency_profile_name: "rf_profile_5ghz_high_limit"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "HIGH"
              zero_wait_dfs: true
              client_limit: 50
          - radio_frequency_profile_name: "rf_profile_6ghz_psc_enforced"
            default_rf_profile: false
            radio_bands: [6]
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              psc_enforcing_enabled: true
              discovery_frames_6ghz: "Broadcast Probe
                Response"
          - radio_frequency_profile_name: "rf_profile_2_4_5ghz_typical"
            default_rf_profile: false
            radio_bands: [2.4, 5]
            radio_bands_2_4ghz_settings:
              parent_profile: "TYPICAL"
              minimum_power_level: 5
              maximum_power_level: 20
            radio_bands_5ghz_settings:
              parent_profile: "TYPICAL"
              channel_width: "20"
              zero_wait_dfs: true
          - radio_frequency_profile_name: "rf_profile_5_6ghz_mixed"
            default_rf_profile: false
            radio_bands: [5, 6]
            radio_bands_5ghz_settings:
              parent_profile: "LOW"
              preamble_puncturing: false
              client_limit: 100
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              psc_enforcing_enabled: true
              maximum_dbs_channel_width: 160
              discovery_frames_6ghz: "None"
          - radio_frequency_profile_name: "rf_profile_2_4_6ghz_high"
            default_rf_profile: false
            radio_bands: [2.4, 6]
            radio_bands_2_4ghz_settings:
              parent_profile: "CUSTOM"
              minimum_power_level: 5
              maximum_power_level: 20
              rx_sop_threshold: "HIGH"
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              minimum_dbs_channel_width: 20
              maximum_dbs_channel_width: 80
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_high_low"
            default_rf_profile: false
            radio_bands: [2.4, 5, 6]
            radio_bands_2_4ghz_settings:
              parent_profile: "HIGH"
              minimum_power_level: 5
              maximum_power_level: 20
            radio_bands_5ghz_settings:
              parent_profile: "LOW"
              channel_width: "80"
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              maximum_dbs_channel_width: 160
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_spatial"
            default_rf_profile: false
            radio_bands: [2.4, 5, 6]
            radio_bands_2_4ghz_settings:
              parent_profile: "HIGH"
              spatial_reuse:
                non_srg_obss_pd: true
                non_srg_obss_pd_max_threshold: -63
            radio_bands_5ghz_settings:
              parent_profile: "LOW"
              flexible_radio_assignment:
                client_aware: true
                client_reset: 5
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              multi_bssid:
                dot_11be_parameters:
                  ofdma_downlink: true
                  mu_mimo_downlink: true
          - radio_frequency_profile_name: "rf_profile_5_6ghz_high_limit"
            default_rf_profile: false
            radio_bands: [5, 6]
            radio_bands_5ghz_settings:
              parent_profile: "HIGH"
              zero_wait_dfs: true
              client_limit: 200
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              broadcast_probe_response_interval: 20
          - radio_frequency_profile_name: "rf_profile_6ghz_twt_broadcast"
            default_rf_profile: false
            radio_bands: [6]
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              multi_bssid:
                target_waketime: true
                twt_broadcast_support: true
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_advanced"
            default_rf_profile: false
            radio_bands: [2.4, 5, 6]
            radio_bands_2_4ghz_settings:
              parent_profile: "LOW"
              dca_channels_list: [1, 6, 11]
              supported_data_rates_list: [1, 11, 12,
                                          18, 2, 24, 36, 48, 5.5, 54, 6, 9]
              mandatory_data_rates_list: [1, 2]
              minimum_power_level: 3
              maximum_power_level: 20
              rx_sop_threshold: "LOW"
              tpc_power_threshold: -65
              coverage_hole_detection:
                minimum_client_level: 3
                data_rssi_threshold: -80
                voice_rssi_threshold: -75
                exception_level: 5
              client_limit: 50
              spatial_reuse:
                non_srg_obss_pd: true
                non_srg_obss_pd_max_threshold: -63
                srg_obss_pd: true
                srg_obss_pd_min_threshold: -63
                srg_obss_pd_max_threshold: -62
            radio_bands_5ghz_settings:
              parent_profile: "TYPICAL"
              channel_width: "80"
              preamble_puncturing: false
              zero_wait_dfs: true
              dca_channels_list: [36, 40, 44, 48]
              supported_data_rates_list: [6, 9, 12,
                                          18, 24, 36, 48, 54]
              mandatory_data_rates_list: [6]
              minimum_power_level: 5
              maximum_power_level: 30
              rx_sop_threshold: "HIGH"
              tpc_power_threshold: -70
              coverage_hole_detection:
                minimum_client_level: 4
                data_rssi_threshold: -75
                voice_rssi_threshold: -70
                exception_level: 4
              client_limit: 100
              flexible_radio_assignment:
                client_aware: true
                client_select: 30
                client_reset: 10
              spatial_reuse:
                non_srg_obss_pd: true
                non_srg_obss_pd_max_threshold: -63
                srg_obss_pd: true
                srg_obss_pd_min_threshold: -63
                srg_obss_pd_max_threshold: -62
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              minimum_dbs_channel_width: 20
              maximum_dbs_channel_width: 160
              preamble_puncturing: true
              psc_enforcing_enabled: true
              dca_channels_list: [37, 53, 69, 85]
              supported_data_rates_list: [12, 18, 24,
                                          36, 48, 54, 6, 9]
              mandatory_data_rates_list: [6, 12]
              minimum_power_level: 10
              maximum_power_level: 30
              rx_sop_threshold: "CUSTOM"
              custom_rx_sop_threshold: -80
              tpc_power_threshold: -60
              coverage_hole_detection:
                minimum_client_level: 5
                data_rssi_threshold: -72
                voice_rssi_threshold: -68
                exception_level: 6
              client_limit: 150
              flexible_radio_assignment:
                client_reset_count: 10
                client_utilization_threshold: 10
              discovery_frames_6ghz: "FILS Discovery"
              broadcast_probe_response_interval: 10
              standard_power_service: false
              multi_bssid:
                dot_11ax_parameters:
                  ofdma_downlink: true
                  ofdma_uplink: true
                  mu_mimo_downlink: true
                  mu_mimo_uplink: false
                dot_11be_parameters:
                  ofdma_downlink: true
                  ofdma_uplink: true
                  mu_mimo_downlink: true
                  mu_mimo_uplink: true
                  ofdma_multi_ru: true
                target_waketime: true
                twt_broadcast_support: true
              spatial_reuse:
                non_srg_obss_pd: true
                non_srg_obss_pd_max_threshold: -63
                srg_obss_pd: true
                srg_obss_pd_min_threshold: -63
                srg_obss_pd_max_threshold: -62
- name: Update Radio Frequency Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - radio_frequency_profiles:
          - radio_frequency_profile_name: "rf_profile_2_4ghz_basic"
            default_rf_profile: false
            radio_bands: [2.4]
            radio_bands_2_4ghz_settings:
              parent_profile: "LOW"
              minimum_power_level: 3
              maximum_power_level: 15
          - radio_frequency_profile_name: "rf_profile_5ghz_basic"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "TYPICAL"
              channel_width: "40"
              zero_wait_dfs: false
          - radio_frequency_profile_name: "rf_profile_6ghz_basic"
            default_rf_profile: false
            radio_bands: [6]
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              minimum_dbs_channel_width: 40
              maximum_dbs_channel_width: 80
          - radio_frequency_profile_name: "rf_profile_2_4ghz_high_parent"
            default_rf_profile: false
            radio_bands: [2.4]
            radio_bands_2_4ghz_settings:
              parent_profile: "TYPICAL"
              dca_channels_list: [1, 6]
              supported_data_rates_list: [1, 11, 12,
                                          18, 2, 24, 36, 48, 5.5, 54, 6, 9]
              mandatory_data_rates_list: [12]
          - radio_frequency_profile_name: "rf_profile_5ghz_160mhz_typical"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "HIGH"
              channel_width: "80"
              dca_channels_list: [52, 56, 60, 64]
              supported_data_rates_list: [18, 24, 36, 48, 54]
              mandatory_data_rates_list: [24]
          - radio_frequency_profile_name: "rf_profile_2_4ghz_custom_power"
            default_rf_profile: false
            radio_bands: [2.4]
            radio_bands_2_4ghz_settings:
              parent_profile: "LOW"
              minimum_power_level: 1
              maximum_power_level: 10
          - radio_frequency_profile_name: "rf_profile_5ghz_high_limit"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "TYPICAL"
              channel_width: "20"
              zero_wait_dfs: true
          - radio_frequency_profile_name: "rf_profile_6ghz_psc_enforced"
            default_rf_profile: false
            radio_bands: [6]
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              minimum_dbs_channel_width: 20
              maximum_dbs_channel_width: 40
          - radio_frequency_profile_name: "rf_profile_5_6ghz_mixed"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "TYPICAL"
              channel_width: "160"
              dca_channels_list: [36, 40, 44, 48, 52, 56, 60, 64]
              supported_data_rates_list: [12, 24, 36, 48, 6, 18, 9, 54]
              mandatory_data_rates_list: [24]
          - radio_frequency_profile_name: "rf_profile_2_4_6ghz_high"
            default_rf_profile: false
            radio_bands: [6]
            radio_bands_6ghz_settings:
              parent_profile: "CUSTOM"
              dca_channels_list: [1, 129, 5, 133, 9, 137, 13, 141, 17, 145]
              supported_data_rates_list: [12, 18, 24, 36, 48, 54, 6, 9]
              mandatory_data_rates_list: [6, 12]
              minimum_dbs_channel_width: 40
              maximum_dbs_channel_width: 80
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_high_low"
            default_rf_profile: false
            radio_bands: [2.4]
            radio_bands_2_4ghz_settings:
              parent_profile: "TYPICAL"
              minimum_power_level: 2
              maximum_power_level: 12
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_spatial"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "CUSTOM"
              channel_width: "40"
              zero_wait_dfs: false
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_advanced"
            default_rf_profile: false
            radio_bands: [5]
            radio_bands_5ghz_settings:
              parent_profile: "LOW"
              channel_width: "20"
              dca_channels_list: [36, 44, 48]
              supported_data_rates_list: [12, 24, 36, 48, 6, 18, 9, 54]
              mandatory_data_rates_list: [24]
- name: Delete Radio Frequency Profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - radio_frequency_profiles:
          - radio_frequency_profile_name: "rf_profile_2_4ghz_basic"
          - radio_frequency_profile_name: "rf_profile_5ghz_basic"
          - radio_frequency_profile_name: "rf_profile_6ghz_basic"
          - radio_frequency_profile_name: "rf_profile_2_4ghz_high_parent"
          - radio_frequency_profile_name: "rf_profile_5ghz_160mhz_typical"
          - radio_frequency_profile_name: "rf_profile_6ghz_custom"
          - radio_frequency_profile_name: "rf_profile_2_4ghz_custom_power"
          - radio_frequency_profile_name: "rf_profile_5ghz_high_limit"
          - radio_frequency_profile_name: "rf_profile_6ghz_psc_enforced"
          - radio_frequency_profile_name: "rf_profile_2_4_5ghz_typical"
          - radio_frequency_profile_name: "rf_profile_5_6ghz_mixed"
          - radio_frequency_profile_name: "rf_profile_2_4_6ghz_high"
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_high_low"
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_spatial"
          - radio_frequency_profile_name: "rf_profile_5_6ghz_high_limit"
          - radio_frequency_profile_name: "rf_profile_6ghz_twt_broadcast"
          - radio_frequency_profile_name: "rf_profile_2_4_5_6ghz_advanced"
- name: Add Anchor Groups
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - anchor_groups:
          - anchor_group_name: "Enterprise_Anchor_Group"
            mobility_anchors:
              - device_name: "WLC_Enterprise_1"
                device_ip_address: "192.168.0.10"
                device_mac_address: '00:1A:2B:3C:4D:5E'
                device_type: "IOS-XE"
                device_priority: 1
                device_nat_ip_address: "10.0.0.10"
                mobility_group_name: Enterprise_Mobility_Group
                managed_device: false
              - device_name: "WLC_Enterprise_2"
                device_ip_address: "192.168.0.11"
                device_mac_address: '00:1A:2B:3C:4D:5F'
                device_type: "AIREOS"
                device_priority: 2
                device_nat_ip_address: "10.0.0.11"
                mobility_group_name: "Enterprise_Mobility_Group"
                managed_device: false
          - anchor_group_name: "Branch_Anchor_Group"
            mobility_anchors:
              - device_name: "WLC_Branch_1"
                device_ip_address: "192.168.0.13"
                device_mac_address: "00:1A:2B:3C:4D:5A"
                device_type: "IOS-XE"
                device_priority: 1
                device_nat_ip_address: "10.0.0.12"
                mobility_group_name: Branch_Mobility_Group
                managed_device: false
              - device_name: "WLC_Branch_2"
                device_ip_address: "192.168.0.14"
                device_mac_address: "00:1A:2B:3C:4D:5B"
                device_type: "AIREOS"
                device_priority: 2
                device_nat_ip_address: "10.0.0.13"
                mobility_group_name: "Branch_Mobility_Group"
                managed_device: false
          - anchor_group_name: DataCenter_Anchor_Group
            mobility_anchors:
              - device_name: "WLC_DC_1"
                device_ip_address: "192.168.0.15"
                device_mac_address: "00:1A:2B:3C:4D:09"
                device_type: "IOS-XE"
                device_priority: 1
                device_nat_ip_address: "10.0.0.14"
                mobility_group_name: "DataCenter_Mobility_Group"
                managed_device: false
              - device_name: "WLC_DC_2"
                device_ip_address: "192.168.0.16"
                device_mac_address: "00:1A:2B:3C:4D:08"
                device_type: "AIREOS"
                device_priority: 2
                device_nat_ip_address: "10.0.0.15"
                mobility_group_name: "DataCenter_Mobility_Group"
                managed_device: false
- name: Update Anchor Groups
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - anchor_groups:
          - anchor_group_name: "Enterprise_Anchor_Group"
            mobility_anchors:
              - device_name: "WLC_Enterprise_1"
                device_ip_address: "192.168.0.10"
                device_mac_address: "00:1A:2B:3C:4D:5E"
                device_type: "IOS-XE"
                device_priority: 1
                device_nat_ip_address: "10.0.0.10"
                mobility_group_name: "Enterprise_Mobility_Group"
                managed_device: false
              - device_name: "WLC_Enterprise_2"
                device_ip_address: "192.168.0.11"
                device_mac_address: "00:1A:2B:3C:4D:5F"
                device_type: "AIREOS"
                device_priority: 3
                device_nat_ip_address: "10.0.0.11"
                mobility_group_name: "Enterprise_Mobility_Group"
                managed_device: false
          - anchor_group_name: "Branch_Anchor_Group"
            mobility_anchors:
              - device_name: "WLC_Branch_1"
                device_ip_address: "192.168.0.13"
                device_mac_address: "00:1A:2B:3C:4D:5A"
                device_type: "IOS-XE"
                device_priority: 1
                device_nat_ip_address: "10.0.0.12"
                mobility_group_name: "Branch_Mobility_Group"
                managed_device: false
              - device_name: Device2
                device_ip_address: "192.168.0.14"
                device_mac_address: "00:1A:2B:3C:4D:5B"
                device_type: "AIREOS"
                device_priority: 3
                device_nat_ip_address: "10.0.0.13"
                mobility_group_name: "Branch_Mobility_Group"
                managed_device: false
              - device_name: "NY-IAC-EWLC.cisco.local"
                device_ip_address: "204.192.6.200"
                device_priority: 2
                managed_device: true
          - anchor_group_name: "DataCenter_Anchor_Group"
            mobility_anchors:
              - device_name: "WLC_DC_1"
                device_ip_address: "192.168.0.15"
                device_mac_address: "00:1A:2B:3C:4D:09"
                device_type: "IOS-XE"
                device_priority: 2
                device_nat_ip_address: "10.0.0.14"
                mobility_group_name: "DataCenter_Mobility_Group"
                managed_device: false
- name: Delete Anchor Groups
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - anchor_groups:
          - anchor_group_name: "Enterprise_Anchor_Group"
          - anchor_group_name: "Branch_Anchor_Group"
          - anchor_group_name: "DataCenter_Anchor_Group"

# Create 802.11be profiles
- name: Add 802.11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - 802_11_be_profiles:
          - profile_name: "wifi7_office_profile"
            ofdma_up_link: true
            ofdma_down_link: true
            mu_mimo_up_link: false
            mu_mimo_down_link: false
            ofdma_multi_ru: false

# Update 802.11be profiles
- name: Update 802.11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - 802_11_be_profiles:
          - profile_name: "wifi7_office_profile"
            ofdma_up_link: true              # Changed from false to true
            ofdma_down_link: true
            mu_mimo_up_link: false
            mu_mimo_down_link: true          # Changed from false to true
            ofdma_multi_ru: false

# Delete 802.11be profiles
- name: Delete 802.11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - 802_11_be_profiles:
          - profile_name: "wifi7_office_profile"

# Update flex connect configuration
- name: Delete 802.11be profiles
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: merged
    config:
      - flex_connect_configuration:
          - site_name_hierarchy: Global/USA/SAN-FRANCISCO/SF_BLD2
            vlan_id: 200

# Delete flex connect configuration
- name: Delete flex connect configuration
  cisco.dnac.wireless_design_workflow_manager:
    dnac_host: "{{dnac_host}}"
    dnac_username: "{{dnac_username}}"
    dnac_password: "{{dnac_password}}"
    dnac_verify: "{{dnac_verify}}"
    dnac_port: "{{dnac_port}}"
    dnac_version: "{{dnac_version}}"
    dnac_debug: "{{dnac_debug}}"
    dnac_log: true
    dnac_log_level: "{{dnac_log_level}}"
    state: deleted
    config:
      - flex_connect_configuration:
          - site_name_hierarchy: Global/USA/SAN-FRANCISCO/SF_BLD3
            vlan_id: 200
            remove_override_in_hierarchy: true  # only for deleted state
"""

RETURN = r"""
# Case_1: Success Scenario
response_1:
  description: A dictionary with  with the response returned by the Cisco Catalyst Center Python SDK
  returned: always
  type: dict
  sample: >
    {
      "response":
        {
          "response": String,
          "version": String
        },
      "msg": String
    }
# Case_2: Error Scenario
response_2:
  description: A string with the response returned by the Cisco Catalyst Center Python SDK
  returned: always
  type: list
  sample: >
    {
      "response": [],
      "msg": String
    }
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.dnac.plugins.module_utils.dnac import (
    DnacBase,
    validate_list_of_dicts,
)
import re


class WirelessDesign(DnacBase):
    """
    A class for managing Wireless Design operations within the Cisco DNA Center using the SDA API.
    """

    def __init__(self, module):
        """
        Initialize an instance of the class.
        Args:
          - module: The module associated with the class instance.
        Returns:
          The method does not return a value.
        """
        self.supported_states = ["merged", "deleted"]
        self.is_default_rf_profile_in_config = False
        super().__init__(module)

    def validate_input(self):
        """
        Validates the input configuration parameters for the playbook.
        Returns:
            object: An instance of the class with updated attributes:
                - self.msg: A message describing the validation result.
                - self.status: The status of the validation (either "success" or "failed").
                - self.validated_config: If successful, a validated version of the "config" parameter.
        """
        self.log("Starting validation of input configuration parameters.", "DEBUG")

        # Check if configuration is available
        if not self.config:
            self.msg = "The playbook configuration is empty or missing."
            self.set_operation_result("failed", False, self.msg, "ERROR")
            return self

        # Expected schema for configuration parameters
        self.temp_spec = {
            "ssids": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "ssid_name": {"type": "str"},
                    "ssid_type": {"type": "str"},
                    "wlan_profile_name": {"type": "str"},
                    "radio_policy": {
                        "type": "dict",
                        "radio_bands": {"type": "list"},
                        "2_dot_4_ghz_band_policy": {"type": "str"},
                        "band_select": {"type": "bool"},
                        "6_ghz_client_steering": {"type": "bool"},
                    },
                    "fast_lane": {"type": "bool"},
                    "quality_of_service": {
                        "type": "dict",
                        "egress": {"type": "str"},
                        "ingress": {"type": "str"},
                    },
                    "ssid_state": {
                        "type": "dict",
                        "admin_status": {"type": "bool"},
                        "broadcast_ssid": {"type": "bool"},
                    },
                    "l2_security": {
                        "type": "dict",
                        "l2_auth_type": {"type": "str"},
                        "ap_beacon_protection": {"type": "bool"},
                        "open_ssid": {"type": "str"},
                        "passphrase_type": {"type": "str"},
                        "passphrase": {"type": "str"},
                        "mpsk_settings": {
                            "type": "list",
                            "elements": "dict",
                            "options": {
                                "mpsk_priority": {"type": "int"},
                                "mpsk_passphrase_type": {"type": "str"},
                                "mpsk_passphrase": {"type": "str"},
                            },
                        },
                    },
                    "fast_transition": {"type": "str"},
                    "fast_transition_over_the_ds": {"type": "bool"},
                    "wpa_encryption": {"type": "list"},
                    "auth_key_management": {"type": "list"},
                    "cckm_timestamp_tolerance": {"type": "int"},
                    "l3_security": {
                        "type": "dict",
                        "l3_auth_type": {"type": "str"},
                        "auth_server": {"type": "str"},
                        "web_auth_url": {"type": "str"},
                        "enable_sleeping_client": {"type": "bool"},
                        "sleeping_client_timeout": {"type": "int"},
                    },
                    "aaa": {
                        "type": "dict",
                        "auth_servers_ip_address_list": {"type": "list"},
                        "accounting_servers_ip_address_list": {"type": "list"},
                        "aaa_override": {"type": "bool"},
                        # "identity_psk": {"type": "bool"},
                        "mac_filtering": {"type": "bool"},
                        "deny_rcm_clients": {"type": "bool"},
                        "enable_posture": {"type": "bool"},
                        "pre_auth_acl_name": {"type": "str"},
                    },
                    "mfp_client_protection": {"type": "str"},
                    "protected_management_frame": {"type": "str"},
                    "11k_neighbor_list": {"type": "bool"},
                    "coverage_hole_detection": {"type": "bool"},
                    "wlan_timeouts": {
                        "type": "dict",
                        "enable_session_timeout": {"type": "bool"},
                        "session_timeout": {"type": "int"},
                        "enable_client_exclusion_timeout": {"type": "bool"},
                        "client_exclusion_timeout": {"type": "int"},
                    },
                    "bss_transition_support": {
                        "type": "dict",
                        "bss_max_idle_service": {"type": "bool"},
                        "bss_idle_client_timeout": {"type": "int"},
                        "directed_multicast_service": {"type": "bool"},
                    },
                    "nas_id": {"type": "list"},
                    "client_rate_limit": {"type": "int"},
                    "sites_specific_override_settings": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "site_name_hierarchy": {"type": "str"},
                            "wlan_profile_name": {"type": "str"},
                            "l2_security": {
                                "type": "dict",
                                "l2_auth_type": {"type": "str"},
                                "open_ssid": {"type": "str"},
                                "passphrase": {"type": "str"},
                                "mpsk_settings": {
                                    "type": "list",
                                    "elements": "dict",
                                    "options": {
                                        "mpsk_priority": {"type": "int"},
                                        "mpsk_passphrase_type": {"type": "str"},
                                        "mpsk_passphrase": {"type": "str"},
                                    },
                                },
                            },
                            "fast_transition": {"type": "str"},
                            "fast_transition_over_the_ds": {"type": "bool"},
                            "wpa_encryption": {"type": "list"},
                            "auth_key_management": {"type": "list"},
                            "aaa": {
                                "type": "dict",
                                "auth_servers_ip_address_list": {"type": "list"},
                                "accounting_servers_ip_address_list": {"type": "list"},
                                "aaa_override": {"type": "bool"},
                                "mac_filtering": {"type": "bool"},
                            },
                            "protected_management_frame": {"type": "str"},
                            "nas_id": {"type": "list"},
                            "client_rate_limit": {"type": "int"},
                            "remove_override_in_hierarchy": {"type": "bool"},
                        },
                    },
                },
            },
            "interfaces": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "interface_name": {"type": "str"},
                    "vlan_id": {"type": "int"},
                },
            },
            "power_profiles": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "power_profile_name": {"type": "str"},
                    "power_profile_description": {"type": "str"},
                    "rules": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "interface_type": {"type": "str"},
                            "interface_id": {"type": "str"},
                            "parameter_type": {"type": "str"},
                            "parameter_value": {"type": "str"},
                        },
                    },
                },
            },
            "access_point_profiles": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "access_point_profile_name": {"type": "str"},
                    "access_point_profile_description": {"type": "str"},
                    # "device_type": {"type": "str"},
                    "remote_teleworker": {"type": "bool"},
                    "management_settings": {
                        "type": "dict",
                        "access_point_authentication": {"type": "str"},
                        "dot1x_username": {"type": "str"},
                        "dot1x_password": {"type": "str"},
                        "ssh_enabled": {"type": "bool"},
                        "telnet_enabled": {"type": "bool"},
                        "management_username": {"type": "str"},
                        "management_password": {"type": "str"},
                        "management_enable_password": {"type": "str"},
                        "cdp_state": {"type": "bool"},
                    },
                    "security_settings": {
                        "type": "dict",
                        "awips": {"type": "bool"},
                        "awips_forensic": {"type": "bool"},
                        "rogue_detection_enabled": {"type": "bool"},
                        "minimum_rssi": {"type": "int"},
                        "transient_interval": {"type": "int"},
                        "report_interval": {"type": "int"},
                        "pmf_denial": {"type": "bool"},
                    },
                    "mesh_enabled": {"type": "bool"},
                    "mesh_settings": {
                        "type": "dict",
                        "range": {"type": "int"},
                        "backhaul_client_access": {"type": "bool"},
                        "rap_downlink_backhaul": {"type": "str"},
                        "ghz_5_backhaul_data_rates": {"type": "str"},
                        "ghz_2_4_backhaul_data_rates": {"type": "str"},
                        "bridge_group_name": {"type": "str"},
                    },
                    "power_settings": {
                        "type": "dict",
                        "ap_power_profile_name": {"type": "str"},
                        "calendar_power_profiles": {
                            "type": "list",
                            "elements": "dict",
                            "required": False,
                            "options": {
                                "ap_power_profile_name": {"type": "str"},
                                "scheduler_type": {"type": "str"},
                                "scheduler_start_time": {"type": "str"},
                                "scheduler_end_time": {"type": "str"},
                                "scheduler_days_list": {"type": "list"},
                                "scheduler_dates_list": {"type": "list"},
                            },
                        },
                    },
                    "country_code": {"type": "str"},
                    "time_zone": {"type": "str"},
                    "time_zone_offset_hour": {"type": "int"},
                    "time_zone_offset_minutes": {"type": "int"},
                    "maximum_client_limit": {"type": "int"},
                },
            },
            "radio_frequency_profiles": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "radio_frequency_profile_name": {"type": "str"},
                    "default_rf_profile": {"type": "bool"},
                    "radio_bands": {"type": "list"},
                    "radio_bands_2_4ghz_settings": {
                        "type": "dict",
                        "parent_profile": {"type": "str"},
                        "dca_channels_list": {"type": "list"},
                        "supported_data_rates_list": {"type": "list"},
                        "mandatory_data_rates_list": {"type": "list"},
                        "minimum_power_level": {"type": "int"},
                        "maximum_power_level": {"type": "int"},
                        "rx_sop_threshold": {"type": "str"},
                        "custom_rx_sop_threshold": {"type": "int"},
                        "tpc_power_threshold": {"type": "int"},
                        "coverage_hole_detection": {
                            "type": "dict",
                            "minimum_client_level": {"type": "int"},
                            "data_rssi_threshold": {"type": "int"},
                            "voice_rssi_threshold": {"type": "int"},
                            "exception_level": {"type": "int"},
                        },
                        "client_limit": {"type": "int"},
                        "spatial_reuse": {
                            "type": "dict",
                            "non_srg_obss_pd": {"type": "bool"},
                            "non_srg_obss_pd_max_threshold": {"type": "int"},
                            "srg_obss_pd": {"type": "bool"},
                            "srg_obss_pd_min_threshold": {"type": "int"},
                            "srg_obss_pd_max_threshold": {"type": "int"},
                        },
                    },
                    "radio_bands_5ghz_settings": {
                        "type": "dict",
                        "parent_profile": {"type": "str"},
                        "channel_width": {"type": "str"},
                        "preamble_puncturing": {"type": "bool"},
                        "zero_wait_dfs": {"type": "bool"},
                        "dca_channels_list": {"type": "list"},
                        "supported_data_rates_list": {"type": "list"},
                        "mandatory_data_rates_list": {"type": "list"},
                        "minimum_power_level": {"type": "int"},
                        "maximum_power_level": {"type": "int"},
                        "rx_sop_threshold": {"type": "str"},
                        "custom_rx_sop_threshold": {"type": "int"},
                        "tpc_power_threshold": {"type": "int"},
                        "coverage_hole_detection": {
                            "type": "dict",
                            "minimum_client_level": {"type": "int"},
                            "data_rssi_threshold": {"type": "int"},
                            "voice_rssi_threshold": {"type": "int"},
                            "exception_level": {"type": "int"},
                        },
                        "client_limit": {"type": "int"},
                        "flexible_radio_assignment": {
                            "type": "dict",
                            "client_aware": {"type": "bool"},
                            "client_select": {"type": "int"},
                            "client_reset": {"type": "int"},
                        },
                        "spatial_reuse": {
                            "type": "dict",
                            "non_srg_obss_pd": {"type": "bool"},
                            "non_srg_obss_pd_max_threshold": {"type": "int"},
                            "srg_obss_pd": {"type": "bool"},
                            "srg_obss_pd_min_threshold": {"type": "int"},
                            "srg_obss_pd_max_threshold": {"type": "int"},
                        },
                    },
                    "radio_bands_6ghz_settings": {
                        "type": "dict",
                        "parent_profile": {"type": "str"},
                        "minimum_dbs_channel_width": {"type": "int"},
                        "maximum_dbs_channel_width": {"type": "int"},
                        "preamble_puncturing": {"type": "bool"},
                        "psc_enforcing_enabled": {"type": "bool"},
                        "dca_channels_list": {"type": "list"},
                        "supported_data_rates_list": {"type": "list"},
                        "mandatory_data_rates_list": {"type": "list"},
                        "minimum_power_level": {"type": "int"},
                        "maximum_power_level": {"type": "int"},
                        "rx_sop_threshold": {"type": "str"},
                        "custom_rx_sop_threshold": {"type": "int"},
                        "tpc_power_threshold": {"type": "int"},
                        "coverage_hole_detection": {
                            "type": "dict",
                            "minimum_client_level": {"type": "int"},
                            "data_rssi_threshold": {"type": "int"},
                            "voice_rssi_threshold": {"type": "int"},
                            "exception_level": {"type": "int"},
                        },
                        "client_limit": {"type": "int"},
                        "flexible_radio_assignment": {
                            "type": "dict",
                            "client_reset_count": {"type": "int"},
                            "client_utilization_threshold": {"type": "int"},
                        },
                        "discovery_frames_6ghz": {"type": "str"},
                        "broadcast_probe_response_interval": {"type": "int"},
                        "multi_bssid": {
                            "type": "dict",
                            "dot_11ax_parameters": {
                                "type": "dict",
                                "ofdma_downlink": {"type": "bool"},
                                "ofdma_uplink": {"type": "bool"},
                                "mu_mimo_downlink": {"type": "bool"},
                                "mu_mimo_uplink": {"type": "bool"},
                            },
                            "dot_11be_parameters": {
                                "type": "dict",
                                "ofdma_downlink": {"type": "bool"},
                                "ofdma_uplink": {"type": "bool"},
                                "mu_mimo_downlink": {"type": "bool"},
                                "mu_mimo_uplink": {"type": "bool"},
                                "ofdma_multi_ru": {"type": "bool"},
                            },
                            "target_waketime": {"type": "bool"},
                            "twt_broadcast_support": {"type": "bool"},
                        },
                        "spatial_reuse": {
                            "type": "dict",
                            "non_srg_obss_pd": {"type": "bool"},
                            "non_srg_obss_pd_max_threshold": {"type": "int"},
                            "srg_obss_pd": {"type": "bool"},
                            "srg_obss_pd_min_threshold": {"type": "int"},
                            "srg_obss_pd_max_threshold": {"type": "int"},
                        },
                    },
                },
            },
            "anchor_groups": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "anchor_group_name": {"type": "str"},
                    "mobility_anchors": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "device_name": {"type": "str"},
                            "device_ip_address": {"type": "str"},
                            "device_mac_address": {"type": "str"},
                            "device_type": {"type": "str"},
                            "device_priority": {"type": "int"},
                            "device_nat_ip_address": {"type": "str"},
                            "mobility_group_name": {"type": "str"},
                            "managed_device": {"type": "bool"},
                        },
                    },
                },
            },
            "feature_template_config": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "aaa_radius_attribute": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "design_name": {"type": "str"},
                            "new_design_name": {"type": "str"},
                            "called_station_id": {"type": "str"},
                            "unlocked_attributes": {"type": "bool", "required": False},
                        },
                    },
                    "advanced_ssid": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "design_name": {"type": "str"},
                            "feature_attributes": {
                                "type": "dict",
                                "required": False,
                                "options": {
                                    "peer2peer_blocking": {
                                        "type": "str",
                                        "choices": ["DISABLE", "DROP", "FORWARD_UP", "ALLOW_PVT_GROUP"]
                                    },
                                    "passive_client": {"type": "bool", "default": False},
                                    "prediction_optimization": {"type": "bool", "default": False},
                                    "dual_band_neighbor_list": {"type": "bool", "default": False},
                                    "radius_nac_state": {"type": "bool", "default": False},
                                    "dhcp_required": {"type": "bool", "default": False},
                                    "dhcp_server": {"type": "str"},
                                    "flex_local_auth": {"type": "bool", "default": False},
                                    "target_wakeup_time": {"type": "bool", "default": False},
                                    "downlink_ofdma": {"type": "bool", "default": False},
                                    "uplink_ofdma": {"type": "bool", "default": False},
                                    "downlink_mu_mimo": {"type": "bool", "default": False},
                                    "uplink_mu_mimo": {"type": "bool", "default": False},
                                    "dot11ax": {"type": "bool", "default": False},
                                    "aironet_ie_support": {"type": "bool", "default": False},
                                    "load_balancing": {"type": "bool", "default": False},
                                    "dtim_period_5ghz": {"type": "int"},
                                    "dtim_period_24ghz": {"type": "int"},
                                    "scan_defer_time": {"type": "int"},
                                    "max_clients": {"type": "int"},
                                    "max_clients_per_radio": {"type": "int"},
                                    "max_clients_per_ap": {"type": "int"},
                                    "wmm_policy": {"type": "str", "choices": ["DISABLED", "ALLOWED", "REQUIRED"]},
                                    "multicast_buffer": {"type": "bool", "default": False},
                                    "multicast_buffer_value": {"type": "int"},
                                    "media_stream_multicast_direct": {"type": "bool", "default": False},
                                    "mu_mimo_11ac": {"type": "bool", "default": False},
                                    "wifi_to_cellular_steering": {"type": "bool", "default": False},
                                    "wifi_alliance_agile_multiband": {"type": "bool", "default": False},
                                    "fastlane_asr": {"type": "bool", "default": False},
                                    "dot11v_bss_max_idle_protected": {"type": "bool", "default": False},
                                    "universal_ap_admin": {"type": "bool", "default": False},
                                    "opportunistic_key_caching": {"type": "bool", "default": False},
                                    "ip_source_guard": {"type": "bool", "default": False},
                                    "dhcp_opt82_remote_id_sub_option": {"type": "bool", "default": False},
                                    "vlan_central_switching": {"type": "bool", "default": False},
                                    "call_snooping": {"type": "bool", "default": False},
                                    "send_disassociate": {"type": "bool", "default": False},
                                    "sent_486_busy": {"type": "bool", "default": False},
                                    "ip_mac_binding": {"type": "bool", "default": False},
                                    "idle_threshold": {"type": "int"},
                                    "defer_priority_0": {"type": "bool", "default": False},
                                    "defer_priority_1": {"type": "bool", "default": False},
                                    "defer_priority_2": {"type": "bool", "default": False},
                                    "defer_priority_3": {"type": "bool", "default": False},
                                    "defer_priority_4": {"type": "bool", "default": False},
                                    "defer_priority_5": {"type": "bool", "default": False},
                                    "defer_priority_6": {"type": "bool", "default": False},
                                    "defer_priority_7": {"type": "bool", "default": False},
                                    "share_data_with_client": {"type": "bool", "default": False},
                                    "advertise_support": {"type": "bool", "default": False},
                                    "advertise_pc_analytics_support": {"type": "bool", "default": False},
                                    "send_beacon_on_association": {"type": "bool", "default": False},
                                    "send_beacon_on_roam": {"type": "bool", "default": False},
                                    "fast_transition_reassociation_timeout": {"type": "int"},
                                    "mdns_mode": {
                                        "type": "str",
                                        "choices": ["MDNS_SD_BRIDGING", "MDNS_SD_DROP", "MDNS_SD_GATEWAY"]
                                    },
                                },
                            },
                            "unlocked_attributes": {"type": "list", "elements": "str", "required": False},
                        },
                    },
                    "clean_air_configuration": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "design_name": {"type": "str"},
                            "feature_attributes": {
                                "type": "dict",
                                "required": False,
                                "options": {
                                    "radio_band": {
                                        "type": "str",
                                        "choices": ["2_4GHZ", "5GHZ", "6GHZ"]
                                    },
                                    "clean_air": {"type": "bool", "default": False},
                                    "clean_air_device_reporting": {"type": "bool", "default": False},
                                    "persistent_device_propagation": {"type": "bool", "default": False},
                                    "description": {"type": "str"},
                                    "interferers_features": {
                                        "type": "dict",
                                        "required": False,
                                        "options": {
                                            "ble_beacon": {"type": "bool", "default": False},
                                            "bluetooth_paging_inquiry": {"type": "bool", "default": False},
                                            "bluetooth_sco_acl": {"type": "bool", "default": False},
                                            "continuous_transmitter": {"type": "bool", "default": False},
                                            "generic_dect": {"type": "bool", "default": False},
                                            "generic_tdd": {"type": "bool", "default": False},
                                            "jammer": {"type": "bool", "default": False},
                                            "microwave_oven": {"type": "bool", "default": False},
                                            "motorola_canopy": {"type": "bool", "default": False},
                                            "si_fhss": {"type": "bool", "default": False},
                                            "spectrum80211_fh": {"type": "bool", "default": False},
                                            "spectrum80211_non_standard_channel": {"type": "bool", "default": False},
                                            "spectrum802154": {"type": "bool", "default": False},
                                            "spectrum_inverted": {"type": "bool", "default": False},
                                            "super_ag": {"type": "bool", "default": False},
                                            "video_camera": {"type": "bool", "default": False},
                                            "wimax_fixed": {"type": "bool", "default": False},
                                            "wimax_mobile": {"type": "bool", "default": False},
                                            "xbox": {"type": "bool", "default": False},
                                        },
                                    },
                                },
                            },
                            "unlocked_attributes": {"type": "list", "elements": "str", "required": False},
                        },
                    },
                    "dot11ax_configuration": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "design_name": {"type": "str"},
                            "feature_attributes": {
                                "type": "dict",
                                "required": False,
                                "options": {
                                    "radio_band": {"type": "str"},
                                    "bss_color": {"type": "bool", "default": False},
                                    "target_waketime_broadcast": {"type": "bool", "default": False},
                                    "non_srg_obss_pd_max_threshold": {"type": "int"},
                                    "target_wakeup_time_11ax": {"type": "bool", "default": False},
                                    "obss_pd": {"type": "bool", "default": False},
                                    "multiple_bssid": {"type": "bool", "default": False},
                                },
                            },
                            "unlocked_attributes": {"type": "list", "elements": "str", "required": False},
                        },
                    },
                    "dot11be_configuration": {
                        "type": "list",
                        "required": False,
                        "elements": "dict",
                        "options": {
                            "design_name": {"type": "str"},
                            "feature_attributes": {
                                "type": "dict",
                                "required": False,
                                "options": {
                                    "dot11be_status": {"type": "bool", "default": False},
                                    "radio_band": {"type": "str", "choices": ["2_4GHZ", "5GHZ", "6GHZ"]},
                                },
                            },
                            "unlocked_attributes": {
                                "type": "list",
                                "elements": "str",
                                "required": False,
                            },
                        },
                    },
                    "event_driven_rrm_configuration": {
                        "type": "list",
                        "required": False,
                        "elements": "dict",
                        "options": {
                            "design_name": {
                                "type": "str",
                                "required": True,
                            },
                            "feature_attributes": {
                                "type": "dict",
                                "required": True,
                                "options": {
                                    "radio_band": {
                                        "type": "str",
                                        "required": True,
                                        "choices": ["2_4GHZ", "5GHZ"],
                                    },
                                    "event_driven_rrm_enable": {
                                        "type": "bool",
                                        "required": False,
                                        "default": False,
                                    },
                                    "event_driven_rrm_threshold_level": {
                                        "type": "str",
                                        "required": False,
                                        "choices": ["LOW", "MEDIUM", "HIGH", "CUSTOM"],
                                    },
                                    "event_driven_rrm_custom_threshold_val": {
                                        "type": "int",
                                        "required": False,
                                    },
                                },
                            },
                            "unlocked_attributes": {
                                "type": "list",
                                "elements": "str",
                                "required": False,
                            },
                        },
                    },
                    "flexconnect_configuration": {
                        "type": "list",
                        "required": False,
                        "elements": "dict",
                        "options": {
                            "design_name": {"type": "str"},
                            "feature_attributes": {
                                "type": "dict",
                                "required": False,
                                "options": {
                                    "overlap_ip_enable": {"type": "bool", "default": False},
                                },
                            },
                            "unlocked_attributes": {
                                "type": "list",
                                "elements": "str",
                                "required": False,
                            },
                        },
                    },
                    "multicast_configuration": {
                        "type": "list",
                        "required": False,
                        "elements": "dict",
                        "options": {
                            "design_name": {
                                "type": "str",
                                "required": True,
                                "maxlength": 64
                            },
                            "feature_attributes": {
                                "type": "dict",
                                "required": True,
                                "options": {
                                    "global_multicast_enabled": {"type": "bool", "required": True},
                                    "multicast_ipv4_mode": {
                                        "type": "str",
                                        "required": False,
                                        "choices": ["UNICAST", "MULTICAST"]
                                    },
                                    "multicast_ipv4_address": {
                                        "type": "str",
                                        "required": False
                                    },
                                    "multicast_ipv6_mode": {
                                        "type": "str",
                                        "required": False,
                                        "choices": ["UNICAST", "MULTICAST"]
                                    },
                                    "multicast_ipv6_address": {
                                        "type": "str",
                                        "required": False
                                    },
                                },
                            },
                            "unlocked_attributes": {
                                "type": "list",
                                "elements": "str",
                                "required": False,
                            },
                        },
                    },
                    "rrm_fra_configuration": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "design_name": {
                                "type": "str",
                                "required": True,
                                "maxlength": 64
                            },
                            "feature_attributes": {
                                "type": "dict",
                                "required": True,
                                "options": {
                                    "radio_band": {
                                        "type": "str",
                                        "required": True,
                                        "choices": ["2_4GHZ_5GHZ", "5GHZ_6GHZ"]
                                    },
                                    "fra_freeze": {
                                        "type": "bool",
                                        "required": False,
                                        "default": False
                                    },
                                    "fra_status": {
                                        "type": "bool",
                                        "required": False,
                                        "default": False
                                    },
                                    "fra_interval": {
                                        "type": "int",
                                        "required": False
                                    },
                                    "fra_sensitivity": {
                                        "type": "str",
                                        "required": False,
                                        "choices": ["LOW", "MEDIUM", "HIGH", "HIGHER", "EVEN_HIGHER", "SUPER_HIGH"]
                                    },
                                },
                            },
                            "unlocked_attributes": {
                                "type": "list",
                                "elements": "str",
                                "required": False,
                            },
                        },
                    },
                    "rrm_general_configuration": {
                        "type": "list",
                        "elements": "dict",
                        "required": False,
                        "options": {
                            "design_name": {
                                "type": "str",
                                "required": True,
                                "maxlength": 64
                            },
                            "feature_attributes": {
                                "type": "dict",
                                "required": True,
                                "options": {
                                    "radio_band": {
                                        "type": "str",
                                        "required": True,
                                        "choices": ["2_4GHZ", "5GHZ", "6GHZ"]
                                    },
                                    "monitoring_channels": {
                                        "type": "str",
                                        "required": False,
                                        "choices": [
                                            "MONITORING_CHANNELS_ALL",
                                            "MONITORING_CHANNELS_COUNTRY",
                                            "MONITORING_CHANNELS_DCA"
                                        ]
                                    },
                                    "neighbor_discover_type": {
                                        "type": "str",
                                        "required": False,
                                        "choices": [
                                            "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT",
                                            "NEIGHBOR_DISCOVER_TYPE_PROTECTED"
                                        ]
                                    },
                                    "throughput_threshold": {
                                        "type": "int",
                                        "required": False
                                    },
                                    "coverage_hole_detection": {
                                        "type": "bool",
                                        "required": False,
                                        "default": False
                                    },
                                },
                            },
                            "unlocked_attributes": {
                                "type": "list",
                                "elements": "str",
                                "required": False,
                            },
                        },
                    }
                },
            },
            "802_11_be_profiles": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "profile_name": {"type": "str"},
                    "ofdma_up_link": {"type": "bool", "default": True},
                    "ofdma_down_link": {"type": "bool", "default": True},
                    "mu_mimo_up_link": {"type": "bool", "default": False},
                    "mu_mimo_down_link": {"type": "bool", "default": False},
                    "ofdma_multi_ru": {"type": "bool", "default": False},
                },
            },
            "flex_connect_configuration": {
                "type": "list",
                "elements": "dict",
                "required": False,
                "options": {
                    "site_name_hierarchy": {"type": "str"},
                    "vlan_id": {"type": "int"},
                    "remove_override_in_hierarchy": {"type": "bool", "default": True}
                }
            }
        }

        # Validate params against the expected schema
        valid_temp, invalid_params = validate_list_of_dicts(self.config, self.temp_spec)

        # Check if any invalid parameters were found
        if invalid_params:
            self.msg = "Invalid parameters in playbook: {0}".format(invalid_params)
            self.set_operation_result("failed", False, self.msg, "ERROR")
            return self

        # Set the validated configuration and update the result with success status
        self.validated_config = valid_temp
        self.msg = "Successfully validated playbook configuration parameters using 'validated_input': {0}".format(
            str(valid_temp)
        )
        self.set_operation_result("success", False, self.msg, "INFO")
        return self

    def verify_delete_rrm_general_requirement(self, rrm_general_list):
        """
        Determines which RRM General configuration templates need to be deleted
        based on the requested parameters.
        Args:
            rrm_general_list (list): A list of dicts containing the requested RRM General
                                    configuration parameters for deletion.
                                    Example: [{"design_name": "rrm_general_design"}]
        Returns:
            list: A list of RRM General configuration templates scheduled for deletion,
                including their IDs.
        """
        delete_list = []

        self.log("Starting verification of RRM General configurations for deletion.", "INFO")

        # Retrieve all existing RRM General configurations
        existing_blocks = self.get_rrm_general_profiles()
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []))

        self.log("Existing RRM General configurations: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary for quick lookup
        existing_dict = {cfg["designName"]: cfg for cfg in instances}
        self.log("Converted existing RRM General configs to dictionary.", "DEBUG")

        # Iterate over requested configurations
        for index, requested_cfg in enumerate(rrm_general_list, start=1):
            design_name = requested_cfg.get("design_name")
            self.log(
                "Iteration {0}: Checking RRM General config '{1}' for deletion.".format(
                    index, design_name
                ),
                "DEBUG",
            )

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                cfg_to_delete = requested_cfg.copy()
                cfg_to_delete["id"] = existing.get("id")
                delete_list.append(cfg_to_delete)
                self.log(
                    "Iteration {0}: RRM General config '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: RRM General config '{1}' not found -> no deletion required.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "RRM General configurations scheduled for deletion: {0} - {1}".format(
                len(delete_list), delete_list
            ),
            "DEBUG",
        )

        return delete_list

    def verify_create_update_rrm_general_requirement(self, rrm_general_list):
        """
        Validates and compares desired RRM General profiles against existing ones and determines
        which need to be created, updated, or left unchanged.
        Args:
            rrm_general_list (list): A list of dicts containing the desired RRM General configuration
                                    parameters for creation or update.
                                    Example: [{"design_name": "rrm_general_design", "feature_attributes": {...}, "unlocked_attributes": [...]}]
        Returns:
            tuple: Three lists containing RRM General configurations to be created, updated, and not updated:
            - add_list (list): Payloads for new RRM General configurations to create
            - update_list (list): Payloads for existing RRM General configurations to update (includes "id" field)
            - no_update_list (list): Existing RRM General configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        self.log("Starting verification of RRM General configurations (Add/Update).", "INFO")

        # --- Inline universal normalizer/validator ---
        def normalize_value(value, value_type="str", choices=None, min_val=None, max_val=None):
            """
            value_type: "str", "enum", "bool", "int"
            - enum: returns UPPERCASE string and validates against choices (if provided)
            - bool: accepts bool or "true"/"false" strings
            - int : casts to int and validates range if min/max provided
            """
            if value is None:
                return None

            if value_type == "bool":
                if isinstance(value, bool):
                    return value
                if isinstance(value, str) and value.lower() in ("true", "false"):
                    return value.lower() == "true"
                return value

            if value_type == "int":
                try:
                    v = int(value)
                    if min_val is not None and v < min_val:
                        raise ValueError("Value {0} below min {1}".format(v, min_val))
                    if max_val is not None and v > max_val:
                        raise ValueError("Value {0} above max {1}".format(v, max_val))
                    return v
                except Exception:
                    return value

            if value_type == "enum":
                v = str(value).upper()
                if choices and v not in choices:
                    raise ValueError("Invalid enum value '{0}'. Allowed: {1}".format(v, sorted(list(choices))))
                return v

            # default: string normalization
            return str(value)

        # --- Constants / choices ---
        allowed_bands = {"2_4GHZ", "5GHZ", "6GHZ"}
        allowed_monitoring = {
            "MONITORING_CHANNELS_ALL",
            "MONITORING_CHANNELS_COUNTRY",
            "MONITORING_CHANNELS_DCA",
        }
        allowed_neighbor = {
            "NEIGHBOR_DISCOVER_TYPE_TRANSPARENT",
            "NEIGHBOR_DISCOVER_TYPE_PROTECTED",
        }
        thr_min, thr_max = 1000, 10_000_000

        # snake -> camel map for unlocked attributes
        unlock_map = {
            "radio_band": "radioBand",
            "monitoring_channels": "monitoringChannels",
            "neighbor_discover_type": "neighborDiscoverType",
            "throughput_threshold": "throughputThreshold",
            "coverage_hole_detection": "coverageHoleDetection",
        }

        # Fetch existing summaries and flatten
        existing_blocks = self.get_rrm_general_profiles()
        instances = []
        for block in (existing_blocks or []):
            instances.extend(block.get("instances", []) or [])
        self.log("Existing RRM General profiles: {0}".format(instances), "DEBUG")

        existing_dict = {inst["designName"]: inst for inst in instances}
        self.log("Converted existing RRM General configs to dictionary.", "DEBUG")

        for index, requested_cfg in enumerate(rrm_general_list or [], start=1):
            design_name = requested_cfg.get("design_name")
            fa_req = requested_cfg.get("feature_attributes") or {}
            unl_req = requested_cfg.get("unlocked_attributes") or []

            self.log("Iteration {0}: Checking RRM General config '{1}'.".format(index, design_name), "DEBUG")

            # --- VALIDATION (input, snake_case) + normalization ---
            try:
                radio_band = normalize_value(fa_req.get("radio_band"), "enum", choices=allowed_bands)
            except ValueError as e:
                self.msg = "Invalid radio_band for design '{0}': {1}".format(design_name, e)
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            monitoring_channels = None
            if "monitoring_channels" in fa_req and fa_req.get("monitoring_channels") is not None:
                try:
                    monitoring_channels = normalize_value(
                        fa_req.get("monitoring_channels"), "enum", choices=allowed_monitoring
                    )
                except ValueError as e:
                    self.msg = "Invalid monitoring_channels for design '{0}': {1}".format(design_name, e)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            neighbor_discover_type = None
            if "neighbor_discover_type" in fa_req and fa_req.get("neighbor_discover_type") is not None:
                try:
                    neighbor_discover_type = normalize_value(
                        fa_req.get("neighbor_discover_type"), "enum", choices=allowed_neighbor
                    )
                except ValueError as e:
                    self.msg = "Invalid neighbor_discover_type for design '{0}': {1}".format(design_name, e)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            throughput_threshold = None
            if "throughput_threshold" in fa_req and fa_req.get("throughput_threshold") is not None:
                tt = normalize_value(fa_req.get("throughput_threshold"), "int", min_val=thr_min, max_val=thr_max)
                if not isinstance(tt, int):
                    self.msg = ("throughput_threshold must be integer within [{0}..{1}] for design '{2}', got '{3}'"
                                .format(thr_min, thr_max, design_name, fa_req.get("throughput_threshold")))
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
                throughput_threshold = tt

            coverage_hole_detection = None
            if "coverage_hole_detection" in fa_req and fa_req.get("coverage_hole_detection") is not None:
                chd = normalize_value(fa_req.get("coverage_hole_detection"), "bool")
                if not isinstance(chd, bool):
                    self.msg = ("coverage_hole_detection must be boolean for design '{0}', got '{1}'"
                                .format(design_name, fa_req.get("coverage_hole_detection")))
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
                coverage_hole_detection = chd

            # Info-only constraint: 2_4GHZ support requires IOS-XE >= 17.9.1 (cannot verify here)
            if radio_band == "2_4GHZ":
                self.log("Note: radio_band=2_4GHZ requires IOS-XE >= 17.9.1.", "DEBUG")

            # Validate unlocked attributes reference only feature attributes (snake case on input)
            if unl_req:
                allowed_unlock_snake = set(unlock_map.keys())
                bad = [u for u in unl_req if u not in allowed_unlock_snake]
                if bad:
                    self.msg = ("Unlocked attributes {0} are invalid for design '{1}'. "
                                "Allowed: {2}").format(bad, design_name, sorted(allowed_unlock_snake))
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # --- Build controller payload (camelCase) ---
            fa_payload = {"radioBand": radio_band}
            if monitoring_channels is not None:
                fa_payload["monitoringChannels"] = monitoring_channels
            if neighbor_discover_type is not None:
                fa_payload["neighborDiscoverType"] = neighbor_discover_type
            if throughput_threshold is not None:
                fa_payload["throughputThreshold"] = throughput_threshold
            if coverage_hole_detection is not None:
                fa_payload["coverageHoleDetection"] = coverage_hole_detection

            payload = {
                "designName": design_name,
                "featureAttributes": fa_payload,
            }

            if unl_req:
                payload["unlockedAttributes"] = [unlock_map[u] for u in unl_req]

            # --- Compare against existing ---
            existing = existing_dict.get(design_name)
            if not existing:
                add_list.append(payload)
                self.log("RRM General '{0}' scheduled for ADD.".format(design_name), "INFO")
                continue

            # Prefer detailed fetch if available
            details = {}
            try:
                details = self.get_rrm_general_profile_details(existing.get("id")) or {}
            except Exception:
                details = existing  # fallback to summary if detail API not available

            existing_fa = (details.get("featureAttributes") or existing.get("featureAttributes") or {})
            existing_unl = (details.get("unlockedAttributes") or existing.get("unlockedAttributes") or [])

            # normalize enums for fair compare
            def U(v):
                return str(v).upper() if isinstance(v, str) else v

            needs_update = (
                U(existing_fa.get("radioBand")) != U(fa_payload.get("radioBand")) or
                U(existing_fa.get("monitoringChannels")) != U(fa_payload.get("monitoringChannels")) or
                U(existing_fa.get("neighborDiscoverType")) != U(fa_payload.get("neighborDiscoverType")) or
                normalize_value(existing_fa.get("throughputThreshold"), "int")
                != normalize_value(fa_payload.get("throughputThreshold"), "int") or
                normalize_value(existing_fa.get("coverageHoleDetection"), "bool")
                != normalize_value(fa_payload.get("coverageHoleDetection"), "bool") or
                set(existing_unl) != set(payload.get("unlockedAttributes", []))
            )

            if needs_update:
                payload["id"] = existing.get("id")
                update_list.append(payload)
                self.log("RRM General '{0}' scheduled for UPDATE.".format(design_name), "INFO")
            else:
                no_update_list.append(details or existing)
                self.log("RRM General '{0}' -> NO CHANGE.".format(design_name), "INFO")

        self.log(
            "RRM General - Add: {0}, Update: {1}, No-Change: {2}".format(
                len(add_list), len(update_list), len(no_update_list)
            ),
            "DEBUG",
        )
        return add_list, update_list, no_update_list

    def get_rrm_general_profile_details(self, template_id):
        """
        Retrieve detailed information for a specific RRM General configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the RRM General feature template.
        Returns:
            dict: The details of the RRM General feature template, or {} if fetch fails.
        """
        self.log("Fetching RRM General configuration details for template_id='{0}'".format(template_id), "DEBUG")

        try:
            if not template_id:
                self.log("No template_id provided for RRM General details.", "ERROR")
                return {}

            response = self.execute_get_request(
                "wireless",
                "get_r_r_m_general_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")

            details = response.get("response") or {}
            return details

        except Exception as e:
            self.log("Failed to fetch RRM General configuration details for template_id={0}: {1}".format(template_id, str(e)), "ERROR")
            return {}

    def get_rrm_general_profiles(self, design_name=None, template_type="RRM_GENERAL_CONFIGURATION"):
        """
        Retrieve existing RRM General feature templates from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
            template_type (str, optional): DNAC template type identifier.
                                        Defaults to "RRM_GENERAL_CONFIGURATION".
        Returns:
            list: A list of existing RRM General template dicts (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing RRM General Templates from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")

            existing_rrm_general = response.get("response", [])
            self.log(
                "Retrieved {0} RRM General Templates.".format(len(existing_rrm_general)),
                "DEBUG",
            )
            return existing_rrm_general

        except Exception as e:
            self.log("Failed to fetch RRM General Templates: {0}".format(str(e)), "ERROR")
            return []

    def verify_delete_rrm_fra_requirement(self, rrm_fra_list):
        """
        Determines which RRM-FRA configuration templates need to be deleted
        based on the requested parameters.
        Args:
            rrm_fra_list (list): A list of dicts containing the requested RRM-FRA
                                configuration parameters for deletion.
                                Example: [{"design_name": "fra_design_1"}]
        Returns:
            list: A list of RRM-FRA configuration templates scheduled for deletion,
                including their IDs.
        """
        delete_list = []

        self.log("Starting verification of RRM-FRA configurations for deletion.", "INFO")

        # Retrieve all existing RRM-FRA configurations
        existing_blocks = self.get_rrm_fra_profiles()
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []))

        self.log("Existing RRM-FRA configurations: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary for quick lookup
        existing_dict = {cfg.get("designName"): cfg for cfg in instances if cfg.get("designName")}
        self.log("Converted existing RRM-FRA configs to dictionary.", "DEBUG")

        # Iterate over requested configurations
        for index, requested_cfg in enumerate(rrm_fra_list, start=1):
            design_name = requested_cfg.get("design_name")
            self.log(
                "Iteration {0}: Checking RRM-FRA config '{1}' for deletion.".format(
                    index, design_name
                ),
                "DEBUG",
            )

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                cfg_to_delete = requested_cfg.copy()
                cfg_to_delete["id"] = existing.get("id")
                delete_list.append(cfg_to_delete)
                self.log(
                    "Iteration {0}: RRM-FRA config '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: RRM-FRA config '{1}' not found -> no deletion required.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "RRM-FRA configurations scheduled for deletion: {0} - {1}".format(
                len(delete_list), delete_list
            ),
            "DEBUG",
        )

        return delete_list

    def verify_create_update_rrm_fra_requirement(self, rrm_fra_list):
        """
        Compares desired RRM-FRA profiles against existing ones and determines
        which need to be created, updated, or left unchanged.
        Args:
            rrm_fra_list (list): A list of dicts containing the desired RRM-FRA configuration
                                parameters for creation or update.
                                Example: [{"design_name": "fra_design_1", "feature_attributes": {...}}]
        Returns:
            tuple: Three lists containing RRM-FRA configurations to be created, updated, and not updated:
            - add_list (list): Payloads for new RRM-FRA configurations to create
            - update_list (list): Payloads for existing RRM-FRA configurations to update (includes "id" field)
            - no_update_list (list): Existing RRM-FRA configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        existing_blocks = self.get_rrm_fra_profiles()
        self.log("Existing RRM-FRA Profiles (summary): {0}".format(existing_blocks), "DEBUG")

        existing_dict = {}
        for block in (existing_blocks or []):
            for inst in block.get("instances", []) or []:
                design_name = inst.get("designName")
                if design_name:
                    existing_dict[design_name] = inst
        self.log("Existing RRM-FRA Profiles Dict: {0}".format(existing_dict), "DEBUG")

        # Allowed values
        allowed_bands = ["2_4GHZ_5GHZ", "5GHZ_6GHZ"]
        allowed_sensitivity = ["LOW", "MEDIUM", "HIGH", "HIGHER", "EVEN_HIGHER", "SUPER_HIGH"]
        advanced_sensitivity = {"HIGHER", "EVEN_HIGHER", "SUPER_HIGH"}

        for attr in (rrm_fra_list or []):
            design_name = attr.get("design_name")
            fa = attr.get("feature_attributes") or {}
            unlocked = attr.get("unlocked_attributes", [])

            # fail early if design_name missing
            if not design_name:
                self.msg = "Missing design_name in RRM-FRA item: {0}".format(attr)
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            radio_band = fa.get("radio_band")
            fra_freeze = fa.get("fra_freeze")
            fra_status = fa.get("fra_status")
            fra_interval = fa.get("fra_interval")
            fra_sensitivity = fa.get("fra_sensitivity")

            # --- Validations (no external modules) ---
            if radio_band not in allowed_bands:
                self.msg = "Invalid radio_band '{0}' for design '{1}'. Must be one of: {2}".format(
                    radio_band, design_name, allowed_bands
                )
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            if fra_interval is not None:
                try:
                    val = int(fra_interval)
                except Exception:
                    self.msg = "fra_interval must be an integer for design '{0}'.".format(design_name)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
                if not (1 <= val <= 24):
                    self.msg = "fra_interval must be between 1 and 24 for design '{0}'.".format(design_name)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            if fra_sensitivity is not None:
                if fra_sensitivity not in allowed_sensitivity:
                    self.msg = "Invalid fra_sensitivity '{0}' for design '{1}'. Must be one of: {2}".format(
                        fra_sensitivity, design_name, allowed_sensitivity
                    )
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
                # Advanced sensitivity only valid for 2_4GHZ_5GHZ
                if (fra_sensitivity in advanced_sensitivity) and (radio_band != "2_4GHZ_5GHZ"):
                    self.msg = ("fra_sensitivity '{0}' is supported only for radio_band=2_4GHZ_5GHZ "
                                "for design '{1}'.").format(fra_sensitivity, design_name)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # Note: fra_freeze controller-version constraints cannot be validated here; log hint only.
            if fra_freeze is not None:
                if radio_band == "2_4GHZ_5GHZ":
                    self.log("Notice: fra_freeze requires controller >= 17.6 for 2_4GHZ_5GHZ.", "DEBUG")
                elif radio_band == "5GHZ_6GHZ":
                    self.log("Notice: fra_freeze requires controller >= 17.9 for 5GHZ_6GHZ.", "DEBUG")

            # Build desired payload (camelCase for controller)
            payload = {
                "designName": design_name,
                "featureAttributes": {
                    "radioBand": radio_band
                }
            }

            # Convert fra_sensitivity from Ansible format to API format
            # API expects: "Low", "Medium", "High", "Higher", "Even Higher", "Super High"
            # Ansible uses: "LOW", "MEDIUM", "HIGH", "HIGHER", "EVEN_HIGHER", "SUPER_HIGH"
            api_fra_sensitivity = None
            if fra_sensitivity is not None:
                sensitivity_map = {
                    "LOW": "Low",
                    "MEDIUM": "Medium",
                    "HIGH": "High",
                    "HIGHER": "Higher",
                    "EVEN_HIGHER": "Even Higher",
                    "SUPER_HIGH": "Super High"
                }
                api_fra_sensitivity = sensitivity_map.get(fra_sensitivity, fra_sensitivity)

            # Use a mapping and loop to set optional attributes only when provided
            fa_attr_map = {
                "fraFreeze": fra_freeze,
                "fraStatus": fra_status,
                # store fraInterval as int if provided (we validated above)
                "fraInterval": int(fra_interval) if fra_interval is not None else None,
                "fraSensitivity": api_fra_sensitivity,
            }
            for key, value in fa_attr_map.items():
                if value is not None:
                    payload["featureAttributes"][key] = value

            # Normalize unlocked attributes to controller keys using a mapping + comprehension
            if unlocked:
                unlock_map = {
                    "radio_band": "radioBand",
                    "fra_freeze": "fraFreeze",
                    "fra_status": "fraStatus",
                    "fra_interval": "fraInterval",
                    "fra_sensitivity": "fraSensitivity",
                }
                norm_unlocked = [unlock_map.get(u, u) for u in unlocked]
                payload["unlockedAttributes"] = norm_unlocked

            # Compare against existing
            existing = existing_dict.get(design_name)
            if not existing:
                add_list.append(payload)
                self.log("RRM-FRA profile '{0}' scheduled for creation.".format(design_name), "DEBUG")
                continue

            details = self.get_rrm_fra_profile_details(existing["id"]) or {}
            self.log("Existing details for '{0}': {1}".format(design_name, details), "DEBUG")

            existing_fa = (details.get("featureAttributes") or {})
            existing_unl = (details.get("unlockedAttributes") or [])

            desired_fa = payload["featureAttributes"]
            desired_unl = payload.get("unlockedAttributes", [])

            # Make comparisons type-consistent: ensure fraInterval compared as int and sensitivity compared case-insensitive
            existing_interval = existing_fa.get("fraInterval")
            desired_interval = desired_fa.get("fraInterval")

            # Normalize sensitivity for comparison: convert both to uppercase and remove spaces
            # API returns "Even Higher" or "Super High", we need to compare against "EVEN_HIGHER", "SUPER_HIGH"
            existing_sens = str(existing_fa.get("fraSensitivity") or "").upper().replace(" ", "_")
            desired_sens = str(desired_fa.get("fraSensitivity") or "").upper().replace(" ", "_")

            needs_update = (
                existing_fa.get("radioBand") != desired_fa.get("radioBand") or
                existing_fa.get("fraFreeze") != desired_fa.get("fraFreeze") or
                existing_fa.get("fraStatus") != desired_fa.get("fraStatus") or
                (existing_interval != desired_interval) or
                (existing_sens != desired_sens) or
                (set(existing_unl) != set(desired_unl))
            )

            if needs_update:
                payload["id"] = existing["id"]
                update_list.append(payload)
                self.log("RRM-FRA profile '{0}' marked for update.".format(design_name), "DEBUG")
            else:
                no_update_list.append(details)
                self.log("RRM-FRA profile '{0}' requires no update.".format(design_name), "DEBUG")

        self.log(
            "RRM-FRA - Add: {0}, Update: {1}, No-Change: {2}".format(
                len(add_list), len(update_list), len(no_update_list)
            ),
            "DEBUG",
        )
        return add_list, update_list, no_update_list

    def get_rrm_fra_profiles(self, design_name=None, template_type="RRM_FRA_CONFIGURATION"):
        """
        Retrieve existing RRM-FRA feature templates from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
            template_type (str, optional): Defaults to "RRM_FRA_CONFIGURATION".
        Returns:
            list: A list of RRM-FRA template dicts (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing RRM-FRA Templates from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_fra = response.get("response", [])
            self.log("Retrieved {0} RRM-FRA Templates.".format(len(existing_fra)), "DEBUG")
            return existing_fra

        except Exception as e:
            self.log("Failed to fetch RRM-FRA Templates: {0}".format(str(e)), "ERROR")
            return []

    def get_rrm_fra_profile_details(self, template_id):
        """
        Retrieve detailed information for a specific RRM-FRA configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the RRM-FRA feature template.
        Returns:
            dict: The details of the RRM-FRA feature template, or {} if fetch fails.
        """
        self.log("Fetching RRM-FRA configuration details for template_id='{0}'".format(template_id), "DEBUG")

        try:
            if not template_id:
                self.log("No template_id provided for RRM-FRA details.", "ERROR")
                return {}

            response = self.execute_get_request(
                "wireless",
                "get_r_r_m_f_r_a_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            details = response.get("response") or {}
            return details

        except Exception as e:
            self.log("Failed to fetch RRM-FRA configuration details for template_id={0}: {1}".format(template_id, str(e)), "ERROR")
            return {}

    def verify_delete_multicast_requirement(self, multicast_list):
        """
        Determines which multicast configuration templates need to be deleted
        based on the requested parameters.
        Args:
            multicast_list (list): A list of dicts containing the requested multicast
                                configuration parameters for deletion.
                                Example: [{"design_name": "multicast_office_profile"}]
        Returns:
            list: A list of multicast configuration templates scheduled for deletion,
                including their IDs.
        """
        delete_list = []

        self.log("Starting verification of multicast configurations for deletion.", "INFO")

        # Retrieve all existing multicast configurations
        existing_blocks = self.get_multicast_profiles()
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []))

        self.log("Existing multicast configurations: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary for quick lookup
        existing_dict = {cfg["designName"]: cfg for cfg in instances}
        self.log("Converted existing multicast configs to dictionary.", "DEBUG")

        # Iterate over requested configurations
        for index, requested_cfg in enumerate(multicast_list, start=1):
            design_name = requested_cfg.get("design_name")
            self.log(
                "Iteration {0}: Checking multicast config '{1}' for deletion.".format(
                    index, design_name
                ),
                "DEBUG",
            )

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                cfg_to_delete = requested_cfg.copy()
                cfg_to_delete["id"] = existing.get("id")
                delete_list.append(cfg_to_delete)
                self.log(
                    "Iteration {0}: multicast config '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: multicast config '{1}' not found -> no deletion required.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "multicast configurations scheduled for deletion: {0} - {1}".format(
                len(delete_list), delete_list
            ),
            "DEBUG",
        )

        return delete_list

    def verify_create_update_multicast_requirement(self, multicast_list):
        """
        Compares desired Multicast profiles against existing ones and determines
        which need to be created, updated, or left unchanged.
        Args:
            multicast_list (list): A list of dictionaries representing desired Multicast profiles.
                Each dictionary should include:
                    - design_name (str): The unique design/profile name.
                    - feature_attributes (dict): The configuration attributes for the profile.
                    - unlocked_attributes (list, optional): List of attribute names to unlock for editing.
        Returns:
            tuple: Three lists containing Multicast configurations to be created, updated, and not updated:
                - add_list (list): Payloads for new Multicast configurations to create
                - update_list (list): Payloads for existing Multicast configurations to update (includes "id" field)
                - no_update_list (list): Existing Multicast configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        # Fetch once
        existing_blocks = self.get_multicast_profiles()
        self.log("Existing Multicast Profiles: {0}".format(existing_blocks), "DEBUG")

        # Flatten instances into dict
        existing_dict = {}
        for block in existing_blocks or []:
            for inst in block.get("instances", []) or []:
                design = inst.get("designName")
                if design:
                    existing_dict[design] = inst
        self.log("Existing Multicast Profiles Dict: {0}".format(existing_dict), "DEBUG")

        allowed_ipv4_modes = ["UNICAST", "MULTICAST"]
        allowed_ipv6_modes = ["UNICAST", "MULTICAST"]

        # mapping for unlocked attribute normalization
        unlock_map = {
            "global_multicast_enabled": "globalMulticastEnabled",
            "multicast_ipv4_mode": "multicastIpv4Mode",
            "multicast_ipv4_address": "multicastIpv4Address",
            "multicast_ipv6_mode": "multicastIpv6Mode",
            "multicast_ipv6_address": "multicastIpv6Address",
        }

        # iterate requested attributes
        for attr in multicast_list or []:
            design_name = attr.get("design_name")
            feature_attrs = attr.get("feature_attributes") or {}
            unlocked_attributes = attr.get("unlocked_attributes", [])

            global_multicast_enabled = feature_attrs.get("global_multicast_enabled")
            ipv4_mode = feature_attrs.get("multicast_ipv4_mode")
            ipv4_address = feature_attrs.get("multicast_ipv4_address")
            ipv6_mode = feature_attrs.get("multicast_ipv6_mode")
            ipv6_address = feature_attrs.get("multicast_ipv6_address")

            # --- Validation ---
            if ipv4_mode and ipv4_mode not in allowed_ipv4_modes:
                self.msg = (
                    "Invalid multicastIpv4Mode '{0}' for design '{1}'. Must be one of: {2}"
                    .format(ipv4_mode, design_name, allowed_ipv4_modes)
                )
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            if ipv6_mode and ipv6_mode not in allowed_ipv6_modes:
                self.msg = (
                    "Invalid multicastIpv6Mode '{0}' for design '{1}'. Must be one of: {2}"
                    .format(ipv6_mode, design_name, allowed_ipv6_modes)
                )
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # IPv4 validation (basic numeric check, only if MULTICAST)
            if ipv4_mode == "MULTICAST" and ipv4_address:
                try:
                    parts = [int(p) for p in ipv4_address.split(".")]
                    if len(parts) != 4 or any(p < 0 or p > 255 for p in parts):
                        raise ValueError
                    if not (224 <= parts[0] <= 239):
                        raise ValueError
                except Exception:
                    self.msg = (
                        "Invalid multicastIpv4Address '{0}' for design '{1}'. "
                        "Must be in range 224.0.0.0–239.255.255.255."
                    ).format(ipv4_address, design_name)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # IPv6 validation (prefix check, only if MULTICAST)
            if ipv6_mode == "MULTICAST" and ipv6_address:
                addr_up = ipv6_address.upper()
                # must start with FF
                if not addr_up.startswith("FF") or len(addr_up) < 4:
                    self.msg = (
                        "Invalid multicastIpv6Address '{0}' for design '{1}'. "
                        "Must start with FF[0 or 1][1,2,3,4,5,8,E]."
                    ).format(ipv6_address, design_name)
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
                else:
                    second = addr_up[2]
                    third = addr_up[3]
                    if second not in ("0", "1") or third not in ("1", "2", "3", "4", "5", "8", "E"):
                        self.msg = (
                            "Invalid multicastIpv6Address '{0}' for design '{1}'. "
                            "Must start with FF[0 or 1][1,2,3,4,5,8,E]."
                        ).format(ipv6_address, design_name)
                        self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # --- Build payload ---
            payload = {
                "designName": design_name,
                "featureAttributes": {
                    "globalMulticastEnabled": global_multicast_enabled,
                },
            }

            # use a small mapping to set optional attrs concisely
            feature_map = {
                "multicastIpv4Mode": ipv4_mode,
                "multicastIpv4Address": ipv4_address,
                "multicastIpv6Mode": ipv6_mode,
                "multicastIpv6Address": ipv6_address,
            }
            for k, v in feature_map.items():
                if v is not None:
                    payload["featureAttributes"][k] = v

            # normalize unlocked attributes using mapping (fallback to original key)
            if unlocked_attributes:
                normalized_unlocked = [unlock_map.get(u, u) for u in unlocked_attributes]
                payload["unlockedAttributes"] = normalized_unlocked

            # --- Compare with existing ---
            existing = existing_dict.get(design_name)
            if not existing:
                add_list.append(payload)
                self.log("Multicast profile '{0}' scheduled for creation.".format(design_name), "DEBUG")
                continue

            details = self.get_multicast_profile_details(existing["id"]) or {}
            self.log("Details for {0}: {1}".format(design_name, details), "DEBUG")

            existing_attrs = details.get("featureAttributes", {}) or {}
            existing_unlocked = details.get("unlockedAttributes", []) or []

            desired_attrs = payload.get("featureAttributes", {}) or {}
            desired_unlocked = payload.get("unlockedAttributes", []) or []

            if (
                existing_attrs.get("globalMulticastEnabled") != desired_attrs.get("globalMulticastEnabled")
                or existing_attrs.get("multicastIpv4Mode") != desired_attrs.get("multicastIpv4Mode")
                or existing_attrs.get("multicastIpv4Address") != desired_attrs.get("multicastIpv4Address")
                or existing_attrs.get("multicastIpv6Mode") != desired_attrs.get("multicastIpv6Mode")
                or existing_attrs.get("multicastIpv6Address") != desired_attrs.get("multicastIpv6Address")
                or set(existing_unlocked) != set(desired_unlocked)
            ):
                payload["id"] = existing["id"]
                update_list.append(payload)
                self.log("Multicast profile '{0}' marked for update.".format(design_name), "DEBUG")
            else:
                no_update_list.append(details)
                self.log("Multicast profile '{0}' requires no update.".format(design_name), "DEBUG")

        # final summary log and return (ensure return is after loop)
        self.log(
            "Multicast Profiles - Add: {0}, Update: {1}, No Changes: {2}".format(
                len(add_list), len(update_list), len(no_update_list)
            ),
            "DEBUG",
        )

        return add_list, update_list, no_update_list

    def get_multicast_profiles(self, design_name=None, template_type="MULTICAST_CONFIGURATION"):
        """
        Retrieve existing Multicast feature templates from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
            template_type (str, optional): Feature template type string used by DNAC.
                                        Defaults to "MULTICAST_CONFIGURATION".
        Returns:
            list: A list of existing Multicast template dicts (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing Multicast Templates from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_multicast = response.get("response", [])
            self.log(
                "Retrieved {0} Multicast Templates.".format(len(existing_multicast)),
                "DEBUG",
            )
            return existing_multicast

        except Exception as e:
            self.log("Failed to fetch Multicast Templates: {0}".format(str(e)), "ERROR")
            return []

    def get_multicast_profile_details(self, template_id):
        """
        Retrieve detailed information for a specific Multicast configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the multicast feature template.
        Returns:
            dict: The details of the multicast feature template, or {} if fetch fails.
        """
        self.log("Fetching multicast configuration details for template_id='{0}'".format(template_id), "DEBUG")

        try:
            if not template_id:
                self.log("No template_id provided for multicast details.", "ERROR")
                return {}

            response = self.execute_get_request(
                "wireless",
                "get_multicast_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")

            details = response.get("response") or {}
            return details

        except Exception as e:
            self.log("Failed to fetch multicast configuration details for template_id='{0}': {1}".format(template_id, str(e)), "ERROR")
            return {}

    def verify_delete_flexconnect_requirement(self, flex_list):
        """
        Build payloads (with id) for FlexConnect templates to delete.
        Args:
            flex_list (list): A list of dicts containing the requested FlexConnect
                         configuration parameters for deletion.
                         Example: [{"design_name": "flex_design_1"}]
        Returns:
            list: A list of FlexConnect configuration templates scheduled for deletion,including their IDs.
        """
        delete_list = []
        skipped = []

        existing_blocks = self.get_flexconnect_profiles() or []
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []) or [])
        existing_by_name = {i.get("designName"): i for i in instances if i.get("designName")}

        for idx, req in enumerate(flex_list or [], start=1):
            dn = req.get("design_name")
            if not dn:
                skipped.append(idx)
                self.log("Iteration {0}: Missing 'design_name' in delete entry. Skipping.".format(idx), "ERROR")
                continue
            if dn in existing_by_name:
                got = dict(req)
                got["id"] = existing_by_name[dn].get("id")
                delete_list.append(got)
                self.log("Iteration {0}: FlexConnect '{1}' -> DELETE".format(idx, dn), "INFO")
            else:
                self.log("Iteration {0}: FlexConnect '{1}' not found -> skip".format(idx, dn), "INFO")

        self.log("FlexConnect scheduled for delete: {0}".format(delete_list), "DEBUG")
        if skipped:
            self.log("FlexConnect entries skipped due to missing design_name: {0}".format(skipped), "WARNING")
        return delete_list

    def verify_create_update_flexconnect_requirement(self, flex_list):
        """
        Build payloads to create/update FlexConnect feature templates.
        Args:
            flex_list (list): A list of dicts containing desired FlexConnect parameters.
            Each dict should include:
                - design_name (str): The unique design/profile name
                - feature_attributes (dict): Configuration attributes
                - unlocked_attributes (list, optional): Attributes to unlock for editing
        Returns:
            tuple: Three lists containing FlexConnect configurations to be created, updated, and not updated:
                - add_list (list): Payloads for new FlexConnect configurations to create
                - update_list (list): Payloads for existing FlexConnect configurations to update (includes "id" field)
                - no_update_list (list): Existing FlexConnect configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        # Get existing (summary)
        existing_blocks = self.get_flexconnect_profiles()
        instances = []
        for block in (existing_blocks or []):
            instances.extend(block.get("instances", []) or [])
        existing_by_name = {i["designName"]: i for i in instances if i.get("designName")}
        self.log("Existing FlexConnect instances: {0}".format(instances), "DEBUG")

        for req in flex_list or []:
            design_name = req.get("design_name")
            fa = req.get("feature_attributes") or {}
            overlap_enable = fa.get("overlap_ip_enable")
            unlocked = req.get("unlocked_attributes", []) or []

            if not design_name:
                self.msg = "FlexConnect: 'design_name' is required."
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # Build normalized payload
            payload = {
                "designName": design_name,
                "featureAttributes": {}
            }
            if overlap_enable is not None:
                payload["featureAttributes"]["overlapIpEnable"] = overlap_enable

            if unlocked:
                # Only valid attribute is overlap_ip_enable -> overlapIpEnable
                name_map = {"overlap_ip_enable": "overlapIpEnable"}
                payload["unlockedAttributes"] = [name_map.get(u, u) for u in unlocked]

            existing = existing_by_name.get(design_name)
            if not existing:
                add_list.append(payload)
                self.log("FlexConnect '{0}' -> ADD".format(design_name), "DEBUG")
                continue

            # Fetch details to compare
            details = self.get_flexconnect_profile_details(existing["id"]) or {}
            ef = (details.get("featureAttributes") or {})
            existing_overlap = ef.get("overlapIpEnable")
            existing_unlocked = details.get("unlockedAttributes", []) or []
            desired_unlocked = payload.get("unlockedAttributes", [])

            needs_update = (
                existing_overlap != overlap_enable
                or set(existing_unlocked) != set(desired_unlocked)
            )

            if needs_update:
                payload["id"] = existing["id"]
                update_list.append(payload)
                self.log("FlexConnect '{0}' -> UPDATE".format(design_name), "DEBUG")
            else:
                no_update_list.append(details)
                self.log("FlexConnect '{0}' -> NO CHANGE".format(design_name), "DEBUG")

        self.log(
            "FlexConnect Add: {0}, Update: {1}, No-Change: {2}".format(
                len(add_list), len(update_list), len(no_update_list)
            ),
            "DEBUG",
        )
        return add_list, update_list, no_update_list

    def get_flexconnect_profiles(self, design_name=None, template_type="FLEX_CONFIGURATION"):
        """
        Summary list of FlexConnect templates (uses get_feature_template_summary with type).
        Args:
            design_name (str, optional): Optional design_name to filter template_type (str, optional): Template type string (default FLEX_CONFIGURATION)
        Returns:
            list: List of FlexConnect template dicts (API 'response' list), or [] on failure
        """
        self.log("Fetching FlexConnect templates (summary).", "DEBUG")
        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name
            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            return response.get("response", []) or []
        except Exception as e:
            self.log("Failed to fetch FlexConnect templates: {0}".format(str(e)), "ERROR")
            return []

    def get_flexconnect_profile_details(self, template_id):
        """
        Details of one FlexConnect template by id.
        Args:
            template_id (str): The unique template ID
        Returns:
            dict: Dict of FlexConnect template details, or {} if fetch fails
        """
        self.log("Fetching FlexConnect details for id='{0}'".format(template_id), "DEBUG")
        try:
            if not template_id:
                self.log("No template_id provided.", "ERROR")
                return {}
            resp = self.execute_get_request(
                "wireless",
                "get_flex_connect_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(resp), "DEBUG")
            return resp.get("response") or {}
        except Exception as e:
            self.log("Failed to fetch FlexConnect details: {0}".format(str(e)), "ERROR")
            return {}

    def verify_delete_dot11be_requirement(self, dot11be_list):
        """
        Determines which dot11be configuration templates need to be deleted
        based on the requested parameters.
        Args:
            dot11be_list (list): A list of dicts containing the requested dot11be
                                configuration parameters for deletion.
                                Example: [{"design_name": "dot11be_2.4ghz_design"}]
        Returns:
            list: A list of dot11be configuration templates scheduled for deletion,
                including their IDs.
        """
        delete_list = []

        self.log("Starting verification of dot11be configurations for deletion.", "INFO")

        # Retrieve all existing dot11be configurations
        existing_blocks = self.get_dot11be_profiles()
        self.log("Existing dot11be Profiles (summary): {0}".format(existing_blocks), "WARNING")
        instances = []
        for block in existing_blocks or []:
            instances.extend(block.get("instances", []) or [])

        self.log("Existing dot11be configurations: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary for quick lookup
        existing_dict = {cfg["designName"]: cfg for cfg in instances}
        self.log("Converted existing dot11be configs to dictionary.", "DEBUG")

        # Iterate over requested configurations
        for index, requested_cfg in enumerate(dot11be_list or [], start=1):
            design_name = requested_cfg.get("design_name")
            self.log(
                "Iteration {0}: Checking dot11be config '{1}' for deletion.".format(
                    index, design_name
                ),
                "INFO",
            )

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                cfg_to_delete = requested_cfg.copy()
                cfg_to_delete["id"] = existing.get("id")
                delete_list.append(cfg_to_delete)
                self.log(
                    "Iteration {0}: dot11be config '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: dot11be config '{1}' not found -> no deletion required.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "dot11be configurations scheduled for deletion: {0} - {1}".format(
                len(delete_list), delete_list
            ),
            "DEBUG",
        )
        self.log("dot11be configurations scheduled for deletion: {0} - {1}".format(
            len(delete_list), delete_list
        ), "Warning")

        return delete_list

    def verify_create_update_dot11be_requirement(self, dot11be_list):
        """
        Compares desired 802.11be profiles against existing ones and determines which need to be created, updated, or left unchanged.
        Args:
            dot11be_list (list): A list of dictionaries representing desired 802.11be profiles.
                Each dictionary should include:
                    - design_name (str): The unique design/profile name.
                    - feature_attributes (dict): The configuration attributes for the profile.
                    - unlocked_attributes (list, optional): List of attribute names to unlock for editing.
        Returns:
            tuple: Three lists containing 802.11be configurations to be created, updated, and not updated:
                - add_list (list): Payloads for new 802.11be configurations to create
                - update_list (list): Payloads for existing 802.11be configurations to update (includes "id" field)
                - no_update_list (list): Existing 802.11be configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        # Fetch once
        existing_blocks = self.get_dot11be_profiles()
        self.log("Existing 802.11be Profiles: {0}".format(existing_blocks), "DEBUG")

        # Flatten instances into dict
        existing_dict = {}
        for block in existing_blocks or []:
            for inst in block.get("instances", []):
                existing_dict[inst["designName"]] = inst
        self.log("Existing 802.11be Profiles Dict: {0}".format(existing_dict), "DEBUG")

        # Allowed values for radioBand
        allowed_bands = ["2_4GHZ", "5GHZ", "6GHZ"]

        # Iterate requested attributes
        for attr in dot11be_list or []:
            design_name = attr.get("design_name")
            feature_attrs = attr.get("feature_attributes") or {}
            dot11be_status = feature_attrs.get("dot11be_status")
            radio_band = feature_attrs.get("radio_band")
            unlocked_attributes = attr.get("unlocked_attributes", [])

            # Normalize radio_band to uppercase for case-insensitive comparison
            if radio_band:
                radio_band = str(radio_band).upper()

            # Validate radio_band value
            if radio_band not in allowed_bands:
                self.msg = ("Invalid radio_band '{0}' for design '{1}'. Must be one of: {2}".format(
                    radio_band, design_name, allowed_bands))
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # Build payload
            payload = {
                "designName": design_name,
                "featureAttributes": {
                    "dot11beStatus": dot11be_status,
                    "radioBand": radio_band,
                },
            }
            if unlocked_attributes:
                # Normalized to camelCase
                name_map = {"dot11be_status": "dot11beStatus", "radio_band": "radioBand"}
                normalized_unlocked = [name_map.get(u, u) for u in unlocked_attributes]
                payload["unlockedAttributes"] = normalized_unlocked

            # Check against existing
            existing = existing_dict.get(design_name)
            if not existing:
                add_list.append(payload)
                self.log("802.11be profile '{0}' scheduled for creation.".format(design_name), "DEBUG")
            else:
                details = self.get_dot11be_profile_details(existing["id"])
                self.log("Details for {0}: {1}".format(design_name, details), "DEBUG")

                existing_status = details.get("featureAttributes", {}).get("dot11beStatus")
                existing_band = details.get("featureAttributes", {}).get("radioBand")
                existing_unlocked = details.get("unlockedAttributes", []) or []

                desired_unlocked = payload.get("unlockedAttributes", [])

                # Compare fields
                if (
                    existing_status != dot11be_status
                    or existing_band != radio_band
                    or set(existing_unlocked) != set(desired_unlocked)
                ):
                    payload["id"] = existing["id"]
                    update_list.append(payload)
                    self.log("802.11be profile '{0}' marked for update.".format(design_name), "DEBUG")
                else:
                    no_update_list.append(details)
                    self.log("802.11be profile '{0}' requires no update.".format(design_name), "DEBUG")

        self.log(
            "802.11be Profiles - Add: {0}, Update: {1}, No Changes: {2}".format(
                len(add_list), len(update_list), len(no_update_list)
            ),
            "DEBUG"
        )

        return add_list, update_list, no_update_list

    def get_dot11be_profiles(self, design_name=None, template_type="DOT11BE_STATUS_CONFIGURATION"):
        """
        Retrieve existing 802.11be feature templates from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
            template_type (str, optional): Feature template type string used by DNAC.
                                        Defaults to "DOT11BE_STATUS_CONFIGURATION".
        Returns:
            list: A list of existing 802.11be template dicts (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing 802.11be Templates from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_dot11be = response.get("response", [])
            self.log(
                "Retrieved {0} 802.11be Templates.".format(len(existing_dot11be)),
                "DEBUG",
            )
            return existing_dot11be

        except Exception as e:
            self.log("Failed to fetch 802.11be Templates: {0}".format(str(e)), "ERROR")
            return []

    def get_dot11be_profile_details(self, template_id):
        """
        Retrieve detailed information for a specific 802.11be configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the 802.11be feature template.
        Returns:
            dict: The details of the 802.11be feature template, or {} if fetch fails.
        """
        self.log("Fetching 802.11be configuration details for template_id='{0}'".format(template_id), "DEBUG")

        try:
            if not template_id:
                self.log("No template_id provided for 802.11be details.", "ERROR")
                return {}

            response = self.execute_get_request(
                "wireless",
                "get_dot11be_status_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")

            details = response.get("response") or {}
            return details

        except Exception as e:
            self.log("Failed to fetch 802.11be configuration details: {0}".format(str(e)), "ERROR")
            return {}

    def verify_create_update_event_rrm_requirement(self, event_rrm_list):
        """
        Compares desired Event Driven RRM profiles against existing ones and determines
        which need to be created, updated, or left unchanged.
        Args:
            event_rrm_list (list): A list of dictionaries representing desired Event Driven RRM profiles.
                Each dictionary should include:
                    - design_name (str): The unique design/profile name.
                    - feature_attributes (dict): The configuration attributes for the profile.
                    - unlocked_attributes (list, optional): List of attribute names to unlock for editing.
        Returns:
            tuple: Three lists containing Event Driven RRM configurations to be created, updated, and not updated:
                - add_list (list): Payloads for new Event Driven RRM configurations to create
                - update_list (list): Payloads for existing Event Driven RRM configurations to update (includes "id" field)
                - no_update_list (list): Existing Event Driven RRM configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        # Fetch once
        existing_blocks = self.get_event_rrm_profiles()
        self.log("Existing Event Driven RRM Profiles: {0}".format(existing_blocks), "DEBUG")

        # Flatten instances into dict
        existing_dict = {}
        for block in existing_blocks or []:
            for inst in block.get("instances", []):
                existing_dict[inst["designName"]] = inst
        self.log("Existing Event Driven RRM Profiles Dict: {0}".format(existing_dict), "DEBUG")

        # Allowed enums / ranges
        allowed_bands = ["2_4GHZ", "5GHZ"]
        allowed_levels = ["LOW", "MEDIUM", "HIGH", "CUSTOM"]

        # Iterate requested profiles
        for attr in event_rrm_list or []:
            design_name = attr.get("design_name")
            fa = attr.get("feature_attributes") or {}
            radio_band = fa.get("radio_band").upper() if fa.get("radio_band") else None
            rrm_enable = fa.get("event_driven_rrm_enable")
            rrm_level = fa.get("event_driven_rrm_threshold_level").upper() if fa.get("event_driven_rrm_threshold_level") else None
            rrm_custom = fa.get("event_driven_rrm_custom_threshold_val")
            unlocked_attributes = attr.get("unlocked_attributes", [])

            # ---- Validations ----
            if not design_name:
                self.msg = "Missing 'design_name' in Event Driven RRM entry."
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            if radio_band not in allowed_bands:
                self.msg = ("Invalid radio_band '{0}' for design '{1}'. Must be one of: {2}".format(
                    radio_band, design_name, allowed_bands))
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            if rrm_level is not None and rrm_level not in allowed_levels:
                self.msg = ("Invalid event_driven_rrm_threshold_level '{0}' for design '{1}'. "
                            "Must be one of: {2}".format(rrm_level, design_name, allowed_levels))
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # Threshold level is only supported when RRM is enabled
            if (rrm_level is not None or rrm_custom is not None) and not rrm_enable:
                self.msg = ("For design '{0}': threshold level/custom value provided but "
                            "event_driven_rrm_enable is not true.".format(design_name))
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # Custom value only when level == CUSTOM and must be 1..99
            if rrm_custom is not None:
                if rrm_level != "CUSTOM":
                    self.msg = ("For design '{0}': event_driven_rrm_custom_threshold_val is only valid when "
                                "event_driven_rrm_threshold_level == 'CUSTOM'.".format(design_name))
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
                if not isinstance(rrm_custom, int) or not (1 <= rrm_custom <= 99):
                    self.msg = ("For design '{0}': event_driven_rrm_custom_threshold_val must be an integer 1–99."
                                .format(design_name))
                    self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # ---- Build normalized payload ----
            payload = {
                "designName": design_name,
                "featureAttributes": {
                    "radioBand": radio_band,
                },
            }
            # Only include optional keys if present (keep payload clean)
            if rrm_enable is not None:
                payload["featureAttributes"]["eventDrivenRrmEnable"] = rrm_enable
            if rrm_level is not None:
                payload["featureAttributes"]["eventDrivenRrmThresholdLevel"] = rrm_level
            if rrm_custom is not None:
                payload["featureAttributes"]["eventDrivenRrmCustomThresholdVal"] = rrm_custom

            # Normalize unlocked attributes (snake_case -> camelCase)
            if unlocked_attributes:
                name_map = {
                    "radio_band": "radioBand",
                    "event_driven_rrm_enable": "eventDrivenRrmEnable",
                    "event_driven_rrm_threshold_level": "eventDrivenRrmThresholdLevel",
                    "event_driven_rrm_custom_threshold_val": "eventDrivenRrmCustomThresholdVal",
                }
                normalized_unlocked = [name_map.get(u, u) for u in unlocked_attributes]
                self.log(
                    "Normalized unlocked attributes for '{0}': {1} -> {2}".format(
                        design_name, unlocked_attributes, normalized_unlocked
                    ),
                    "DEBUG"
                )
                payload["unlockedAttributes"] = normalized_unlocked

            # ---- Compare with existing ----
            existing = existing_dict.get(design_name)
            if not existing:
                add_list.append(payload)
                self.log("Event Driven RRM profile '{0}' scheduled for creation.".format(design_name), "DEBUG")
            else:
                details = self.get_event_rrm_profile_details(existing["id"])
                self.log("Details for {0}: {1}".format(design_name, details), "DEBUG")

                ef = details.get("featureAttributes", {}) or {}
                existing_band = ef.get("radioBand")
                existing_enable = ef.get("eventDrivenRrmEnable")
                existing_level = ef.get("eventDrivenRrmThresholdLevel")
                existing_custom = ef.get("eventDrivenRrmCustomThresholdVal")
                existing_unlocked = details.get("unlockedAttributes", []) or []
                desired_unlocked = payload.get("unlockedAttributes", [])

                needs_update = (
                    existing_band != radio_band
                    or existing_enable != rrm_enable
                    or existing_level != rrm_level
                    or existing_custom != rrm_custom
                    or set(existing_unlocked) != set(desired_unlocked)
                )

                if needs_update:
                    payload["id"] = existing["id"]
                    update_list.append(payload)
                    self.log("Event Driven RRM profile '{0}' marked for update.".format(design_name), "DEBUG")
                else:
                    no_update_list.append(details)
                    self.log("Event Driven RRM profile '{0}' requires no update.".format(design_name), "DEBUG")

        self.log(
            "Event Driven RRM - Add: {0}, Update: {1}, No Changes: {2}".format(
                add_list, update_list, no_update_list
            ),
            "DEBUG"
        )
        self.log(
            "Event Driven RRM - Add: {0}, Update: {1}, No Changes: {2}".format(
                len(add_list), len(update_list), len(no_update_list)
            ),
            "DEBUG"
        )

        return add_list, update_list, no_update_list

    def get_event_rrm_profile_details(self, template_id):
        """
        Retrieve detailed information for a specific Event Driven RRM configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the Event Driven RRM feature template.
        Returns:
            dict: The details of the Event Driven RRM feature template, or {} if fetch fails.
        """
        self.log("Fetching Event Driven RRM configuration details for template_id='{0}'".format(template_id), "DEBUG")

        try:
            if not template_id:
                self.log("No template_id provided for Event Driven RRM details.", "ERROR")
                return {}

            response = self.execute_get_request(
                "wireless",
                "get_event_driven_r_r_m_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")

            details = response.get("response") or {}
            return details

        except Exception as e:
            self.log("Failed to fetch Event Driven RRM configuration details: {0}".format(str(e)), "ERROR")
            return {}

    def get_event_rrm_profiles(self, design_name=None, template_type="EVENT_DRIVEN_RRM_CONFIGURATION"):
        """
        Retrieve existing Event Driven RRM feature templates (summary) from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
            template_type (str, optional): Feature template type string used by DNAC.
                                        Defaults to "EVENT_DRIVEN_RRM_CONFIGURATION".
        Returns:
            list: A list of existing Event Driven RRM template dicts (summary, not full details),
                or [] on failure.
        """
        self.log("Fetching existing Event Driven RRM Templates (summary) from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")

            existing_event_rrm = response.get("response", [])
            self.log(
                "Retrieved {0} Event Driven RRM Templates (summary).".format(len(existing_event_rrm)),
                "DEBUG",
            )
            return existing_event_rrm

        except Exception as e:
            self.log("Failed to fetch Event Driven RRM Templates: {0}".format(str(e)), "ERROR")
            return []

    def verify_delete_event_rrm_requirement(self, event_rrm_list):
        """
        Determines which Event-Driven RRM configuration templates need to be deleted
        based on the requested parameters.
        Args:
            event_rrm_list (list): A list of dicts containing the requested Event-Driven RRM
                                configuration parameters for deletion.
                                Example: [{"design_name": "edrrm_2_4ghz_design"}]
        Returns:
            list: A list of Event-Driven RRM configuration templates scheduled for deletion,
                including their IDs.
        """
        delete_list = []

        self.log("Starting verification of Event-Driven RRM configurations for deletion.", "INFO")

        # Retrieve all existing Event-Driven RRM configurations (summary)
        existing_blocks = self.get_event_rrm_profiles()
        instances = []
        for block in existing_blocks or []:
            instances.extend(block.get("instances", []) or [])

        self.log("Existing Event-Driven RRM configurations: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary for quick lookup
        existing_dict = {cfg["designName"]: cfg for cfg in instances}
        self.log("Converted existing Event-Driven RRM configs to dictionary.", "DEBUG")

        # Iterate over requested configurations
        for index, requested_cfg in enumerate(event_rrm_list or [], start=1):
            design_name = requested_cfg.get("design_name")
            self.log(
                "Iteration {0}: Checking Event-Driven RRM config '{1}' for deletion.".format(
                    index, design_name
                ),
                "DEBUG",
            )

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                cfg_to_delete = requested_cfg.copy()
                cfg_to_delete["id"] = existing.get("id")
                delete_list.append(cfg_to_delete)
                self.log(
                    "Iteration {0}: Event-Driven RRM config '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: Event-Driven RRM config '{1}' not found -> no deletion required.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "Event-Driven RRM configurations scheduled for deletion: {0} - {1}".format(
                len(delete_list), delete_list
            ),
            "DEBUG",
        )

        return delete_list

    def verify_delete_dot11axs_requirement(self, dot11ax_list):
        """
        Determines which dot11ax configuration templates need to be deleted
        based on the requested parameters.
        Args:
            dot11ax_list (list): A list of dicts containing the requested dot11ax
                                configuration parameters for deletion.
                                Example: [{"design_name": "dot11ax_24ghz_design"}]
        Returns:
            list: A list of dot11ax configuration templates scheduled for deletion,
                including their IDs.
        """
        delete_list = []

        self.log("Starting verification of dot11ax configurations for deletion.", "INFO")

        # Retrieve all existing dot11ax configurations
        existing_blocks = self.get_dot11ax_templates()
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []))

        self.log("Existing dot11ax configurations: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary for quick lookup
        existing_dict = {cfg["designName"]: cfg for cfg in instances}
        self.log("Converted existing dot11ax configs to dictionary.", "DEBUG")

        # Iterate over requested configurations
        for index, requested_cfg in enumerate(dot11ax_list, start=1):
            design_name = requested_cfg.get("design_name")
            self.log(
                "Iteration {0}: Checking dot11ax config '{1}' for deletion.".format(
                    index, design_name
                ),
                "DEBUG",
            )

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                cfg_to_delete = requested_cfg.copy()
                cfg_to_delete["id"] = existing.get("id")
                delete_list.append(cfg_to_delete)
                self.log(
                    "Iteration {0}: dot11ax config '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: dot11ax config '{1}' not found -> no deletion required.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "dot11ax configurations scheduled for deletion: {0} - {1}".format(
                len(delete_list), delete_list
            ),
            "DEBUG",
        )

        return delete_list

    def verify_create_update_dot11axs_requirement(self, dot11ax_list):
        """
        Compare requested dot11ax profiles against existing templates and determine
        which should be added, updated, or left unchanged.
        Args:
            dot11ax_list (list): A list of dictionaries representing desired 802.11ax profiles.
                Each dictionary should include:
                    - design_name (str): The unique design/profile name.
                    - feature_attributes (dict): The configuration attributes for the profile.
                    - unlocked_attributes (list): List of attribute names to unlock for editing.
        Returns:
            tuple: Three lists containing 802.11ax configurations to be created, updated, and not updated:
                - add_list (list): Payloads for new 802.11ax configurations to create
                - update_list (list): Payloads for existing 802.11ax configurations to update (includes "id" field)
                - no_update_list (list): Existing 802.11ax configurations that require no changes
        """
        add_list, update_list, no_update_list = [], [], []

        self.log("verify_create_update_dot11axs_requirement input: {0}".format(dot11ax_list), "DEBUG")

        # map snake_case keys from playbook to controller keys (adjust if controller uses different names)
        key_name_map = {
            "design_name": "designName",
            "feature_attributes": "featureAttributes",
            "unlocked_attributes": "unlockedAttributes",
            "radio_band": "radioBand",
            "bss_color": "bssColor",
            "target_waketime_broadcast": "targetWaketimeBroadcast",
            "non_srg_obss_pd_max_threshold": "nonSRGObssPdMaxThreshold",
            "target_wakeup_time_11ax": "targetWakeUpTime11ax",
            "obss_pd": "obssPd",
            "multiple_bssid": "multipleBssid",
        }

        # helper: snake_case -> lowerCamelCase fallback
        def snake_to_camel(s):
            parts = s.split("_")
            return parts[0] + "".join(p.capitalize() for p in parts[1:]) if len(parts) > 1 else s

        # Controller-allowed unlocked attribute names (explicit list from controller validation message).
        # If you have an API to fetch this dynamically, replace this static set with that call.
        allowed_unlocked = {
            "targetWakeUpTime11ax",
            "obssPd",
            "bssColor",
            "targetWaketimeBroadcast",
            "nonSRGObssPdMaxThreshold",
            "multipleBssid",
        }

        # fetch existing dot11ax templates once and flatten by designName
        existing_blocks = self.get_dot11ax_templates() or []
        self.log("Existing dot11ax templates: {0}".format(existing_blocks), "DEBUG")
        existing_dict = {}
        for block in existing_blocks or []:
            for inst in block.get("instances", []) or []:
                existing_dict[inst.get("designName")] = inst
        self.log("Existing dot11ax templates dict: {0}".format(existing_dict), "DEBUG")

        # iterate requests
        for requested in dot11ax_list or []:
            design_name = requested.get("design_name")
            feature_attrs_raw = requested.get("feature_attributes") or {}
            unlocked_attrs = requested.get("unlocked_attributes") or []

            # Build normalized payload (controller-style keys) for featureAttributes
            normalized_features = {}
            for rk, rv in feature_attrs_raw.items():
                tk = key_name_map.get(rk) or snake_to_camel(rk)
                normalized_features[tk] = rv

            # Normalize & filter unlocked attributes: map to controller keys and only keep allowed first-level attributes
            requested_unlocked = unlocked_attrs or []
            normalized_unlocked = []
            dropped_unlocked = []
            unmapped_unlocked = []

            for ua in requested_unlocked:
                mapped = key_name_map.get(ua) or snake_to_camel(ua)
                if mapped in allowed_unlocked:
                    normalized_unlocked.append(mapped)
                else:
                    # keep track to log back to the user / playbook author
                    # if it didn't map to ANY reasonable controller key, mark as unmapped, else dropped because not allowed
                    if (key_name_map.get(ua) or snake_to_camel(ua)) != mapped:
                        unmapped_unlocked.append(ua)
                    else:
                        dropped_unlocked.append(ua)

            if dropped_unlocked or unmapped_unlocked:
                # warn user / playbook author that some unlocked attrs were invalid and dropped
                self.log(
                    "[{0}] Some unlockedAttributes were invalid and removed: dropped={1} ({2}), unmapped={3} ({4})".format(
                        design_name, len(dropped_unlocked), dropped_unlocked, len(unmapped_unlocked), unmapped_unlocked
                    ),
                    "WARNING",
                )

            payload = {"designName": design_name, "featureAttributes": normalized_features}
            if normalized_unlocked:
                payload["unlockedAttributes"] = normalized_unlocked

            self.log("Checking dot11ax profile: {0}".format(design_name), "DEBUG")

            existing = existing_dict.get(design_name)

            # If not existing -> add
            if not existing:
                add_list.append(payload)
                self.log("dot11ax '{0}' marked for ADD.".format(design_name), "INFO")
                continue

            # fetch full details for accurate comparison
            details = self.get_dot11ax_details(existing.get("id")) or {}
            self.log("Details for {0}: {1}".format(design_name, details), "DEBUG")

            existing_features = details.get("featureAttributes", {}) or {}
            existing_unlocked = details.get("unlockedAttributes", []) or []

            needs_update = False

            # Compare only the keys supplied by the user
            for key, req_value in normalized_features.items():
                exist_value = existing_features.get(key)

                # normalize boolean-like strings on request side
                if isinstance(req_value, str) and req_value.lower() in ("true", "false"):
                    req_value = req_value.lower() == "true"
                # if controller omitted the key and request is boolean, treat omitted as False
                if exist_value is None and isinstance(req_value, bool):
                    exist_value = False
                # normalize controller boolean strings
                if isinstance(exist_value, str) and exist_value.lower() in ("true", "false"):
                    exist_value = exist_value.lower() == "true"

                lower_key = key.lower()

                # numeric comparison for numeric-looking keys
                if (
                    isinstance(req_value, (int, float))
                    or (isinstance(req_value, str) and req_value.isdigit())
                    or any(sub in lower_key for sub in ("threshold", "max", "count"))
                ):
                    try:
                        ev_num = int(exist_value) if exist_value is not None else None
                    except Exception:
                        ev_num = exist_value
                    try:
                        rv_num = int(req_value) if req_value is not None else None
                    except Exception:
                        rv_num = req_value
                    if ev_num != rv_num:
                        self.log("Diff for {0}: existing({1}) != requested({2})".format(key, ev_num, rv_num), "DEBUG")
                        needs_update = True
                        break

                else:
                    # default strict equality
                    if exist_value != req_value:
                        self.log("Diff for {0}: existing({1}) != requested({2})".format(key, exist_value, req_value), "DEBUG")
                        needs_update = True
                        break

            # compare unlocked attributes (order-insensitive)
            if not needs_update:
                # Normalize existing unlocked (controller should already be lowerCamelCase; defensively map snake -> camel just in case)
                normalized_existing_unlocked = []
                for eu in existing_unlocked:
                    # assume existing values are controller style; but normalize just in case:
                    # if someone stored snake_case in controller (unlikely), convert. We only convert if '_' present.
                    if isinstance(eu, str) and "_" in eu:
                        normalized_existing_unlocked.append(key_name_map.get(eu) or snake_to_camel(eu))
                    else:
                        normalized_existing_unlocked.append(eu)

                # Compare as sets (order-insensitive). If request omitted unlockedAttributes entirely, we treat as "no change requested"
                if "unlockedAttributes" in payload:
                    if set(normalized_existing_unlocked) != set(payload.get("unlockedAttributes", [])):
                        self.log(
                            "Unlocked attributes differ: existing({0}) != requested({1})".format(
                                normalized_existing_unlocked, payload.get("unlockedAttributes", [])
                            ),
                            "DEBUG",
                        )
                        needs_update = True

            # finalize
            if needs_update:
                payload["id"] = existing.get("id")
                update_list.append(payload)
                self.log("dot11ax '{0}' marked for UPDATE.".format(design_name), "INFO")
            else:
                no_update_list.append(details)
                self.log("dot11ax '{0}' requires NO UPDATE.".format(design_name), "INFO")

        self.log("dot11ax to ADD: {0}, UPDATE: {1}, NO-UPDATE: {2}".format(len(add_list), len(update_list), len(no_update_list)), "DEBUG")
        return add_list, update_list, no_update_list

    def get_dot11ax_details(self, template_id):
        """
        Retrieve detailed information for a specific dot11ax configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the dot11ax feature template.
        Returns:
            dict: The details of the dot11ax feature template, or {} if fetch fails.
        """
        self.log("Fetching dot11ax configuration details for template_id='{0}'".format(template_id), "DEBUG")

        try:
            if not template_id:
                self.log("No template_id provided for dot11ax details.", "ERROR")
                return {}

            response = self.execute_get_request(
                "wireless",
                "get_dot11ax_configuration_feature_template",
                {"id": template_id}
            )

            self.log("Received API response: {0}".format(response), "DEBUG")

            details = response.get("response") or {}
            return details

        except Exception as e:
            self.log("Failed to fetch dot11ax configuration details: {0}".format(str(e)), "ERROR")
            return {}

    def get_dot11ax_templates(self, design_name=None, template_type="DOT11AX_CONFIGURATION"):
        """
        Retrieve detailed information for a specific dot11ax configuration template from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the dot11ax feature template.
        Returns:
            dict: The details of the dot11ax feature template, or {} if fetch fails.
        """
        self.log("Fetching existing dot11ax Templates from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_dot11ax = response.get("response", [])
            self.log(
                "Retrieved {0} dot11ax Templates.".format(len(existing_dot11ax)),
                "DEBUG",
            )
            return existing_dot11ax

        except Exception as e:
            self.log("Failed to fetch dot11ax Templates: {0}".format(str(e)), "ERROR")
            return []

    def verify_delete_clean_air_requirement(self, clean_air_list):
        """
        Determines which CleanAir profiles need to be deleted based on the requested parameters.
        Args:
            clean_air_list (list): A list of dicts containing the requested CleanAir parameters for deletion.
                                Example: [{"design_name": "sample_cleanair_design_24ghz"}]
        Returns:
            list: A list of CleanAir entries to delete. Each entry is the original requested dict
                with an added "id" key (the controller template id) when a match is found.
        """
        delete_clean_air_list = []

        self.log("Starting verification of CleanAir profiles for deletion.", "INFO")

        # Retrieve all existing CleanAir templates
        existing_blocks = self.get_clean_air_templates() or []
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []) or [])

        self.log("Existing CleanAir instances: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary keyed by designName
        existing_dict = {item["designName"]: item for item in instances}
        self.log("Converted existing CleanAir templates to dictionary.", "DEBUG")

        # Iterate over the requested entries for deletion
        for idx, requested in enumerate(clean_air_list or [], start=1):
            design_name = requested.get("design_name")
            self.log(
                "Iteration {0}: Checking CleanAir '{1}' for deletion requirement.".format(idx, design_name),
                "DEBUG",
            )

            if not design_name:
                self.log(
                    "Iteration {0}: Skipping entry with missing design_name: {1}".format(idx, requested),
                    "WARNING",
                )
                continue

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                to_delete = requested.copy()
                to_delete["id"] = existing.get("id")
                delete_clean_air_list.append(to_delete)
                self.log(
                    "Iteration {0}: CleanAir '{1}' scheduled for deletion (id={2}).".format(
                        idx, design_name, existing.get("id")
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: CleanAir '{1}' not found - no deletion required.".format(idx, design_name),
                    "INFO",
                )

        self.log(
            "CleanAir profiles scheduled for deletion: {0} - {1}".format(len(delete_clean_air_list), delete_clean_air_list),
            "DEBUG",
        )

        return delete_clean_air_list

    def _validate_clean_air_interferer_band_compatibility(self, design_name, radio_band, interferers_features):
        """
        Validate that interferer types are compatible with the specified radio band.
        Args:
            design_name (str): Name of the design for error reporting
            radio_band (str): Radio band (2_4GHZ, 5GHZ, or 6GHZ)
            interferers_features (dict): Dictionary of interferer settings
        Raises:
            Sets self.status to "failed" and raises check_return_status() if validation fails
        """
        if not interferers_features:
            return  # No interferers configured, nothing to validate

        # Define interferers supported by each band (snake_case keys)
        # 2.4GHz supports all interferers
        interferers_24ghz = {
            "ble_beacon", "bluetooth_paging_inquiry", "bluetooth_sco_acl",
            "continuous_transmitter", "generic_dect", "generic_tdd", "jammer",
            "microwave_oven", "motorola_canopy", "si_fhss", "spectrum80211_fh",
            "spectrum80211_non_standard_channel", "spectrum802154", "spectrum_inverted",
            "super_ag", "video_camera", "wimax_fixed", "wimax_mobile", "xbox"
        }

        # 5GHz supports subset (excludes BLE, Bluetooth variants, microwave_oven, spectrum80211_fh, spectrum802154, xbox)
        interferers_5ghz = {
            "continuous_transmitter", "generic_dect", "generic_tdd", "jammer",
            "motorola_canopy", "si_fhss", "spectrum80211_non_standard_channel",
            "spectrum_inverted", "super_ag", "video_camera", "wimax_fixed", "wimax_mobile"
        }

        # 6GHz supports only continuous_transmitter
        interferers_6ghz = {
            "continuous_transmitter"
        }

        # Determine which interferers are allowed for this band
        if radio_band == "2_4GHZ":
            allowed_interferers = interferers_24ghz
        elif radio_band == "5GHZ":
            allowed_interferers = interferers_5ghz
        elif radio_band == "6GHZ":
            allowed_interferers = interferers_6ghz
        else:
            # Unknown band, skip validation (let API handle it)
            return

        # Check if any enabled interferers are not supported by this band
        invalid_interferers = []
        for interferer_key, interferer_value in interferers_features.items():
            # Only check interferers that are enabled (set to True)
            if interferer_value is True and interferer_key not in allowed_interferers:
                invalid_interferers.append(interferer_key)

        if invalid_interferers:
            band_display = radio_band.replace("_", ".")
            self.msg = (
                "Invalid CleanAir configuration for design '{0}': The following interferers are not "
                "supported on {1} band: {2}. Please refer to the module documentation for the complete "
                "band/interferer compatibility matrix."
            ).format(design_name, band_display, ", ".join(invalid_interferers))
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

    def _get_all_interferers_with_defaults(self, radio_band):
        """
        Get all possible interferers with false defaults based on radio_band.
        Args:
            radio_band (str): Radio band (2_4GHZ, 5GHZ, or 6GHZ)
        Returns:
            dict: All interferers with false defaults
        """
        # Base interferers for all bands
        interferers = {}

        if radio_band == "2_4GHZ":
            interferers.update({
                "bleBeacon": False,
                "bluetoothPagingInquiry": False,
                "bluetoothScoAcl": False,
                "continuousTransmitter": False,
                "genericDect": False,
                "genericTdd": False,
                "jammer": False,
                "microwaveOven": False,
                "motorolaCanopy": False,
                "siFhss": False,
                "spectrum80211Fh": False,
                "spectrum80211NonStandardChannel": False,
                "spectrum802154": False,
                "spectrumInverted": False,
                "superAg": False,
                "videoCamera": False,
                "wimaxFixed": False,
                "wimaxMobile": False,
                "xbox": False,
            })
        elif radio_band == "5GHZ":
            interferers.update({
                "continuousTransmitter": False,
                "genericDect": False,
                "genericTdd": False,
                "jammer": False,
                "motorolaCanopy": False,
                "siFhss": False,
                "spectrum80211NonStandardChannel": False,
                "spectrumInverted": False,
                "superAg": False,
                "videoCamera": False,
                "wimaxFixed": False,
                "wimaxMobile": False,
            })
        elif radio_band == "6GHZ":
            # 6GHz supports only continuousTransmitter
            interferers.update({
                "continuousTransmitter": False,
            })

        return interferers

    def _normalize_clean_air_payload(self, requested_entry, key_name_map, snake_to_camel):
        """
        Normalize a requested CleanAir entry into controller payload format.
        Args:
            requested_entry (dict): Raw playbook entry with snake_case keys
            key_name_map (dict): Mapping from snake_case to camelCase
            snake_to_camel (callable): Function to convert snake_case to camelCase
        Returns:
            dict: Normalized payload with camelCase keys
        """
        design_name = requested_entry.get("design_name")
        requested_features_raw = requested_entry.get("feature_attributes") or {}
        # Extract radio_band from feature_attributes (not from top level)
        radio_band = requested_features_raw.get("radio_band")
        requested_unlocked = requested_entry.get("unlocked_attributes")
        requested_unlocked = [] if requested_unlocked is None else requested_unlocked

        # Validate interferer/band compatibility BEFORE normalization
        interferers_features = requested_features_raw.get("interferers_features")
        if interferers_features and radio_band:
            self._validate_clean_air_interferer_band_compatibility(design_name, radio_band, interferers_features)

        # Build normalized features (convert top-level keys)
        normalized_features = {}
        for raw_k, raw_v in requested_features_raw.items():
            if raw_k == "interferers_features" and isinstance(raw_v, dict):
                # Get all possible interferers with defaults based on radio_band
                interferers = self._get_all_interferers_with_defaults(radio_band)
                # Override with user-specified values
                for ik, iv in raw_v.items():
                    inner_key = key_name_map.get(ik, ik)
                    interferers[inner_key] = iv
                normalized_features["interferersFeatures"] = interferers
            else:
                mapped_key = key_name_map.get(raw_k, snake_to_camel(raw_k))
                normalized_features[mapped_key] = raw_v

        payload = {"designName": design_name, "radioBand": radio_band, "featureAttributes": normalized_features}

        if requested_unlocked:
            # transform unlocked dot-notation to controller-style
            normalized_unlocked = []
            for u in requested_unlocked:
                if "." in u:
                    left, right = u.split(".", 1)
                    left_mapped = key_name_map.get(left, snake_to_camel(left))
                    normalized_unlocked.append(left_mapped + "." + right)
                else:
                    normalized_unlocked.append(key_name_map.get(u, snake_to_camel(u)))
            payload["unlockedAttributes"] = normalized_unlocked

        return payload

    def _compare_nested_interferers(self, key, normalized_features, existing_features, boolean_defaults, to_bool_if_str, reg_diff_fn):
        """
        Compare interferersFeatures nested attributes for CleanAir profiles.
        Args:
            key (str): Field key, potentially with dot notation (e.g., "interferersFeatures.ble_beacon")
            normalized_features (dict): Requested features
            existing_features (dict): Existing features from controller
            boolean_defaults (dict): Default values for missing fields
            to_bool_if_str (callable): Function to coerce string bools to bool
            reg_diff_fn (callable): Function to register differences
        Returns:
            bool: True if update is needed, False otherwise
        """
        needs_update = False

        if "." in key:
            # Single nested field check (e.g., "interferersFeatures.ble_beacon")
            outer, inner = key.split(".", 1)
            req_map = normalized_features.get("interferersFeatures", {})
            req_val = req_map.get(inner)
            exist_map = existing_features.get("interferersFeatures", {}) or {}
            exist_val = exist_map.get(inner)

            # coerce bool-like strings
            req_val = to_bool_if_str(req_val)
            if exist_val is None and isinstance(req_val, bool):
                exist_val = (
                    boolean_defaults.get("interferersFeatures", {}).get(inner)
                    if isinstance(boolean_defaults.get("interferersFeatures"), dict)
                    else False
                )
            if isinstance(exist_val, str) and exist_val.lower() in ("true", "false"):
                exist_val = exist_val.lower() == "true"

            if exist_val != req_val:
                reg_diff_fn("interferersFeatures." + inner, exist_val, req_val)
                needs_update = True
        else:
            # Compare entire interferersFeatures map
            # Compare all keys from both requested and existing, treating missing as False
            req_map = normalized_features.get("interferersFeatures", {}) or {}
            exist_map = existing_features.get("interferersFeatures", {}) or {}

            # Iterate over all keys from both maps
            for inner_key in set(list(req_map.keys()) + list(exist_map.keys())):
                req_val = req_map.get(inner_key)
                exist_val = exist_map.get(inner_key)

                # Treat missing values as False for both sides
                if req_val is None:
                    req_val = False
                else:
                    req_val = to_bool_if_str(req_val)

                if exist_val is None:
                    exist_val = False
                elif isinstance(exist_val, str) and exist_val.lower() in ("true", "false"):
                    exist_val = exist_val.lower() == "true"

                if exist_val != req_val:
                    reg_diff_fn("interferersFeatures." + inner_key, exist_val, req_val)
                    needs_update = True

        return needs_update

    def _compare_clean_air_fields(self, key, normalized_features, existing_features, boolean_defaults, to_bool_if_str, reg_diff_fn):
        """
        Compare non-nested CleanAir field values.
        Args:
            key (str): Field key to compare
            normalized_features (dict): Requested features
            existing_features (dict): Existing features from controller
            boolean_defaults (dict): Default values for missing fields
            to_bool_if_str (callable): Function to coerce string bools to bool
            reg_diff_fn (callable): Function to register differences
        Returns:
            bool: True if update is needed, False otherwise
        """
        needs_update = False
        req_value = normalized_features.get(key)
        exist_value = existing_features.get(key)

        # coerce boolean-like strings
        req_value = to_bool_if_str(req_value)

        # consult boolean_defaults for missing exist_value
        if exist_value is None:
            if key in boolean_defaults:
                exist_value = boolean_defaults[key]
            elif isinstance(req_value, bool):
                exist_value = False  # safe fallback

        if isinstance(exist_value, str) and exist_value.lower() in ("true", "false"):
            exist_value = exist_value.lower() == "true"

        # type-specific comparison
        lower_key = key.lower()
        if lower_key in ("description",):
            # string compare
            if exist_value != req_value:
                reg_diff_fn(key, exist_value, req_value)
                needs_update = True
        elif isinstance(req_value, bool) or isinstance(exist_value, bool):
            # boolean compare
            if bool(exist_value) != bool(req_value):
                reg_diff_fn(key, exist_value, req_value)
                needs_update = True
        elif isinstance(req_value, (int, float)) or isinstance(exist_value, (int, float)):
            # numeric tolerant compare
            try:
                evn = int(exist_value) if exist_value is not None else None
            except Exception:
                evn = exist_value
            try:
                rvn = int(req_value) if req_value is not None else None
            except Exception:
                rvn = req_value
            if evn != rvn:
                reg_diff_fn(key, evn, rvn)
                needs_update = True
        else:
            # default equality
            if exist_value != req_value:
                reg_diff_fn(key, exist_value, req_value)
                needs_update = True

        return needs_update

    def verify_create_update_clean_air_requirement(self, clean_air_list, field_to_check=None):
        """
        Determine which CleanAir profiles to add, update, or leave unchanged.
        Args:
            clean_air_list (list): list of requested clean-air dicts from the playbook
            field_to_check (str|None): optional single-field to check (snake_case or camelCase, supports dot notation)
        Returns:
            tuple: (add_list, update_list, no_update_list)
        Side effect:
            sets self.clean_air_update_diffs = { designName: [(key, existing, requested), ...], ... }
        """
        add_list, update_list, no_update_list = [], [], []
        clean_air_update_diffs = {}
        self.log("verify_create_update_clean_air_requirement input: {0}".format(clean_air_list), "DEBUG")

        # key map: playbook snake_case -> controller camelCase
        key_name_map = {
            "design_name": "designName",
            "radio_band": "radioBand",
            "feature_attributes": "featureAttributes",
            "unlocked_attributes": "unlockedAttributes",
            "clean_air": "cleanAir",
            "clean_air_device_reporting": "cleanAirDeviceReporting",
            "persistent_device_propagation": "persistentDevicePropagation",
            "description": "description",
            "interferers_features": "interferersFeatures",
            "ble_beacon": "bleBeacon",
            "bluetooth_paging_inquiry": "bluetoothPagingInquiry",
            "bluetooth_sco_acl": "bluetoothScoAcl",
            "continuous_transmitter": "continuousTransmitter",
            "generic_dect": "genericDect",
            "generic_tdd": "genericTdd",
            "jammer": "jammer",
            "microwave_oven": "microwaveOven",
            "motorola_canopy": "motorolaCanopy",
            "si_fhss": "siFhss",
            "spectrum80211_fh": "spectrum80211Fh",
            "spectrum80211_non_standard_channel": "spectrum80211NonStandardChannel",
            "spectrum802154": "spectrum802154",
            "spectrum_inverted": "spectrumInverted",
            "super_ag": "superAg",
            "video_camera": "videoCamera",
            "wimax_fixed": "wimaxFixed",
            "wimax_mobile": "wimaxMobile",
            "xbox": "xbox",
        }

        # optional per-key boolean defaults when controller omits key (controller-style names)
        boolean_defaults = {
            # Example: "cleanAir": False,
            # For nested interferersFeatures you could default to {} or set specific inner keys
            # e.g. "interferersFeatures": {}
        }

        # helper: snake_case -> lowerCamelCase
        def snake_to_camel(s):
            parts = s.split("_")
            return parts[0] + "".join(p.capitalize() for p in parts[1:]) if len(parts) > 1 else s

        # normalize field_to_check (support dot notation like 'interferers_features.ble_beacon')
        def normalize_field_key(raw_key):
            if raw_key is None:
                return None
            if "." in raw_key:
                left, right = raw_key.split(".", 1)
                left_mapped = key_name_map.get(left, snake_to_camel(left))
                # keep nested part as provided (we will compare nested dicts specially)
                return left_mapped + "." + right
            return key_name_map.get(raw_key, snake_to_camel(raw_key))

        field_check_key = normalize_field_key(field_to_check)

        # helper to coerce "true"/"false" strings to bool
        def to_bool_if_str(v):
            if isinstance(v, str) and v.lower() in ("true", "false"):
                return v.lower() == "true"
            return v

        # Fetch existing templates and flatten by designName
        existing_blocks = self.get_clean_air_templates() or []
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []) or [])
        existing_by_design = {inst["designName"]: inst for inst in instances}
        self.log("Existing CleanAir instances: {0}".format(instances), "DEBUG")

        # Iterate requested profiles
        for requested_entry in clean_air_list or []:
            design_name = requested_entry.get("design_name")

            # Normalize payload using helper
            payload = self._normalize_clean_air_payload(requested_entry, key_name_map, snake_to_camel)

            # check existing
            existing_entry = existing_by_design.get(design_name)
            if not existing_entry:
                add_list.append(payload)
                self.log("CleanAir design '{0}' not found -> ADD".format(design_name), "INFO")
                continue

            # fetch full existing details
            existing_details = self.get_clean_air_details(existing_entry["id"]) or {}
            self.log("Existing clean-air details for {0}: {1}".format(design_name, existing_details), "DEBUG")
            existing_features = existing_details.get("featureAttributes", {}) or {}
            existing_unlocked = existing_details.get("unlockedAttributes", []) or []

            needs_update = False
            per_design_diffs = []

            # helper to register diff
            def _reg_diff(k, ev, rv):
                per_design_diffs.append((k, ev, rv))

            # Determine keys to compare
            normalized_features = payload.get("featureAttributes", {})
            if field_check_key is None:
                keys_to_check = list(normalized_features.keys())
            else:
                keys_to_check = [field_check_key]

            # Compare keys
            for key in keys_to_check:
                if key.startswith("interferersFeatures"):
                    # Use helper for nested interferers comparison
                    if self._compare_nested_interferers(key, normalized_features, existing_features,
                                                        boolean_defaults, to_bool_if_str, _reg_diff):
                        needs_update = True
                else:
                    # Use helper for non-nested field comparison
                    if self._compare_clean_air_fields(key, normalized_features, existing_features,
                                                      boolean_defaults, to_bool_if_str, _reg_diff):
                        needs_update = True

            # unlocked attributes diff
            if (field_check_key is None) or (field_check_key and field_check_key.startswith("unlockedAttributes")):
                if set(existing_unlocked) != set(payload.get("unlockedAttributes", [])):
                    _reg_diff("unlockedAttributes", existing_unlocked, payload.get("unlockedAttributes", []))
                    needs_update = True

            # finalize
            if needs_update:
                payload["id"] = existing_entry.get("id")
                update_list.append(payload)
                clean_air_update_diffs[design_name] = per_design_diffs
                self.log("CleanAir design '{0}' marked for UPDATE. Diffs: {1}".format(design_name, per_design_diffs), "INFO")
            else:
                no_update_list.append(existing_details)
                self.log("CleanAir design '{0}' requires NO UPDATE".format(design_name), "INFO")

        # attach diffs to self for inspection
        self.clean_air_update_diffs = clean_air_update_diffs
        self.log("Collected CleanAir diffs: {0}".format(clean_air_update_diffs), "DEBUG")
        self.log("ADD: {0}, UPDATE: {1}, NO-CHANGE: {2}".format(len(add_list), len(update_list), len(no_update_list)), "DEBUG")
        return add_list, update_list, no_update_list

    def verify_delete_advanced_ssid_requirement(self, adv_ssid_list):
        """
        Determines which Advanced SSIDs need to be deleted based on the requested parameters.
        Args:
            adv_ssid_list (list): A list of dicts containing the requested Advanced SSID parameters for deletion.
                                Example: [{"design_name": "Corporate_WLAN_Design"}]
        Returns:
            list: A list of Advanced SSID entries to delete. Each entry is the original requested dict
                with an added "id" key (the controller template id) when a match is found.
        """
        delete_ssid_list = []

        self.log("Starting verification of Advanced SSIDs for deletion.", "INFO")

        # Retrieve all existing Advanced SSID templates
        existing_blocks = self.get_advanced_ssid_templates() or []
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []) or [])

        self.log("Existing Advanced SSID instances: {0}".format(instances), "DEBUG")

        # Convert existing instances into a dictionary keyed by designName
        existing_dict = {ssid["designName"]: ssid for ssid in instances}
        self.log("Converted existing Advanced SSIDs to dictionary.", "DEBUG")

        # Iterate over the requested entries for deletion
        for idx, requested in enumerate(adv_ssid_list or [], start=1):
            design_name = requested.get("design_name")
            self.log(
                "Iteration {0}: Checking Advanced SSID '{1}' for deletion requirement.".format(idx, design_name),
                "DEBUG",
            )

            if not design_name:
                self.log(
                    "Iteration {0}: Skipping entry with missing design_name: {1}".format(idx, requested),
                    "WARNING",
                )
                continue

            if design_name in existing_dict:
                existing = existing_dict[design_name]
                to_delete = requested.copy()
                to_delete["id"] = existing.get("id")
                delete_ssid_list.append(to_delete)
                self.log(
                    "Iteration {0}: Advanced SSID '{1}' scheduled for deletion (id={2}).".format(
                        idx, design_name, existing.get("id")
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: Advanced SSID '{1}' not found - no deletion required.".format(idx, design_name),
                    "INFO",
                )

        self.log(
            "Advanced SSIDs scheduled for deletion: {0} - {1}".format(len(delete_ssid_list), delete_ssid_list),
            "DEBUG",
        )

        return delete_ssid_list

    def verify_create_update_advanced_ssid_requirement(self, adv_ssid_list, field_to_check=None):
        """
        Determine which Advanced SSIDs to add, update, or leave unchanged.
        This function compares requested Advanced SSID configurations against existing ones
        and categorizes them based on whether they need to be created, updated, or left unchanged.
        It supports both full configuration comparison and single-field checking.
        Args:
            adv_ssid_list (list): A list of dictionaries representing the Advanced SSIDs to verify.
                Each dictionary should contain:
                    - design_name (str): The unique design/profile name
                    - feature_attributes (dict): The configuration attributes for the profile
                    - unlocked_attributes (list, optional): List of attribute names to unlock for editing
            field_to_check (str, optional): The specific field to check for changes. Accepts both
            camelCase and snake_case field names. If provided, only that field (or 'unlocked_attributes')
            is compared. Defaults to None (compare all fields).

        Returns:
            tuple: Three lists containing Advanced SSID configurations:
                - add_payloads (list): Payloads for new Advanced SSID configurations to create
                - update_payloads (list): Payloads for existing Advanced SSID configurations to update
                (includes "id" field)
                - no_change_payloads (list): Existing Advanced SSID configurations that require no changes
        """
        add_payloads, update_payloads, no_change_payloads = [], [], []
        update_diffs = {}
        self.log("verify_create_update_advanced_ssid_requirement input: {0}".format(adv_ssid_list), "DEBUG")

        # key name map (complete map from your playbook)
        key_name_map = {
            # top-level
            "design_name": "designName",
            "feature_attributes": "featureAttributes",
            "unlocked_attributes": "unlockedAttributes",

            # common ssid fields / enums / booleans
            "peer2peer_blocking": "peer2peerblocking",
            "passive_client": "passiveClient",
            "prediction_optimization": "predictionOptimization",
            "dual_band_neighbor_list": "dualBandNeighborList",
            "radius_nac_state": "radiusNacState",
            "dhcp_required": "dhcpRequired",
            "dhcp_server": "dhcpServer",
            "flex_local_auth": "flexLocalAuth",
            "target_wakeup_time": "targetWakeupTime",

            # OFDMA / MU-MIMO / 802.11ax
            "downlink_ofdma": "downlinkOfdma",
            "uplink_ofdma": "uplinkOfdma",
            "downlink_mu_mimo": "downlinkMuMimo",
            "uplink_mu_mimo": "uplinkMuMimo",
            "dot11ax": "dot11ax",
            "mu_mimo_11ac": "muMimo11ac",

            # vendor / extra flags
            "aironet_ie_support": "aironetIeSupport",
            "load_balancing": "loadBalancing",

            # timing / counts / numeric
            "dtim_period_5ghz": "dtimPeriod5GHz",
            "dtim_period_24ghz": "dtimPeriod24GHz",
            "scan_defer_time": "scanDeferTime",
            "max_clients": "maxClients",
            "max_clients_per_radio": "maxClientsPerRadio",
            "max_clients_per_ap": "maxClientsPerAP",
            "idle_threshold": "idleThreshold",
            "fast_transition_reassociation_timeout": "fastTransitionReassociationTimeout",

            # WMM / multicast
            "wmm_policy": "wmmPolicy",
            "multicast_buffer": "multicastBuffer",
            "multicast_buffer_value": "multicastBufferValue",
            "media_stream_multicast_direct": "mediaStreamMulticastDirect",

            # steering / agile multiband / fastlane
            "wifi_to_cellular_steering": "wifiToCellularSteering",
            "wifi_alliance_agile_multiband": "wifiAllianceAgileMultiband",
            "fastlane_asr": "fastlaneAsr",

            # 11v / AP admin / caching / security guards
            "dot11v_bss_max_idle_protected": "dot11vBssMaxIdleProtected",
            "universal_ap_admin": "universalApAdmin",
            "opportunistic_key_caching": "opportunisticKeyCaching",
            "ip_source_guard": "ipSourceGuard",
            "dhcp_opt82_remote_id_sub_option": "dhcpOpt82RemoteIdSubOption",
            "vlan_central_switching": "vlanCentralSwitching",

            # call / snooping / disassociate / busy
            "call_snooping": "callSnooping",
            "send_disassociate": "sendDisassociate",
            "sent_486_busy": "sent486Busy",

            # ip/mac binding
            "ip_mac_binding": "ipMacBinding",

            # defer priorities (0..7)
            "defer_priority_0": "deferPriority0",
            "defer_priority_1": "deferPriority1",
            "defer_priority_2": "deferPriority2",
            "defer_priority_3": "deferPriority3",
            "defer_priority_4": "deferPriority4",
            "defer_priority_5": "deferPriority5",
            "defer_priority_6": "deferPriority6",
            "defer_priority_7": "deferPriority7",

            # sharing / analytics / beacons
            "share_data_with_client": "shareDataWithClient",
            "advertise_support": "advertiseSupport",
            "advertise_pc_analytics_support": "advertisePcAnalyticsSupport",
            "send_beacon_on_association": "sendBeaconOnAssociation",
            "send_beacon_on_roam": "sendBeaconOnRoam",

            # mdns
            "mdns_mode": "mDNSMode",
        }

        # small helper to normalize a provided field_to_check into controller key style
        def _normalize_field_check_key(raw_key):
            if raw_key is None:
                return None
            if raw_key in key_name_map:
                return key_name_map[raw_key]
            if "_" in raw_key:
                parts = raw_key.split("_")
                return parts[0] + "".join(p.capitalize() for p in parts[1:])
            return raw_key

        # small helper to canonicalize peer2peer-like values for tolerant compare
        def _canon_peer2peer_value(v):
            if v is None:
                return None
            s = str(v).strip().upper()
            if s in ("DISABLE", "DROP", "OFF", "FALSE", "0"):
                return "DROP"
            if s in ("ENABLE", "ALLOW", "ON", "TRUE", "1"):
                return "ALLOW"
            return s

        field_check_key = _normalize_field_check_key(field_to_check)

        # Fetch existing templates once and flatten by designName
        existing_templates = self.get_advanced_ssid_templates() or []
        self.log("Existing Advanced SSID templates: {0}".format(existing_templates), "DEBUG")
        existing_by_design = {}
        for block in existing_templates:
            for instance in block.get("instances", []) or []:
                existing_by_design[instance["designName"]] = instance

        # Iterate requested SSIDs
        for requested_entry in adv_ssid_list or []:
            design_name = requested_entry.get("design_name")
            requested_feature_attrs_raw = requested_entry.get("feature_attributes") or {}
            requested_unlocked = requested_entry.get("unlocked_attributes")
            requested_unlocked = [] if requested_unlocked is None else requested_unlocked

            # Inline snake_case -> lowerCamelCase normalization for payload
            normalized_feature_attrs = {}
            for raw_key, raw_val in requested_feature_attrs_raw.items():
                if raw_key in key_name_map:
                    target_key = key_name_map[raw_key]
                else:
                    if "_" in raw_key:
                        parts = raw_key.split("_")
                        target_key = parts[0] + "".join(p.capitalize() for p in parts[1:])
                    else:
                        target_key = raw_key

                if target_key == "fastTransitionReassociationTimeout" and isinstance(raw_val, (float, str)):
                    try:
                        raw_val = int(float(raw_val))
                    except Exception:
                        pass

                normalized_feature_attrs[target_key] = raw_val

            # **FIX: Normalize unlocked_attributes from snake_case to camelCase**
            normalized_unlocked = []
            if requested_unlocked:
                for attr in requested_unlocked:
                    if attr in key_name_map:
                        normalized_unlocked.append(key_name_map[attr])
                    else:
                        # fallback: snake_case to camelCase conversion
                        if "_" in attr:
                            parts = attr.split("_")
                            normalized_unlocked.append(parts[0] + "".join(p.capitalize() for p in parts[1:]))
                        else:
                            normalized_unlocked.append(attr)

            payload = {"designName": design_name, "featureAttributes": normalized_feature_attrs}
            if normalized_unlocked:
                payload["unlockedAttributes"] = normalized_unlocked

            self.log("Evaluating design: {0} (field_to_check={1})".format(design_name, field_to_check), "DEBUG")

            existing_entry = existing_by_design.get(design_name)
            self.log("Existing entry match: {0}".format(existing_entry), "DEBUG")

            # If design doesn't exist yet, schedule for creation
            if not existing_entry:
                add_payloads.append(payload)
                self.log("Design '{0}' not found -> ADD".format(design_name), "INFO")
                continue

            # Fetch complete details to compare real stored values
            existing_details = self.get_advanced_ssid_details(existing_entry["id"]) or {}
            self.log("Existing design details: {0}".format(existing_details), "DEBUG")
            existing_features = existing_details.get("featureAttributes", {}) or {}
            existing_unlocked = existing_details.get("unlockedAttributes", []) or []

            needs_update = False
            per_design_diffs = []  # collect all diffs for this design

            # If no single-field restriction, check all requested keys
            if field_check_key is None:
                # Compare only keys the user provided
                for attr_key, req_value in normalized_feature_attrs.items():
                    # read raw existing value from controller
                    exist_value = existing_features.get(attr_key)

                    # coerce boolean-like strings to bool for requested side
                    if isinstance(req_value, str) and req_value.lower() in ("true", "false"):
                        req_value = req_value.lower() == "true"

                    # If controller omitted the key (exist_value is None) but the requested value is boolean,
                    # treat the missing controller value as False (common when controllers omit default/false flags).
                    if exist_value is None and isinstance(req_value, bool):
                        exist_value = False

                    # normalize controller-side boolean strings
                    if isinstance(exist_value, str) and exist_value.lower() in ("true", "false"):
                        exist_value = exist_value.lower() == "true"

                    lower_key = attr_key.lower()

                    # peer2peer tolerant comparison
                    if "peer2peer" in lower_key:
                        if _canon_peer2peer_value(exist_value) != _canon_peer2peer_value(req_value):
                            self.log("Diff for {0}: existing({1}) != requested({2})".format(attr_key, exist_value, req_value), "DEBUG")
                            per_design_diffs.append((attr_key, exist_value, req_value))
                            needs_update = True
                            continue

                    # wmmPolicy tolerant (case-insensitive)
                    elif attr_key.lower() == "wmmpolicy" or attr_key == "wmmPolicy":
                        ev = None if exist_value is None else str(exist_value).upper()
                        rv = None if req_value is None else str(req_value).upper()
                        if ev != rv:
                            self.log("Diff for {0}: existing({1}) != requested({2})".format(attr_key, ev, rv), "DEBUG")
                            per_design_diffs.append((attr_key, ev, rv))
                            needs_update = True
                            continue

                    # numeric-ish fields: try int comparison
                    elif any(sub in lower_key for sub in ("dtim", "maxclients", "idle", "timeout", "value", "scan", "defer")):
                        try:
                            ev_num = int(existing_features.get(attr_key)) if existing_features.get(attr_key) is not None else None
                        except Exception:
                            ev_num = existing_features.get(attr_key)
                        try:
                            rv_num = int(req_value) if req_value is not None else None
                        except Exception:
                            rv_num = req_value
                        if ev_num != rv_num:
                            self.log("Diff for {0}: existing({1}) != requested({2})".format(attr_key, ev_num, rv_num), "DEBUG")
                            per_design_diffs.append((attr_key, ev_num, rv_num))
                            needs_update = True
                            continue

                    # default strict equality
                    else:
                        if exist_value != req_value:
                            self.log("Diff for {0}: existing({1}) != requested({2})".format(attr_key, exist_value, req_value), "DEBUG")
                            per_design_diffs.append((attr_key, exist_value, req_value))
                            needs_update = True
                            continue

                # If still no difference found, compare unlocked attributes
                if set(existing_unlocked) != set(normalized_unlocked):
                    self.log("Unlocked attrs differ: existing({0}) != requested({1})".format(existing_unlocked, normalized_unlocked), "DEBUG")
                    per_design_diffs.append(("unlockedAttributes", existing_unlocked, normalized_unlocked))
                    needs_update = True

            else:
                # Only compare the single requested field or unlocked attributes
                if field_to_check in ("unlocked_attributes", "unlockedAttributes") or field_check_key == "unlockedAttributes":
                    if set(existing_unlocked) != set(normalized_unlocked):
                        self.log(
                            "Unlocked attrs differ (single-field check): "
                            "existing({0}) != requested({1})".format(existing_unlocked, normalized_unlocked),
                            "DEBUG",
                        )
                        per_design_diffs.append(("unlockedAttributes", existing_unlocked, normalized_unlocked))
                        needs_update = True
                else:
                    # if the requested payload didn't include the field to check, treat as NO-UPDATE
                    if field_check_key not in normalized_feature_attrs:
                        self.log(
                            "Requested entry missing field_to_check '{0}' -> "
                            "treating NO-UPDATE for design {1}".format(field_to_check, design_name),
                            "DEBUG",
                        )
                        needs_update = False
                    else:
                        req_value = normalized_feature_attrs.get(field_check_key)
                        exist_value = existing_features.get(field_check_key)

                        # coerce boolean-like strings
                        if isinstance(req_value, str) and req_value.lower() in ("true", "false"):
                            req_value = req_value.lower() == "true"
                        # same missing-key -> False heuristic for boolean requested values
                        if exist_value is None and isinstance(req_value, bool):
                            exist_value = False
                        if isinstance(exist_value, str) and exist_value.lower() in ("true", "false"):
                            exist_value = exist_value.lower() == "true"

                        lower_key = field_check_key.lower()
                        if "peer2peer" in lower_key:
                            if _canon_peer2peer_value(exist_value) != _canon_peer2peer_value(req_value):
                                self.log("Diff for {0}: existing({1}) != requested({2})".format(field_check_key, exist_value, req_value), "DEBUG")
                                per_design_diffs.append((field_check_key, exist_value, req_value))
                                needs_update = True
                        elif field_check_key.lower() == "wmmpolicy" or field_check_key == "wmmPolicy":
                            ev = None if exist_value is None else str(exist_value).upper()
                            rv = None if req_value is None else str(req_value).upper()
                            if ev != rv:
                                self.log("Diff for {0}: existing({1}) != requested({2})".format(field_check_key, ev, rv), "DEBUG")
                                per_design_diffs.append((field_check_key, ev, rv))
                                needs_update = True
                        elif any(sub in lower_key for sub in ("dtim", "maxclients", "idle", "timeout", "value", "scan", "defer")):
                            try:
                                ev_num = int(existing_features.get(field_check_key)) if existing_features.get(field_check_key) is not None else None
                            except Exception:
                                ev_num = existing_features.get(field_check_key)
                            try:
                                rv_num = int(req_value) if req_value is not None else None
                            except Exception:
                                rv_num = req_value
                            if ev_num != rv_num:
                                self.log("Diff for {0}: existing({1}) != requested({2})".format(field_check_key, ev_num, rv_num), "DEBUG")
                                per_design_diffs.append((field_check_key, ev_num, rv_num))
                                needs_update = True
                        else:
                            if exist_value != req_value:
                                self.log("Diff for {0}: existing({1}) != requested({2})".format(field_check_key, exist_value, req_value), "DEBUG")
                                per_design_diffs.append((field_check_key, exist_value, req_value))
                                needs_update = True

            # Finalize lists - **FIX: Always ensure unlockedAttributes is in the update payload when it differs**
            if needs_update:
                payload["id"] = existing_entry.get("id")

                # **FIX: Ensure unlockedAttributes is always included in update payload when there's a difference**
                if "unlockedAttributes" not in payload:
                    if normalized_unlocked:
                        payload["unlockedAttributes"] = normalized_unlocked
                        self.log(
                            "Design '{0}': Adding unlockedAttributes to update payload: {1}".format(
                                design_name, normalized_unlocked
                            ),
                            "INFO"
                        )
                    elif existing_unlocked:
                        # Explicitly clear unlocked attributes if playbook wants empty list
                        payload["unlockedAttributes"] = []
                        self.log(
                            "Design '{0}': Clearing unlockedAttributes in update payload (was: {1})".format(
                                design_name, existing_unlocked
                            ),
                            "INFO"
                        )

                update_payloads.append(payload)
                update_diffs[design_name] = per_design_diffs
                self.log("Design '{0}' marked for UPDATE. Diffs: {1}".format(design_name, per_design_diffs), "INFO")
            else:
                no_change_payloads.append(existing_details)
                self.log("Design '{0}' requires NO UPDATE".format(design_name), "INFO")

        self.log("ADD: {0}, UPDATE: {1}, NO-CHANGE: {2}".format(len(add_payloads), len(update_payloads), len(no_change_payloads)), "DEBUG")
        return add_payloads, update_payloads, no_change_payloads

    def get_clean_air_templates(self, design_name=None, template_type="CLEANAIR_CONFIGURATION"):
        """
        Retrieve existing CleanAir feature templates from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
            template_type (str, optional): Feature template type string used by DNAC. Defaults to "CLEAN_AIR_CONFIGURATION".
        Returns:
            list: A list of existing CleanAir template dicts (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing CleanAir Templates from DNAC.", "DEBUG")

        try:
            params = {"type": template_type}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_clean_air = response.get("response", [])
            self.log(
                "Retrieved {0} CleanAir Templates.".format(len(existing_clean_air)),
                "DEBUG",
            )
            return existing_clean_air

        except Exception as e:
            self.log("Failed to fetch CleanAir Templates: {0}".format(str(e)), "ERROR")
            return []

    def get_clean_air_details(self, template_id):
        """
        Retrieve full details for a specific CleanAir feature template.
        Args:
            template_id (str): The feature template ID to fetch.
        Returns:
            dict: The template details (API 'response' object) or {} on failure.
        """
        self.log("Fetching CleanAir template details for id: {0}".format(template_id), "DEBUG")

        if not template_id:
            self.log("get_clean_air_details called without template_id.", "WARNING")
            return {}

        try:
            response = self.execute_get_request(
                "wireless",
                "get_clean_air_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            details = response.get("response", {}) or {}
            self.log("Retrieved CleanAir template details: {0}".format(details), "DEBUG")
            return details

        except Exception as e:
            self.log("Failed to fetch CleanAir template details: {0}".format(str(e)), "ERROR")
            return {}

    def get_advanced_ssid_details(self, ssid_id):
        """
        Retrieve detailed information for a specific Advanced SSID configuration template from Cisco Catalyst Center.
        Args:
            ssid_id (str): The unique ID of the Advanced SSID feature template.
        Returns:
            dict: The details of the Advanced SSID feature template, or {} if fetch fails.
        """
        self.log("Fetching existing Advanced SSID Templates from DNAC.", "DEBUG")
        try:
            params = {}
            if ssid_id:
                params["id"] = ssid_id

            # Execute API call to DNA Center
            response = self.execute_get_request(
                "wireless",
                "get_advanced_ssid_configuration_feature_template",
                params
            )

            # Log raw response for debugging
            self.log("Received API response: {0}".format(response), "DEBUG")
            # Extract templates from response
            existing_ssids = response.get("response", [])
            # Validate response data
            if not isinstance(existing_ssids, dict):
                self.log("Unexpected response format. Expected list, got {0}".format(type(existing_ssids)), "WARNING")
                return []
            return existing_ssids

        except Exception as e:
            self.log(
                "Failed to fetch Advanced SSID Templates: {0}".format(str(e)), "ERROR"
            )
            return []

    def get_advanced_ssid_templates(self, design_name=None):
        """
        Retrieve existing Advanced SSID feature templates from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
        Returns:
            list: A list of existing Advanced SSID template dicts.
        """
        self.log("Fetching existing Advanced SSID Templates from DNAC.", "DEBUG")

        try:
            params = {"type": "ADVANCED_SSID_CONFIGURATION"}

            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_ssids = response.get("response", [])
            self.log(
                "Retrieved {0} Advanced SSID Templates.".format(len(existing_ssids)),
                "DEBUG",
            )
            return existing_ssids

        except Exception as e:
            self.log(
                "Failed to fetch Advanced SSID Templates: {0}".format(str(e)), "ERROR"
            )
            return []

    def verify_delete_aaa_radius_attributes_requirement(self, aaa_attr_list):
        """
        Determines whether AAA Radius Attributes need to be deleted based on the requested parameters.
        Args:
            aaa_attr_list (list): A list of dictionaries containing the requested AAA Radius Attribute parameters for deletion.
                                Example: [{"design_name": "AAA_Radius_Template_01"}]
        Returns:
            list: A list of AAA Radius Attribute entries to delete. Each entry is the original requested dict
                with an added "id" key (the controller template id) when a match is found.
        """
        delete_attrs_list = []

        self.log("Starting verification of AAA Radius Attributes for deletion.", "INFO")

        # Retrieve all existing AAA Radius Attributes
        existing_blocks = self.get_aaa_radius_attributes()
        instances = []
        for block in existing_blocks:
            instances.extend(block.get("instances", []))

        self.log("Existing AAA Radius Attributes: {0}".format(instances), "DEBUG")

        # Convert existing attributes into a dictionary for quick lookup by design name
        existing_dict = {attr["designName"]: attr for attr in instances}
        self.log("Converted existing AAA Radius Attributes to dictionary.", "DEBUG")

        # Iterate over the requested attributes
        for index, requested_attr in enumerate(aaa_attr_list, start=1):
            design_name = requested_attr.get("design_name")
            self.log(
                "Iteration {0}: Checking AAA Radius Attribute '{1}' for deletion requirement.".format(
                    index, design_name
                ),
                "DEBUG",
            )

            if design_name in existing_dict:
                # Match found → prepare payload with ID
                existing = existing_dict[design_name]
                attr_to_delete = requested_attr.copy()
                attr_to_delete["id"] = existing.get("id")
                delete_attrs_list.append(attr_to_delete)
                self.log(
                    "Iteration {0}: AAA Radius Attribute '{1}' scheduled for deletion.".format(
                        index, design_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: Deletion not required for AAA Radius Attribute '{1}'. It does not exist.".format(
                        index, design_name
                    ),
                    "INFO",
                )

        self.log(
            "AAA Radius Attributes scheduled for deletion: {0} - {1}".format(
                len(delete_attrs_list), delete_attrs_list
            ),
            "DEBUG",
        )

        return delete_attrs_list

    def verify_create_update_aaa_radius_attributes_requirement(self, aaa_attr_list):
        """
        Compares desired AAA Radius Attributes against existing ones and determines
        which need to be created, updated, or left unchanged.
        Args:
            aaa_attr_list (list): A list of dictionaries containing the requested AAA Radius Attribute parameters.
                Each dictionary should contain:
                    - design_name (str): The unique design/profile name
                    - new_design_name (str, optional): New name for the design (for rename operation)
                    - called_station_id (str): The called station ID value
                    - unlocked_attributes (bool, optional): Whether to unlock the calledStationId attribute
        Returns:
            tuple: Three lists containing AAA Radius Attribute configurations:
                - add_attrs (list): Payloads for new AAA Radius Attribute configurations to create
                - update_attrs (list): Payloads for existing AAA Radius Attribute configurations to update (includes "id" field)
                - no_update_attrs (list): Existing AAA Radius Attribute configurations that require no changes
        Note:
            This function ONLY builds and returns payloads. It does NOT call create/update APIs — execution should happen elsewhere.
        """
        add_attrs, update_attrs, no_update_attrs = [], [], []

        # Fetch once
        existing_blocks = self.get_aaa_radius_attributes()
        self.log("Existing AAA Radius Attributes: {0}".format(existing_blocks), "DEBUG")

        # Flatten instances into dict
        existing_dict = {}
        for block in existing_blocks:
            for inst in block.get("instances", []):
                existing_dict[inst["designName"]] = inst
        self.log("Existing AAA Radius Attributes Dict: {0}".format(existing_dict), "DEBUG")

        # Iterate requested attributes
        for attr in aaa_attr_list:
            design_name = attr.get("design_name")
            new_design_name = attr.get("new_design_name")
            called_station_id = attr.get("called_station_id")
            if called_station_id is not None:
                called_station_id = called_station_id.upper()
            self.log("Evaluating AAA Radius Attribute design: {0}".format(design_name), "DEBUG")
            self.log("Requested called_station_id: {0}".format(called_station_id), "DEBUG")
            if new_design_name:
                self.log("New design name requested: {0}".format(new_design_name), "DEBUG")
            unlocked_attributes = attr.get("unlocked_attributes", False)

            # validate the called_station_id value
            allowed_values = [
                "AP_ETHMAC_ONLY", "AP_ETHMAC_SSID", "AP_GROUP_NAME", "AP_LABEL_ADDRESS",
                "AP_LABEL_ADDRESS_SSID", "AP_LOCATION", "AP_MACADDRESS", "AP_MACADDRESS_SSID",
                "AP_NAME", "AP_NAME_SSID", "IPADDRESS", "MACADDRESS", "VLAN_ID"
            ]
            if called_station_id not in allowed_values:
                self.msg = ("Invalid called_station_id '{0}' for design '{1}'. Must be one of: {2}".format(
                    called_station_id, design_name, allowed_values))
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            # Check if design_name exists
            existing = existing_dict.get(design_name)

            # Case 1: design_name exists
            if existing:
                self.log("Design '{0}' exists in Cisco Catalyst Center.".format(design_name), "DEBUG")

                # Get existing details for comparison
                details = self.get_aaa_radius_attribute_details(existing["id"])
                self.log("Details for {0}: {1}".format(design_name, details), "DEBUG")

                existing_called = details.get("featureAttributes", {}).get("calledStationId")
                existing_unlocked = details.get("unlockedAttributes", []) or []

                # Desired unlocked (only set if explicitly requested True)
                desired_unlocked = ["calledStationId"] if unlocked_attributes else []

                # Determine if update is needed (config changes or rename)
                config_changed = (
                    existing_called != called_station_id
                    or set(existing_unlocked) != set(desired_unlocked)
                )

                # Case 1a: new_design_name is provided - rename with potential config update
                if new_design_name:
                    self.log("Rename requested from '{0}' to '{1}'.".format(design_name, new_design_name), "INFO")

                    # Check if new_design_name already exists (conflict check)
                    if new_design_name in existing_dict and new_design_name != design_name:
                        self.msg = ("Cannot rename design '{0}' to '{1}' - target name already exists.".format(
                            design_name, new_design_name))
                        self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

                    # Build update payload with new name and potentially new config
                    payload = {
                        "id": existing["id"],
                        "designName": new_design_name,  # Use new name
                        "featureAttributes": {"calledStationId": called_station_id},
                    }
                    if unlocked_attributes:
                        payload["unlockedAttributes"] = ["calledStationId"]

                    update_attrs.append(payload)
                    self.log("AAA Radius Attribute '{0}' scheduled for rename to '{1}' with config update.".format(
                        design_name, new_design_name), "DEBUG")

                # Case 1b: No rename, but config changed
                elif config_changed:
                    payload = {
                        "id": existing["id"],
                        "designName": design_name,  # Keep original name
                        "featureAttributes": {"calledStationId": called_station_id},
                    }
                    if unlocked_attributes:
                        payload["unlockedAttributes"] = ["calledStationId"]

                    update_attrs.append(payload)
                    self.log("AAA Radius Attribute '{0}' marked for config update.".format(design_name), "DEBUG")

                # Case 1c: No changes needed
                else:
                    no_update_attrs.append(details)
                    self.log("AAA Radius Attribute '{0}' requires no update.".format(design_name), "DEBUG")

            # Case 2: design_name does NOT exist
            else:
                self.log("Design '{0}' does not exist in Cisco Catalyst Center.".format(design_name), "DEBUG")

                # Case 2a: new_design_name provided but design_name doesn't exist
                # Per requirement: "take design name as priority and create it"
                if new_design_name:
                    self.log(
                        "Design '{0}' does not exist. new_design_name '{1}' provided but will be ignored. "
                        "Creating with original design_name '{0}' as priority.".format(design_name, new_design_name),
                        "WARNING"
                    )

                # Create with design_name (prioritize design_name for creation)
                payload = {
                    "designName": design_name,
                    "featureAttributes": {"calledStationId": called_station_id},
                }
                if unlocked_attributes:
                    payload["unlockedAttributes"] = ["calledStationId"]

                add_attrs.append(payload)
                self.log("AAA Radius Attribute '{0}' scheduled for creation.".format(design_name), "DEBUG")

        self.log("AAA Radius Attributes to Add: {0}".format(add_attrs), "DEBUG")
        self.log("AAA Radius Attributes to Update: {0}".format(update_attrs), "DEBUG")
        self.log("AAA Radius Attributes with No Changes: {0}".format(no_update_attrs), "DEBUG")

        return add_attrs, update_attrs, no_update_attrs

    def get_aaa_radius_attribute_details(self, template_id):
        """
        Fetch detailed AAA Radius Attribute template by ID from Cisco Catalyst Center.
        Args:
            template_id (str): The unique ID of the feature template.
        Returns:
            dict: Detailed AAA Radius Attribute template (with featureAttributes, unlockedAttributes).
        """
        self.log(
            "Fetching AAA Radius Attribute details for ID: {0}".format(template_id),
            "DEBUG",
        )
        self.template_id = template_id
        try:
            response = self.execute_get_request(
                "wireless",
                "get_aaa_radius_attributes_configuration_feature_template",
                {"id": template_id}
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            details = response.get("response", {})
            self.log(
                "Retrieved AAA Radius Attribute details for ID {0}: {1}".format(
                    template_id, details
                ),
                "DEBUG",
            )
            return details

        except Exception as e:
            self.log(
                "Failed to fetch AAA Radius Attribute details for ID {0}: {1}".format(
                    template_id, str(e)
                ),
                "ERROR",
            )
            return {}

    def get_aaa_radius_attributes(self, design_name=None):
        """
        Retrieve existing AAA Radius Attributes from Cisco Catalyst Center.
        Args:
            design_name (str, optional): Specific feature template design name to filter by.
        Returns:
            list: A list of existing AAA Radius Attribute dicts (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing AAA Radius Attributes from DNAC.", "DEBUG")

        try:
            params = {"type": "AAA_RADIUS_ATTRIBUTES_CONFIGURATION"}
            if design_name:
                params["design_name"] = design_name

            response = self.execute_get_request(
                "wireless",
                "get_feature_template_summary",
                params
            )
            self.log("Received API response: {0}".format(response), "DEBUG")
            existing_attrs = response.get("response", [])
            self.log(
                "Retrieved {0} AAA Radius Attributes.".format(len(existing_attrs)),
                "DEBUG",
            )
            return existing_attrs

        except Exception as e:
            self.log(
                "Failed to fetch AAA Radius Attributes: {0}".format(str(e)), "ERROR"
            )
            return []

    def process_add_rrm_general(self, params):
        """
        Handles the creation of RRM General configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of RRM General payloads to create. Each payload should include:
                        {
                            "designName": "<str>",
                            "featureAttributes": { ... },
                            ["unlockedAttributes": [...]]
                        }
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for RRM General Configurations.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                self.log("Creating RRM General configuration: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_r_r_m_general_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # Validate returned tasks (consistent with other handlers)
                    self.check_tasks_response_status(response, "create_r_r_m_general_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created RRM General configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create RRM General configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"rrm_general_add": results}
            self.status = (
                "failed" if results and all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"rrm_general_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_rrm_general(self, params):
        """
        Handles the update of RRM General configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of RRM General payloads to update. Each payload must include:
                        {
                            "id": "<template_id>",
                            "designName": "<str>",
                            "featureAttributes": { ... },
                            ["unlockedAttributes": [...]]
                        }
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for RRM General Configurations.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                template_id = payload.get("id")

                self.log(
                    "Updating RRM General configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_r_r_m_general_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # Validate returned tasks
                    self.check_tasks_response_status(response, "update_r_r_m_general_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated RRM General configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update RRM General configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"rrm_general_update": results}
            self.status = (
                "failed" if results and all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values())
                else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"rrm_general_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_rrm_fra(self, params):
        """
        Handles the creation of RRM-FRA configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of RRM-FRA payloads to create. Each payload should
                        include at least 'designName' and 'featureAttributes'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for RRM-FRA Configurations.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                self.log("Creating RRM-FRA configuration: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_r_r_m_f_r_a_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "create_r_r_m_f_r_a_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created RRM-FRA configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create RRM-FRA configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"rrm_fra_add": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"rrm_fra_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_rrm_fra(self, params):
        """
        Handles the update of RRM-FRA configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of RRM-FRA payloads to update. Each payload must include 'id',
                        'designName', and 'featureAttributes'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for RRM-FRA Configurations.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                template_id = payload.get("id")

                self.log(
                    "Updating RRM-FRA configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_r_r_m_f_r_a_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "update_r_r_m_f_r_a_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated RRM-FRA configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update RRM-FRA configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"rrm_fra_update": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"rrm_fra_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_multicast(self, params):
        """
        Handles addition of Multicast configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of multicast payloads to add.
                        Each payload must contain at least 'design_name' and 'feature_attributes'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for Multicast Configurations.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or "Unknown"

                self.log(
                    "Adding Multicast configuration: design='{0}'".format(design_name),
                    "DEBUG",
                )

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_multicast_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "create_multicast_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created Multicast configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create Multicast configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while adding: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            self.msg = {"multicast_add": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"multicast_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_multicast(self, params):
        """
        Handles update of Multicast configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of multicast payloads to update.
                        Each payload must contain at least the template 'id' and updated attributes.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for Multicast Configurations.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Updating Multicast configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_multicast_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id, **payload},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "update_multicast_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated Multicast configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update Multicast configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            self.msg = {"multicast_update": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"multicast_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_multicast(self, params):
        """
        Handles deletion of Multicast configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of multicast payloads to delete.
                        Each payload must contain at least the template 'id'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for Multicast Configurations.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting Multicast configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_multicast_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "delete_multicast_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted Multicast configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete Multicast configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            self.msg = {"multicast_delete": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"multicast_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_flexconnect(self, params):
        """
        Handles the creation of FlexConnect configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of FlexConnect payloads to create. Each payload should
                        include at least 'designName' and 'featureAttributes'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for FlexConnect.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")
        results = {}
        try:
            for payload in params or []:
                dn = payload.get("designName") or payload.get("design_name") or "<unknown>"
                try:
                    resp = self.dnac._exec(
                        family="wireless",
                        function="create_flex_connect_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(resp), "DEBUG")
                    self.check_tasks_response_status(resp, "create_flex_connect_configuration_feature_template")
                    results[dn] = (
                        "Successfully created FlexConnect."
                        if self.status not in ["failed", "exited"]
                        else "Failed to create FlexConnect: {0}".format(self.msg)
                    )
                except Exception as exc:
                    results[dn] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[dn], "ERROR")
            self.msg = {"flexconnect_add": results}
            self.status = "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self
        except Exception as exc:
            self.msg = {"flexconnect_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_flexconnect(self, params):
        """
        Handles the update of FlexConnect configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of FlexConnect payloads to update.
                        Each payload must contain at least the template 'id' and updated attributes.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for FlexConnect.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")
        results = {}
        try:
            for payload in params or []:
                dn = payload.get("designName") or payload.get("design_name") or "<unknown>"
                tid = payload.get("id")
                if not tid:
                    results[dn] = "Skipped update: missing 'id' in payload."
                    self.log(results[dn], "ERROR")
                    continue
                try:
                    resp = self.dnac._exec(
                        family="wireless",
                        function="update_flex_connect_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(resp), "DEBUG")
                    self.check_tasks_response_status(resp, "update_flex_connect_configuration_feature_template")
                    results[dn] = (
                        "Successfully updated FlexConnect."
                        if self.status not in ["failed", "exited"]
                        else "Failed to update FlexConnect: {0}".format(self.msg)
                    )
                except Exception as exc:
                    results[dn] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[dn], "ERROR")
            self.msg = {"flexconnect_update": results}
            self.status = "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self
        except Exception as exc:
            self.msg = {"flexconnect_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_event_driven_rrm(self, params):
        """
        Handles the creation of Event-Driven RRM configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of Event-Driven RRM payloads to create.
                        Each payload should include at least 'designName'
                        and 'featureAttributes' as produced by your verifier.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for Event-Driven RRM Configurations.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                self.log("Creating Event-Driven RRM configuration: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_event_driven_r_r_m_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate returned tasks
                    self.check_tasks_response_status(response, "create_event_driven_r_r_m_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created Event-Driven RRM configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create Event-Driven RRM configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"event_driven_rrm_add": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"event_driven_rrm_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_event_driven_rrm(self, params):
        """
        Handles updating of Event-Driven RRM configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of Event-Driven RRM payloads to update.
                        Each payload must include at least 'id', 'designName',
                        and 'featureAttributes' as produced by your verifier.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for Event-Driven RRM Configurations.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                template_id = payload.get("id")

                self.log(
                    "Updating Event-Driven RRM configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_event_driven_r_r_m_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate returned tasks
                    self.check_tasks_response_status(response, "update_event_driven_r_r_m_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated Event-Driven RRM configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update Event-Driven RRM configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"event_driven_rrm_update": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"event_driven_rrm_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_dot11be(self, params):
        """
        Handles the creation of dot11be configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of dot11be payloads to create. Each payload should include at least 'designName'
                        and 'featureAttributes' as produced by your verifier.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for dot11be Configurations.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                self.log("Creating dot11be configuration: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_dot11be_status_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate returned tasks
                    self.check_tasks_response_status(response, "create_dot11be_status_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created dot11be configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create dot11be configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"dot11be_add": results}
            self.status = "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"dot11be_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_dot11be(self, params):
        """
        Handles the update of dot11be configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of dot11be payloads to update. Each payload should include at least 'id',
                        'designName', and 'featureAttributes' as produced by your verifier.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for dot11be Configurations.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                template_id = payload.get("id")
                self.log("Updating dot11be configuration: {0}".format(design_name), "DEBUG")

                if not template_id:
                    results[design_name] = "Skipping update: missing template ID"
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_dot11be_status_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate returned tasks
                    self.check_tasks_response_status(response, "update_dot11be_status_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated dot11be configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update dot11be configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"dot11be_update": results}
            self.status = "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"dot11be_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_dot11ax(self, params):
        """
        Handles the creation of dot11ax configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of dot11ax payloads to create. Each payload should include at least 'designName'
                        and 'featureAttributes' as produced by your verifier.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for dot11ax Configurations.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                self.log("Creating dot11ax configuration: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_dot11ax_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate returned tasks (keeps parity with other handlers)
                    self.check_tasks_response_status(response, "create_dot11ax_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created dot11ax configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create dot11ax configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"dot11ax_add": results}
            self.status = "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"dot11ax_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_dot11ax(self, params):
        """
        Handles the update of dot11ax configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of dot11ax payloads to update. Each payload should include 'id' (existing template id)
                        and may include 'designName' and 'featureAttributes'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for dot11ax Configurations.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                template_id = payload.get("id")
                self.log("Updating dot11ax configuration: design='{0}', id='{1}'".format(design_name, template_id), "DEBUG")

                if not template_id:
                    results[design_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_dot11ax_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate returned tasks (keeps parity with other handlers)
                    self.check_tasks_response_status(response, "update_dot11ax_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated dot11ax configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update dot11ax configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"dot11ax_update": results}
            # If every result contains 'Failed' or 'Exception' or 'Skipped', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"dot11ax_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_clean_air(self, params):
        """
        Handles the creation of CleanAir Profiles in Cisco Catalyst Center.
        Args:
            params (list): A list of CleanAir payloads to create.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for CleanAir Profiles.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params:
                design_name = payload.get("designName")
                self.log("Creating CleanAir Profile: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_clean_air_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "create_clean_air_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created CleanAir Profile."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create CleanAir Profile: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as e:
                    results[design_name] = "Exception while creating: {0}".format(str(e))
                    self.log(results[design_name], "ERROR")

            # Final message after all payloads processed
            self.msg = {"clean_air_add": results}
            self.status = "failed" if all("Failed" in v or "Exception" in v for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as e:
            self.msg = {"clean_air_add": "Exception during add: {0}".format(str(e))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_clean_air(self, params):
        """
        Handles the update of CleanAir Profiles in Cisco Catalyst Center.
        Args:
            params (list): A list of CleanAir payloads to update. Each must include 'id'.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for CleanAir Profiles.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params:
                design_name = payload.get("designName")
                template_id = payload.get("id")
                self.log("Updating CleanAir Profile: design='{0}', id='{1}'".format(design_name, template_id), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_clean_air_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "update_clean_air_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated CleanAir Profile."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update CleanAir Profile: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as e:
                    results[design_name] = "Exception while updating: {0}".format(str(e))
                    self.log(results[design_name], "ERROR")

            # Final result after processing all updates
            self.msg = {"clean_air_update": results}
            self.status = "failed" if all("Failed" in v or "Exception" in v for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as e:
            self.msg = {"clean_air_update": "Exception during update: {0}".format(str(e))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_advanced_ssids(self, params):
        """
        Handles updating Advanced SSIDs in Cisco Catalyst Center.
        Args:
            params (list): A list of Advanced SSID payloads to update. Each payload should include 'id' (existing template id).

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for Advanced SSIDs.", "INFO")
        self.log("Advanced SSID update params: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or payload.get("design_name") or "<unknown>"
                template_id = payload.get("id")
                self.log("Updating Advanced SSID: {0} (id={1})".format(design_name, template_id), "DEBUG")

                if not template_id:
                    results[design_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_advanced_ssid_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "update_advanced_ssid_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully updated Advanced SSID."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to update Advanced SSID: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while updating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"advanced_ssids_update": results}
            # If every result contains 'Failed' or 'Exception', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"advanced_ssids_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_advanced_ssids(self, params):
        """
        Handles the creation of Advanced SSIDs in Cisco Catalyst Center.
        Args:
            params (list): A list of Advanced SSID payloads to create (each payload should contain 'designName' and 'featureAttributes', etc.)

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for Advanced SSIDs.", "INFO")
        self.log("Advanced SSID add params: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("designName") or "<unknown>"
                self.log("Creating Advanced SSID: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_advanced_ssid_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # Reuse your existing task-check helper to validate task status
                    self.check_tasks_response_status(response, "create_advanced_ssid_configuration_feature_template")

                    # self.status / self.msg are expected to be set by check_tasks_response_status (or set below on failure)
                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created Advanced SSID."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create Advanced SSID: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while creating: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Summarize outcome
            self.msg = {"advanced_ssids_add": results}
            # If every result contains 'Failed' or 'Exception', mark overall as failed; otherwise success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            # catastrophic failure while iterating/processing
            self.msg = {"advanced_ssids_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            # set_operation_result(...).check_return_status() matches your AAA pattern
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_aaa_radius_attributes(self, params):
        """
        Handles the creation of AAA Radius Attributes in Cisco Catalyst Center.
        Args:
            params (list): A list of AAA Radius Attribute payloads to create.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for AAA Radius Attributes.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params:
                design_name = payload.get("designName")
                self.log("Creating AAA Radius Attribute: {0}".format(design_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_aaa_radius_attributes_configuration_feature_template",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "create_aaa_radius_attributes_configuration_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully created AAA Radius Attribute."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to create AAA Radius Attribute: {0}".format(
                            fail_reason
                        )
                        self.log(results[design_name], "ERROR")

                except Exception as e:
                    results[design_name] = "Exception while creating: {0}".format(str(e))
                    self.log(results[design_name], "ERROR")

            # Final message after all payloads processed
            self.msg = {"aaa_radius_attributes_add": results}
            self.status = (
                "failed" if all("Failed" in v or "Exception" in v for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as e:
            self.msg = {
                "aaa_radius_attributes_add": "Exception during add: {0}".format(str(e))
            }
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_update_aaa_radius_attributes(self, params):
        """
        Handles the update of AAA Radius Attributes in Cisco Catalyst Center.
        Args:
            params (list): A list of AAA Radius Attribute payloads to update.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for AAA Radius Attributes.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params:
                design_name = payload.get("designName")
                self.log("Updating AAA Radius Attribute: {0}".format(design_name), "DEBUG")

                response = self.dnac._exec(
                    family="wireless",
                    function="update_aaa_radius_attributes_configuration_feature_template",
                    op_modifies=True,
                    params={"id": self.template_id, "payload": payload},
                )
                self.log("Received API response: {0}".format(response), "DEBUG")
                self.check_tasks_response_status(
                    response, "update_aaa_radius_attributes_configuration_feature_template"
                )

                if self.status not in ["failed", "exited"]:
                    results[design_name] = "Successfully updated AAA Radius Attribute."
                else:
                    fail_reason = self.msg
                    results[design_name] = "Failed to update AAA Radius Attribute: {0}".format(
                        fail_reason
                    )
                    self.log(results[design_name], "ERROR")

            # Final result after all payloads processed
            self.msg = {"aaa_radius_attributes_update": results}
            # If all updates failed → status = failed, else = success
            self.status = (
                "failed" if all("Failed" in v for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as e:
            self.msg = {
                "aaa_radius_attributes_update": "Exception during update: {0}".format(str(e))
            }
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_rrm_general(self, params):
        """
        Handles deletion of RRM General configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of RRM General payloads to delete.
                        Each payload must contain at least the template 'id'
                        and optionally 'design_name' or 'designName'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for RRM General Configurations.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting RRM General configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_r_r_m_general_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted RRM General configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete RRM General configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"rrm_general_delete": results}
            # If every result is Failed/Exception/Skipped -> failed; else success
            self.status = (
                "failed" if results and all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values())
                else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"rrm_general_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_rrm_fra(self, params):
        """
        Handles deletion of RRM-FRA configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of RRM-FRA payloads to delete.
                        Each payload must contain at least the template 'id'
                        and optionally 'design_name' or 'designName'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for RRM-FRA Configurations.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting RRM-FRA configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_r_r_m_f_r_a_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # Validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted RRM-FRA configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete RRM-FRA configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"rrm_fra_delete": results}
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"rrm_fra_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_flexconnect(self, params):
        self.log("Processing DELETE for FlexConnect.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")
        results = {}
        try:
            for payload in params or []:
                dn = payload.get("design_name") or payload.get("designName") or "Unknown"
                tid = payload.get("id")
                self.log("Deleting FlexConnect: design='{0}', id='{1}'".format(dn, tid), "DEBUG")
                if not tid:
                    results[dn] = "Skipped delete: missing 'id' in payload."
                    self.log(results[dn], "ERROR")
                    continue
                try:
                    resp = self.dnac._exec(
                        family="wireless",
                        function="delete_flex_connect_configuration_feature_template",
                        op_modifies=True,
                        params={"id": tid},
                    )
                    self.log("Received API response: {0}".format(resp), "DEBUG")
                    self.check_tasks_response_status(resp, "delete_feature_template")
                    results[dn] = (
                        "Successfully deleted FlexConnect."
                        if self.status not in ["failed", "exited"]
                        else "Failed to delete FlexConnect: {0}".format(self.msg)
                    )
                except Exception as exc:
                    results[dn] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[dn], "ERROR")
            self.msg = {"flexconnect_delete": results}
            self.status = "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self
        except Exception as exc:
            self.msg = {"flexconnect_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_dot11be(self, params):
        """
        Handles deletion of dot11be configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of dot11be payloads to delete.
                        Each payload must contain at least the template 'id' and optionally 'design_name' or 'designName'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for dot11be Configurations.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting dot11be configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_dot11be_status_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted dot11be configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete dot11be configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"dot11be_delete": results}
            # If every result contains 'Failed' or 'Exception' or 'Skipped', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"dot11be_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_dot11ax(self, params):
        """
        Handles deletion of dot11ax configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of dot11ax payloads to delete.
                        Each payload must contain at least the template 'id' and optionally 'design_name' or 'designName'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for dot11ax Configurations.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting dot11ax configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_dot11ax_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted dot11ax configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete dot11ax configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"dot11ax_delete": results}
            # If every result contains 'Failed' or 'Exception' or 'Skipped', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"dot11ax_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_clean_air(self, params):
        """
        Handles the deletion of CleanAir profiles in Cisco Catalyst Center.
        Args:
            params (list): A list of CleanAir payloads to delete.
                        Each payload must contain at least the template 'id' and optionally 'design_name' or 'designName'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for CleanAir Profiles.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting CleanAir Profile: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_clean_air_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted CleanAir Profile."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete CleanAir Profile: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"clean_air_delete": results}
            # If every result contains 'Failed' or 'Exception' or 'Skipped', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"clean_air_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_event_driven_rrm(self, params):
        """
        Handles deletion of Event-Driven RRM configurations in Cisco Catalyst Center.
        Args:
            params (list): A list of Event-Driven RRM payloads to delete.
                        Each payload must contain at least the template 'id'
                        and optionally 'design_name' or 'designName'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for Event-Driven RRM Configurations.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting Event-Driven RRM configuration: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    # DNAC API for Event-Driven RRM delete
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_event_driven_r_r_m_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted Event-Driven RRM configuration."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete Event-Driven RRM configuration: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"event_driven_rrm_delete": results}
            # If every result contains 'Failed' or 'Exception' or 'Skipped', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values())
                else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"event_driven_rrm_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_advanced_ssids(self, params):
        """
        Handles the deletion of Advanced SSIDs in Cisco Catalyst Center.
        Args:
            params (list): A list of Advanced SSID payloads to delete.
                        Each payload must contain at least the template 'id' and optionally 'design_name'.
        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for Advanced SSIDs.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                design_name = payload.get("design_name") or payload.get("designName") or "Unknown"
                template_id = payload.get("id")

                self.log(
                    "Deleting Advanced SSID: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                if not template_id:
                    results[design_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[design_name], "ERROR")
                    continue

                try:
                    # NOTE: replace function name if your DNAC SDK expects a different delete function
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_advanced_ssid_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    # validate the returned task(s)
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted Advanced SSID."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete Advanced SSID: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as exc:
                    results[design_name] = "Exception while deleting: {0}".format(str(exc))
                    self.log(results[design_name], "ERROR")

            # Final aggregated message
            self.msg = {"advanced_ssids_delete": results}
            # If every result contains 'Failed' or 'Exception', mark overall as failed; else success
            self.status = (
                "failed" if all(("Failed" in v or "Exception" in v or "Skipped" in v) for v in results.values()) else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"advanced_ssids_delete": "Exception during delete: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_delete_aaa_radius_attributes(self, params):
        """
        Handles the deletion of AAA Radius Attributes in Cisco Catalyst Center.
        Args:
            params (list): A list of AAA Radius Attribute payloads to delete.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing DELETE for AAA Radius Attributes.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params:
                design_name = payload.get("design_name", "Unknown")
                template_id = payload.get("id")

                self.log(
                    "Deleting AAA Radius Attribute: design='{0}', id='{1}'".format(design_name, template_id),
                    "DEBUG",
                )

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_aaa_radius_attributes_configuration_feature_template",
                        op_modifies=True,
                        params={"id": template_id},
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")
                    self.check_tasks_response_status(response, "delete_feature_template")

                    if self.status not in ["failed", "exited"]:
                        results[design_name] = "Successfully deleted AAA Radius Attribute."
                    else:
                        fail_reason = self.msg
                        results[design_name] = "Failed to delete AAA Radius Attribute: {0}".format(fail_reason)
                        self.log(results[design_name], "ERROR")

                except Exception as e:
                    results[design_name] = "Exception while deleting: {0}".format(str(e))
                    self.log(results[design_name], "ERROR")

            # Final result after processing all deletions
            self.msg = {"aaa_radius_attributes_delete": results}
            self.status = "failed" if all("Failed" in v or "Exception" in v for v in results.values()) else "success"
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as e:
            self.msg = {"aaa_radius_attributes_delete": "Exception during delete: {0}".format(str(e))}
            self.status = "failed"
            self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()
            return self

    def process_add_802_11_be_profile(self, params):
        """
        Handles the creation of 802.11be (Wi-Fi 7) profiles in Cisco Catalyst Center.

        Args:
            params (list): A list of 802.11be payloads to create.
                        Each payload should include 'profileName' and 'featureAttributes'.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing ADD for 802.11be Profiles.", "INFO")
        self.log("Params for ADD: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                profile_name = payload.get("profileName") or "<unknown>"
                self.log("Creating 802.11be profile: {0}".format(profile_name), "DEBUG")

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="create_a80211be_profile",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")

                    # Validate async task
                    self.check_tasks_response_status(response, "create80211beProfile")

                    if self.status not in ["failed", "exited"]:
                        results[profile_name] = "Successfully created 802.11be profile."
                    else:
                        fail_reason = self.msg
                        results[profile_name] = (
                            "Failed to create 802.11be profile: {0}".format(fail_reason)
                        )
                        self.log(results[profile_name], "ERROR")

                except Exception as exc:
                    results[profile_name] = (
                        "Exception while creating 802.11be profile: {0}".format(str(exc))
                    )
                    self.log(results[profile_name], "ERROR")

            # Final aggregated message
            self.msg = {"80211be_add": results}
            self.status = (
                "failed"
                if all(("Failed" in v or "Exception" in v) for v in results.values())
                else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"80211be_add": "Exception during add: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()
            return self

    def process_update_802_11_be_profile(self, params):
        """
        Handles the update of 802.11be (Wi-Fi 7) profiles in Cisco Catalyst Center.

        Args:
            params (list): A list of 802.11be payloads to update.
                        Each payload must include 'id' and may include
                        'profileName' and 'featureAttributes'.

        Returns:
            self (with self.msg and self.status set)
        """
        self.log("Processing UPDATE for 802.11be Profiles.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                profile_name = payload.get("profileName") or "<unknown>"
                profile_id = payload.get("id")

                self.log(
                    "Updating 802.11be profile: profile='{0}', id='{1}'".format(
                        profile_name, profile_id
                    ),
                    "DEBUG",
                )

                if not profile_id:
                    results[profile_name] = "Skipped update: missing 'id' in payload."
                    self.log(results[profile_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update80211be_profile",
                        op_modifies=True,
                        params=payload,
                    )
                    self.log("Received API response: {0}".format(response), "DEBUG")

                    # Validate async task
                    self.check_tasks_response_status(response, "update80211beProfile")

                    if self.status not in ["failed", "exited"]:
                        results[profile_name] = "Successfully updated 802.11be profile."
                    else:
                        fail_reason = self.msg
                        results[profile_name] = (
                            "Failed to update 802.11be profile: {0}".format(fail_reason)
                        )
                        self.log(results[profile_name], "ERROR")

                except Exception as exc:
                    results[profile_name] = (
                        "Exception while updating 802.11be profile: {0}".format(str(exc))
                    )
                    self.log(results[profile_name], "ERROR")

            # Final aggregated message
            self.msg = {"80211be_update": results}
            self.status = (
                "failed"
                if all(
                    ("Failed" in v or "Exception" in v or "Skipped" in v)
                    for v in results.values()
                )
                else "success"
            )
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {"80211be_update": "Exception during update: {0}".format(str(exc))}
            self.status = "failed"
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()
            return self

    def validate_required_ssid_params(self, ssid, state="merged"):
        """
        Validates the required parameters for an SSID based on the specified state.
        Args:
            ssid (dict): The SSID configuration parameters.
            state (str): The state of the SSID configuration, either "merged" or "deleted".
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Starting validation for required SSID parameters with state: {0}.".format(
                state
            ),
            "DEBUG",
        )

        # Determine required parameters based on state
        if state == "merged":
            required_params = ["ssid_name", "ssid_type"]
        elif state == "deleted":
            required_params = ["ssid_name"]

        # Check for missing required parameters
        missing_params = [param for param in required_params if param not in ssid]
        if missing_params:
            self.fail_and_exit(
                "The following required parameters for SSID configuration are missing: {}. "
                "Provided parameters: {}".format(", ".join(missing_params), ssid)
            )

        # Validate the length of ssid_name if it is present
        ssid_name = ssid.get("ssid_name")
        if ssid_name and len(ssid_name) > 32:
            self.fail_and_exit(
                "The 'ssid_name' exceeds the maximum length of 32 characters. "
                "Provided 'ssid_name': {} (length: {})".format(
                    ssid_name, len(ssid_name)
                )
            )

        self.log(
            "Required SSID parameters validated successfully for state: {0}.".format(
                state
            ),
            "DEBUG",
        )

    def validate_ssid_type_params(self, ssid_type, l2_security, l3_security):
        """
        Validates the parameters based on the SSID type.
        Args:
            ssid_type (str): The type of the SSID ("Enterprise" or "Guest").
            l2_security (dict): The Layer 2 security settings for the SSID.
            l3_security (dict): The Layer 3 security settings for the SSID.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Normalize ssid_type to lowercase for consistent validation
        ssid_type_normalized = ssid_type.lower()

        # Define required parameters based on normalized ssid_type
        required_params = {
            "enterprise": [("l2_security", l2_security)],
            "guest": [("l2_security", l2_security), ("l3_security", l3_security)],
        }

        self.log(
            "Starting validation for SSID type parameters for SSID type: {0}.".format(
                ssid_type_normalized
            ),
            "DEBUG",
        )

        # Validate normalized ssid_type
        if ssid_type_normalized not in required_params:
            self.msg = "Invalid ssid_type: {}. Allowed types are 'Enterprise' and 'Guest'.".format(
                ssid_type
            )
            self.fail_and_exit(self.msg)

        self.log(
            "SSID type parameters validated successfully for SSID type: {0}.".format(
                ssid_type_normalized
            ),
            "INFO",
        )

    def validate_site_name_hierarchy(self, site_exists, site_id, site_name_hierarchy):
        """
        Validates the site name hierarchy for a given site.
        Args:
            site_exists (bool): Indicates whether the site exists.
            site_id (str): The ID of the site if it exists.
            site_name_hierarchy (str): The hierarchy name of the site to be validated.
        Raises:
            Exception: If the site does not exist, an exception is raised with a descriptive message.
        """
        # Check if the site exists
        if not site_exists:
            self.msg = (
                "Error: Site '{0}' does not exist in Cisco Catalyst Center.".format(
                    site_name_hierarchy
                )
            )
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "Site '{0}' exists with ID: {1}.".format(site_name_hierarchy, site_id),
                "DEBUG",
            )

        self.log(
            "Successfully validated site_name_hierarchy for Site: '{0}'.".format(
                site_name_hierarchy
            ),
            "DEBUG",
        )

    def validate_ssid_radio_policy_params(self, ssid_name, radio_policy):
        """
        Validates the radio policy parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            radio_policy (dict): The radio policy settings to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Define valid radio bands
        valid_radio_bands = {2.4, 5, 6}

        # Validate radio_bands if present in radio_policy
        if "radio_bands" in radio_policy:
            # Convert float to int for comparison
            radio_bands_set = set(map(float, radio_policy["radio_bands"]))

            # Check if radio_bands_set is a subset of valid_radio_bands
            if not radio_bands_set.issubset(valid_radio_bands):
                self.msg = "Invalid elements in 'radio_bands' for SSID: '{0}'. Allowed values are [2.4, 5, 6].".format(
                    ssid_name
                )
                self.fail_and_exit(self.msg)

            # Validate 2_dot_4_ghz_band_policy
            if "2_dot_4_ghz_band_policy" in radio_policy:
                if 2.4 not in radio_bands_set:
                    self.msg = "For SSID: {0} 2_dot_4_ghz_band_policy is specified but 2.4 GHz is not enabled in 'radio_bands'.".format(
                        ssid_name
                    )
                    self.fail_and_exit(self.msg)

            # Validate band_select
            if "band_select" in radio_policy and radio_policy["band_select"]:
                if not (radio_bands_set == {2.4, 5} or radio_bands_set == {2.4, 5, 6}):
                    self.msg = (
                        "Error enabling 'band_select' for SSID: '{0}'. 'band_select' can only be enabled when 'radio_bands' are atleast"
                        " 2.4GHz and 5GHz or Triple band operation [2.4, 5, 6].".format(
                            ssid_name
                        )
                    )
                    self.fail_and_exit(self.msg)

            # Validate 6_ghz_client_steering
            if (
                "6_ghz_client_steering" in radio_policy
                and radio_policy["6_ghz_client_steering"]
            ):
                if 6 not in radio_bands_set:
                    self.msg = (
                        "Error enabling '6_ghz_client_steering' for SSID: '{0}', it can only be enabled if 'radio_bands' "
                        "includes 6 GHz.".format(ssid_name)
                    )
                    self.fail_and_exit(self.msg)

        # Validate 2_dot_4_ghz_band_policy
        if "2_dot_4_ghz_band_policy" in radio_policy and radio_policy[
            "2_dot_4_ghz_band_policy"
        ] not in ["802.11-bg", "802.11-g"]:
            self.msg = "Invalid '2_dot_4_ghz_band_policy' provided for SSID: '{0}'. Allowed values are ['802.11-bg',  '802.11-g'].".format(
                ssid_name
            )
            self.fail_and_exit(self.msg)

        self.log(
            "Radio policy parameters validated successfully for SSID: {0}.".format(
                ssid_name
            ),
            "INFO",
        )

    def validate_qos_params(self, ssid_name, qos, fast_lane_enabled):
        """
        Validates the Quality of Service (QoS) parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            qos (dict): The Quality of Service settings to be validated.
            fast_lane_enabled (bool): Indicates if Fast Lane is enabled for the SSID.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Starting validation for Quality of Service parameters for SSID: {0}.".format(
                ssid_name
            ),
            "DEBUG",
        )

        # Validate Quality of Service parameters
        if fast_lane_enabled:
            if qos:
                self.msg = (
                    "The Quality of Service selection will not be applicable when Fast Lane is enabled for SSID: '{0}'. "
                    "QoS settings should be empty when 'fast_lane' is enabled."
                ).format(ssid_name)
                self.fail_and_exit(self.msg)

        # Validate egress QoS
        if "egress" in qos:
            egress = qos["egress"]
            if egress:
                if egress.upper() not in ["PLATINUM", "SILVER", "GOLD", "BRONZE"]:
                    self.msg = "Invalid 'egress' QoS for SSID: '{0}'. Allowed values are ['PLATINUM', 'SILVER', 'GOLD', 'BRONZE'].".format(
                        ssid_name
                    )
                    self.fail_and_exit(self.msg)

        # Validate ingress QoS
        if "ingress" in qos:
            ingress = qos["ingress"]
            if ingress:
                if ingress.upper() not in [
                    "PLATINUM-UP",
                    "SILVER-UP",
                    "GOLD-UP",
                    "BRONZE-UP",
                ]:
                    self.msg = "Invalid 'ingress' QoS for SSID: '{0}'. Allowed values are ['PLATINUM-UP', 'SILVER-UP', 'GOLD-UP', 'BRONZE-UP'].".format(
                        ssid_name
                    )
                    self.fail_and_exit(self.msg)

        self.log(
            "Quality of Service parameters validated successfully for SSID: {0}.".format(
                ssid_name
            ),
            "INFO",
        )

    def validate_l2_security_params(
        self,
        ssid_name,
        l2_security,
        fast_transition,
        fast_transition_over_the_ds,
        wpa_encryption,
        auth_key_management,
        cckm_timestamp_tolerance=None,
    ):
        """
        Validates the Layer 2 security parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            l2_security (dict): The Layer 2 security settings to be validated.
            fast_transition (str): The fast transition setting (For example, "DISABLE", "ENABLE", "ADAPTIVE").
            fast_transition_over_the_ds (bool): Indicates if fast transition over the DS is enabled.
            wpa_encryption (list): A list of WPA encryption methods used.
            auth_key_management (list): A list of authentication key management methods used.
            cckm_timestamp_tolerance (int, optional): Tolerance for CCKM timestamp, if applicable.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        l2_auth_type = l2_security.get("l2_auth_type")
        mpsk_settings = l2_security.get("mpsk_settings")

        valid_configurations = self.get_valid_l2_security_configurations()

        self.validate_l2_auth_type(ssid_name, l2_auth_type, valid_configurations)
        self.validate_required_params(
            ssid_name,
            l2_auth_type,
            wpa_encryption,
            auth_key_management,
            fast_transition,
            valid_configurations,
        )
        self.validate_mpsk_settings(ssid_name, mpsk_settings)
        self.validate_cckm_timestamp_tolerance(ssid_name, cckm_timestamp_tolerance)

    def get_valid_l2_security_configurations(self):
        """
        Returns a dictionary of valid Layer 2 security configurations.
        Returns:
            dict: A dictionary containing valid Layer 2 security configurations.
        """
        return {
            "WPA2_ENTERPRISE": {
                "required": ["wpa_encryption", "auth_key_management"],
                "fast_transition_options": {
                    "ADAPTIVE": {
                        "CCMP128": ["CCKM", "802.1X-SHA1", "802.1X-SHA2"],
                        "GCMP128": ["SUITE-B-1X"],
                        "CCMP256": ["SUITE-B-192X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                    "DISABLE": {
                        "CCMP128": ["CCKM", "802.1X-SHA1", "802.1X-SHA2"],
                        "GCMP128": ["SUITE-B-1X"],
                        "CCMP256": ["SUITE-B-192X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                    "ENABLE": {
                        "CCMP128": ["CCKM", "802.1X-SHA1", "802.1X-SHA2", "FT+802.1X"],
                        "GCMP128": ["SUITE-B-1X"],
                        "CCMP256": ["SUITE-B-192X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                },
                "ap_beacon_protection_allowed": False,
            },
            "WPA3_ENTERPRISE": {
                "fast_transition_options": {
                    "ADAPTIVE": {
                        "CCMP128": ["802.1X-SHA1", "802.1X-SHA2"],
                        "GCMP128": ["SUITE-B-1X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                    "DISABLE": {
                        "CCMP128": ["802.1X-SHA1", "802.1X-SHA2"],
                        "GCMP128": ["SUITE-B-1X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                    "ENABLE": {
                        "CCMP128": ["802.1X-SHA1", "802.1X-SHA2", "FT+802.1X"],
                        "GCMP128": ["SUITE-B-1X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                },
                "ap_beacon_protection_allowed": True,
            },
            "WPA2_WPA3_ENTERPRISE": {
                "required": ["wpa_encryption", "auth_key_management"],
                "fast_transition_options": {
                    "ADAPTIVE": {
                        "CCMP128": ["CCKM", "802.1X-SHA1", "802.1X-SHA2"],
                        "GCMP128": ["SUITE-B-1X"],
                        "CCMP256": ["SUITE-B-192X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                    "DISABLE": {
                        "CCMP128": ["CCKM", "802.1X-SHA1", "802.1X-SHA2"],
                        "GCMP128": ["SUITE-B-1X"],
                        "CCMP256": ["SUITE-B-192X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                    "ENABLE": {
                        "CCMP128": ["CCKM", "802.1X-SHA1", "802.1X-SHA2", "FT+802.1X"],
                        "GCMP128": ["SUITE-B-1X"],
                        "CCMP256": ["SUITE-B-192X"],
                        "GCMP256": ["SUITE-B-192X"],
                    },
                },
                "ap_beacon_protection_allowed": True,
            },
            "WPA2_PERSONAL": {
                "required": ["passphrase", "wpa_encryption", "auth_key_management"],
                "fast_transition_options": {
                    "ADAPTIVE": {"CCMP128": ["PSK", "PSK-SHA2", "EASY-PSK"]},
                    "DISABLE": {"CCMP128": ["PSK", "PSK-SHA2", "EASY-PSK"]},
                    "ENABLE": {"CCMP128": ["PSK", "PSK-SHA2", "EASY-PSK", "FT+PSK"]},
                },
                "ap_beacon_protection_allowed": False,
            },
            "WPA3_PERSONAL": {
                "required": ["passphrase", "wpa_encryption", "auth_key_management"],
                "fast_transition_options": {
                    "ENABLE": {
                        "CCMP128": ["SAE", "SAE-EXT-KEY", "FT+SAE", "FT+SAE-EXT-KEY"],
                        "GCMP256": ["SAE-EXT-KEY", "FT+SAE-EXT-KEY"],
                    },
                    "DISABLE": {
                        "CCMP128": ["SAE", "SAE-EXT-KEY"],
                        "GCMP256": ["SAE-EXT-KEY"],
                    },
                },
                "ap_beacon_protection_allowed": True,
            },
            "WPA2_WPA3_PERSONAL": {
                "required": ["passphrase", "wpa_encryption", "auth_key_management"],
                "fast_transition_options": {
                    "ENABLE": {
                        "CCMP128": [
                            "SAE",
                            "SAE-EXT-KEY",
                            "PSK",
                            "PSK-SHA2",
                            "FT+SAE",
                            "FT+PSK",
                            "FT+SAE-EXT-KEY",
                        ],
                        "GCMP256": ["SAE-EXT-KEY", "FT+SAE-EXT-KEY"],
                    },
                    "DISABLE": {
                        "CCMP128": ["SAE", "SAE-EXT-KEY", "PSK", "PSK-SHA2"],
                        "GCMP256": ["SAE-EXT-KEY"],
                    },
                },
                "ap_beacon_protection_allowed": True,
            },
            "OPEN-SECURED": {
                "required": ["wpa_encryption", "auth_key_management"],
                "fast_transition_options": {
                    "DISABLE": {"CCMP128": ["OWE"], "GCMP256": ["OWE"]}
                },
                "ap_beacon_protection_allowed": False,
            },
            "OPEN": {
                "required": [],
                "fast_transition_options": {
                    "ADAPTIVE": {},
                    "DISABLE": {},
                    "ENABLE": {},
                },
                "ap_beacon_protection_allowed": False,
            },
        }

    def validate_l2_auth_type(self, ssid_name, l2_auth_type, valid_configurations):
        """
        Validates the Layer 2 authentication type for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            l2_auth_type (str): The Layer 2 authentication type to be validated.
            valid_configurations (dict): A dictionary of valid Layer 2 security configurations.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log("Validating 'l2_auth_type' for SSID: {0}".format(ssid_name), "DEBUG")

        if l2_auth_type and l2_auth_type.upper() not in valid_configurations:
            valid_l2_auth_types = valid_configurations.keys()
            self.msg = "Invalid 'l2_auth_type': '{0}' supplied for SSID: '{1}'. Allowed values are {2}.".format(
                l2_auth_type, ssid_name, valid_l2_auth_types
            )
            self.fail_and_exit(self.msg)
        self.log("'l2_auth_type' for SSID: {0} is valid.".format(ssid_name), "INFO")

    def validate_required_params(
        self,
        ssid_name,
        l2_auth_type,
        wpa_encryption,
        auth_key_management,
        fast_transition,
        valid_configurations,
    ):
        """
        Validates the required parameters for a given Layer 2 authentication type.
        Args:
            ssid_name (str): The name of the SSID.
            l2_auth_type (str): The Layer 2 authentication type.
            wpa_encryption (list): The WPA encryption settings.
            auth_key_management (list): The authentication key management settings.
            fast_transition (str): The fast transition setting.
            valid_configurations (dict): A dictionary of valid Layer 2 security configurations.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Validating required parameters for SSID: {0}".format(ssid_name), "DEBUG"
        )

        auth_config = valid_configurations.get(l2_auth_type, {})
        required_l2_auth_params = auth_config.get("required", [])
        fast_transition = fast_transition.upper()
        fast_transition_options = auth_config.get("fast_transition_options", {}).get(
            fast_transition, {}
        )

        # Validate WPA encryption settings
        if "wpa_encryption" in required_l2_auth_params:
            if wpa_encryption and not all(
                encryption.upper() in fast_transition_options
                for encryption in wpa_encryption
            ):
                allowed_options = ", ".join(fast_transition_options.keys())
                self.msg = (
                    "For SSID: '{0}', invalid 'wpa_encryption' provided for L2 Authentication type: '{1}'. "
                    "Provided: {2}. Allowed options for fast transition '{3}': {4}.".format(
                        ssid_name,
                        l2_auth_type,
                        wpa_encryption,
                        fast_transition,
                        allowed_options,
                    )
                )
                self.fail_and_exit(self.msg)

        # Validate authentication key management settings
        if (
            "auth_key_management" in required_l2_auth_params
            and auth_key_management
            and wpa_encryption
        ):
            for akm in auth_key_management:
                akm = akm.upper()
                is_valid_akm = any(
                    akm in fast_transition_options.get(encryption, [])
                    for encryption in wpa_encryption
                )
                if not is_valid_akm:
                    allowed_options = ", ".join(
                        "{0}: {1}".format(
                            encryption,
                            ", ".join(fast_transition_options.get(encryption, [])),
                        )
                        for encryption in wpa_encryption
                    )
                    self.msg = (
                        "For SSID: '{0}', invalid 'auth_key_management' provided for L2 Authentication type: '{1}'. "
                        "Provided: {2}. Allowed options for fast transition '{3}': {4}.".format(
                            ssid_name,
                            l2_auth_type,
                            auth_key_management,
                            fast_transition,
                            allowed_options,
                        )
                    )
                    self.fail_and_exit(self.msg)

        self.log(
            "Required parameters for SSID: {0} are valid.".format(ssid_name), "INFO"
        )

    def validate_mpsk_settings(self, ssid_name, mpsk_settings):
        """
        Validates the MPSK (Multiple Pre-Shared Key) settings for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            mpsk_settings (list): The MPSK settings to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log("Validating MPSK settings for SSID: {0}".format(ssid_name), "DEBUG")
        if mpsk_settings:
            if not isinstance(mpsk_settings, list) or len(mpsk_settings) >= 5:
                self.msg = "For SSID: '{0}', MPSK settings must be a list with less than 5 entries.".format(
                    ssid_name
                )
                self.fail_and_exit(self.msg)

            for idx, mpsk_setting in enumerate(mpsk_settings):
                mpsk_passphrase = mpsk_setting.get("mpsk_passphrase")
                if not mpsk_passphrase:
                    self.msg = "For SSID: '{0}', MPSK settings entry {1} requires a 'passphrase' to be provided.".format(
                        ssid_name, idx + 1
                    )
                    self.fail_and_exit(self.msg)

                mpsk_priority = mpsk_setting.get("mpsk_priority")
                if mpsk_priority is not None and not (0 <= mpsk_priority <= 4):
                    self.msg = (
                        "For SSID: '{0}', entry {1}, Invalid 'mpsk_priority' in MPSK settings: {2}. "
                        "Allowed values are 0 to 4.".format(
                            ssid_name, idx + 1, mpsk_priority
                        )
                    )
                    self.fail_and_exit(self.msg)

                mpsk_passphrase_type = mpsk_setting.get("mpsk_passphrase_type", "ASCII")
                if mpsk_passphrase_type:
                    mpsk_passphrase_type = mpsk_passphrase_type.upper()
                    if mpsk_passphrase_type not in ["HEX", "ASCII"]:
                        self.msg = (
                            "For SSID: '{0}', entry {1}, invalid passphrase_type in MPSK settings: {2}. "
                            "Allowed values are 'HEX' or 'ASCII'.".format(
                                ssid_name, idx + 1, mpsk_passphrase_type
                            )
                        )
                        self.fail_and_exit(self.msg)

                    if mpsk_passphrase_type == "ASCII":
                        if not (8 <= len(mpsk_passphrase) <= 63):
                            self.msg = (
                                "For SSID: '{0}', entry {1}, invalid ASCII passphrase length in MPSK settings. "
                                "Must be between 8 and 63 characters.".format(
                                    ssid_name, idx + 1
                                )
                            )
                            self.fail_and_exit(self.msg)
                    elif mpsk_passphrase_type == "HEX":
                        if len(mpsk_passphrase) != 64:
                            self.msg = (
                                "For SSID: '{0}', entry {1}, invalid HEX passphrase length in MPSK settings. "
                                "Must be exactly 64 characters.".format(
                                    ssid_name, idx + 1
                                )
                            )
                            self.fail_and_exit(self.msg)
        self.log("MPSK settings for SSID: {0} are valid.".format(ssid_name), "INFO")

    def validate_cckm_timestamp_tolerance(self, ssid_name, cckm_timestamp_tolerance):
        """
        Validates the CCKM (Cisco Centralized Key Management) timestamp tolerance for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            cckm_timestamp_tolerance (int): The CCKM timestamp tolerance to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Validating CCKM timestamp tolerance for SSID: {0}".format(ssid_name),
            "DEBUG",
        )

        if cckm_timestamp_tolerance:
            if not (1000 <= cckm_timestamp_tolerance <= 5000):
                self.msg = (
                    "For SSID: {0}, invalid 'cckm_timestamp_tolerance': {1}. "
                    "Allowed range is 1000 to 5000.".format(
                        ssid_name, cckm_timestamp_tolerance
                    )
                )
                self.fail_and_exit(self.msg)
        self.log(
            "CCKM timestamp tolerance for SSID: {0} is valid.".format(ssid_name), "INFO"
        )

    def validate_l3_security_aaa_params(self, ssid_name, ssid_type, l3_security, aaa):
        """
        Validates the Layer 3 security and AAA parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            ssid_type (str): The type of the SSID (For example, "Enterprise", "Guest").
            l3_security (dict): The Layer 3 security settings to be validated.
            aaa (dict): The AAA (Authentication, Authorization, and Accounting) settings to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Validate l3_security settings
        if l3_security:
            # Extract necessary information from l3_security
            l3_auth_type = l3_security.get("l3_auth_type")
            auth_server = l3_security.get("auth_server")
            web_auth_url = l3_security.get("web_auth_url")
            enable_sleeping_client = l3_security.get("enable_sleeping_client", False)
            sleeping_client_timeout = l3_security.get("sleeping_client_timeout", 720)

            # Validate l3_auth_type
            if not l3_auth_type:
                self.msg = (
                    "For SSID: '{0}', 'l3_auth_type' not provided, it is a required parameter. "
                    "Valid values are 'OPEN' or 'WEB_AUTH'.".format(ssid_name)
                )
                self.fail_and_exit(self.msg)

            if l3_auth_type not in ["OPEN", "WEB_AUTH"]:
                self.msg = (
                    "For SSID: '{0}', invalid 'l3_auth_type': '{1}'. "
                    "Allowed values are 'OPEN' or 'WEB_AUTH'.".format(
                        ssid_name, l3_auth_type
                    )
                )
                self.fail_and_exit(self.msg)

            # Validate auth_server when l3_auth_type is WEB_AUTH
            if l3_auth_type == "WEB_AUTH":
                if auth_server:
                    if auth_server not in [
                        "central_web_authentication",
                        "web_authentication_internal",
                        "web_authentication_external",
                        "web_passthrough_internal",
                        "web_passthrough_external",
                    ]:
                        self.msg = (
                            "For SSID: '{0}', invalid 'auth_server': '{1}' with 'l3_auth_type' as 'WEB_AUTH'. "
                            "Allowed values are 'central_web_authentication', 'web_authentication_internal', "
                            "'web_authentication_external', 'web_passthrough_internal', 'web_passthrough_external'.".format(
                                ssid_name, auth_server
                            )
                        )
                        self.fail_and_exit(self.msg)

                    # Validate web_auth_url for specific auth_server types
                    if (
                        auth_server
                        in ["web_authentication_external", "web_passthrough_external"]
                        and not web_auth_url
                    ):
                        self.msg = "For SSID: '{0}', 'web_auth_url' is required when 'auth_server' is '{1}'.".format(
                            ssid_name, auth_server
                        )
                        self.fail_and_exit(self.msg)

            # Validate sleeping_client_timeout
            if enable_sleeping_client and (
                not isinstance(sleeping_client_timeout, int)
                or sleeping_client_timeout <= 0
            ):
                self.msg = (
                    "For SSID: '{0}', invalid 'sleeping_client_timeout': '{1}'. "
                    "Must be a positive integer.".format(
                        ssid_name, sleeping_client_timeout
                    )
                )
                self.fail_and_exit(self.msg)

        # Validate AAA settings
        if aaa:
            # Extract necessary information from aaa
            auth_servers_ip_list = aaa.get("auth_servers_ip_address_list", [])
            # aaa_override = aaa.get("aaa_override", False)
            # mac_filtering = aaa.get("mac_filtering", False)
            enable_posture = aaa.get("enable_posture", False)
            pre_auth_acl_name = aaa.get("pre_auth_acl_name", None)
            l3_auth_type = l3_security.get("l3_auth_type") if l3_security else None

            # Validate AAA for Guest SSID with Central Web Authentication
            if (
                ssid_type == "Guest"
                and l3_auth_type
                and l3_auth_type == "central_web_authentication"
            ):
                if not auth_servers_ip_list:
                    self.msg = (
                        "For SSID: '{0}', at least one server IP is required in 'auth_servers_ip_address_list' "
                        "when 'l3_auth_type' is 'central_web_authentication'.".format(
                            ssid_name
                        )
                    )
                    self.fail_and_exit(self.msg)

            # Validate enable_posture and pre_auth_acl_name for Enterprise SSID
            if enable_posture:
                if ssid_type != "Enterprise" and not pre_auth_acl_name:
                    self.msg = (
                        "For SSID: '{0}': The SSID type must be 'Enterprise' to activate 'enable_posture' and 'pre_auth_acl_name' is required "
                        "when it is enabled.".format(ssid_name)
                    )
                    self.fail_and_exit(self.msg)

        self.log(
            "All L3 Security and AAA parameters are valid for SSID: '{0}'.".format(
                ssid_name
            ),
            "DEBUG",
        )

    def validate_mfp_client_protection_params(
        self, ssid_name, mfp_client_protection, radio_bands
    ):
        """
        Validates the MFP (Management Frame Protection) client protection parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            mfp_client_protection (str): The MFP client protection setting to be validated.
            radio_bands (list): A list of radio bands associated with the SSID.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Validate mfp_client_protection value against allowed options
        mfp_client_protection = mfp_client_protection.upper()
        if mfp_client_protection not in ["OPTIONAL", "DISABLED", "REQUIRED"]:
            self.msg = "For SSID: '{0}', invalid 'mfp_client_protection' provided. Valid values are 'OPTIONAL', 'DISABLED', and 'REQUIRED'.".format(
                ssid_name
            )
            self.fail_and_exit(self.msg)

        # Validate mfp_client_protection for 6 GHz radio bands
        if radio_bands and radio_bands == [6] and mfp_client_protection != "OPTIONAL":
            self.msg = (
                "For SSID: '{0}', 'mfp_client_protection' must be 'OPTIONAL' for 6GHz radio bands. "
                "Current value is '{1}'.".format(ssid_name, mfp_client_protection)
            )
            self.fail_and_exit(self.msg)

        self.log(
            "MFP client protection is valid for SSID: '{0}'.".format(ssid_name), "DEBUG"
        )

    def validate_protected_management_frame_params(
        self, ssid_name, protected_management_frame
    ):
        """
        Validates the Protected Management Frame (PMF) parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            protected_management_frame (str): The PMF setting to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Validate if the value is one of the valid options
        if protected_management_frame.upper() not in [
            "OPTIONAL",
            "DISABLED",
            "REQUIRED",
        ]:
            self.msg = (
                "For SSID: '{0}', invalid 'protected_management_frame': '{1}'. "
                "Allowed values are 'OPTIONAL', 'DISABLED', or 'REQUIRED'.".format(
                    ssid_name, protected_management_frame
                )
            )
            self.fail_and_exit(self.msg)

        self.log(
            "Protected management frame settings are valid for SSID: '{0}'.".format(
                ssid_name
            ),
            "DEBUG",
        )

    def validate_wlan_timeouts_params(self, ssid_name, wlan_timeouts):
        """
        Validates the WLAN timeouts parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            wlan_timeouts (dict): A dictionary containing WLAN timeout settings.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Extract relevant information from wlan_timeouts
        enable_session_timeout = wlan_timeouts.get("enable_session_timeout", True)
        session_timeout = wlan_timeouts.get("session_timeout")
        enable_client_exclusion_timeout = wlan_timeouts.get(
            "enable_client_exclusion_timeout", True
        )
        client_exclusion_timeout = wlan_timeouts.get("client_exclusion_timeout")

        # Validate session_timeout if session timeout is enabled
        if enable_session_timeout:
            # Ensure session_timeout is within the valid range
            if session_timeout and not (1 <= session_timeout <= 86400):
                self.msg = (
                    "For SSID: '{0}', 'session_timeout' must be between 1 and 86400 seconds. "
                    "Current value is '{1}'.".format(ssid_name, session_timeout)
                )
                self.fail_and_exit(self.msg)
        else:
            # Ensure session_timeout is not provided when session timeout is disabled
            if session_timeout is not None:
                self.msg = "For SSID: '{0}', 'session_timeout' should not be provided when 'enable_session_timeout' is False.".format(
                    ssid_name
                )
                self.fail_and_exit(self.msg)

        # Validate client_exclusion_timeout if client exclusion is enabled
        if enable_client_exclusion_timeout:
            # Ensure client_exclusion_timeout is within the valid range
            if client_exclusion_timeout and not (
                0 <= client_exclusion_timeout <= 2147483647
            ):
                self.msg = (
                    "For SSID: '{0}', 'client_exclusion_timeout' must be between 0 and 2147483647 seconds. "
                    "Current value is '{1}'.".format(
                        ssid_name, client_exclusion_timeout
                    )
                )
                self.fail_and_exit(self.msg)
        else:
            # Ensure client_exclusion_timeout is not provided when client exclusion is disabled
            if client_exclusion_timeout is not None:
                self.msg = "For SSID: '{0}', 'client_exclusion_timeout' should not be provided when 'enable_client_exclusion_timeout' is False.".format(
                    ssid_name
                )
                self.fail_and_exit(self.msg)

        self.log("WLAN timeouts are valid for SSID: '{0}'.".format(ssid_name), "DEBUG")

    def validate_bss_transition_support_params(self, ssid_name, bss_transition_support):
        """
        Validates the 11v BSS Transition Support parameters for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            bss_transition_support (dict): The BSS Transition Support parameters to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Extract necessary information from bss_transition_support
        bss_max_idle_service = bss_transition_support.get("bss_max_idle_service", True)
        bss_idle_client_timeout = bss_transition_support.get("bss_idle_client_timeout")

        # Validate bss_idle_client_timeout and bss_max_idle_service
        if bss_idle_client_timeout:
            # Ensure that bss_max_idle_service is enabled if bss_idle_client_timeout is set
            if not bss_max_idle_service:
                self.msg = (
                    "For SSID: '{0}', 'bss_idle_client_timeout' is provided but 'bss_max_idle_service' is not enabled. "
                    "'bss_max_idle_service' must be True to set 'bss_idle_client_timeout'.".format(
                        ssid_name
                    )
                )
                self.fail_and_exit(self.msg)

            # Check if bss_idle_client_timeout is within the valid range
            if not (15 <= bss_idle_client_timeout <= 100000):
                self.msg = (
                    "For SSID: '{0}', 'bss_idle_client_timeout' must be between 15 and 100000 seconds. "
                    "Current value is '{1}'.".format(ssid_name, bss_idle_client_timeout)
                )
                self.fail_and_exit(self.msg)

        self.log(
            "11v BSS Transition Support parameters are valid for SSID: '{0}'.".format(
                ssid_name
            ),
            "DEBUG",
        )

    def validate_nas_id_param(self, ssid_name, nas_id):
        """
        Validates the NAS ID parameter for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            nas_id (list): The NAS ID list to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Check the length of nas_id
        if len(nas_id) > 4:
            self.msg = (
                "For SSID: '{0}', 'nas_id' can have a maximum of 4 values. "
                "Current count is '{1}'.".format(ssid_name, len(nas_id))
            )
            self.fail_and_exit(self.msg)

        self.log("NAS ID is valid for SSID: '{0}'.".format(ssid_name), "DEBUG")

    def validate_client_rate_limit_param(self, ssid_name, client_rate_limit):
        """
        Validates the client rate limit parameter for an SSID.
        Args:
            ssid_name (str): The name of the SSID.
            client_rate_limit (int): The client rate limit to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        # Define valid range for client_rate_limit
        min_rate = 8000
        max_rate = 100000000000

        # Validate client_rate_limit if provided
        if client_rate_limit:
            # Check if the client_rate_limit is within the valid range
            if not (min_rate <= client_rate_limit <= max_rate):
                self.msg = (
                    "For SSID: '{0}', 'client_rate_limit' must be between {1} and {2}. "
                    "Current value is '{3}'.".format(
                        ssid_name, min_rate, max_rate, client_rate_limit
                    )
                )
                self.fail_and_exit(self.msg)

            # Check if the client_rate_limit is a multiple of 500
            if client_rate_limit % 500 != 0:
                self.msg = (
                    "For SSID: '{0}', 'client_rate_limit' must be a multiple of 500. "
                    "Current value is '{1}'.".format(ssid_name, client_rate_limit)
                )
                self.fail_and_exit(self.msg)

        self.log(
            "Client rate limit is valid for SSID: '{0}'.".format(ssid_name), "DEBUG"
        )

    def validate_sites_specific_override_settings_params(
        self,
        ssid_name,
        ssid_type,
        sites_specific_override_settings,
        global_l3_security,
        global_l2_security,
    ):
        """
        Validates the site-specific override settings for SSIDs.
        Args:
            ssid_name (str): The name of the SSID.
            ssid_type (str): The type of the SSID.
            sites_specific_override_settings (list): A list of dictionaries containing site-specific override settings.
            global_l3_security (dict): The global Layer 3 security settings.
            global_l2_security (dict): The global Layer 2 security settings.
        Raises:
            Exception: If any validation fails, an exception is raised with a descriptive message.
        """
        # Define allowed parameters for site-specific overrides
        allowed_parameters = [
            "site_name_hierarchy",
            "wlan_profile_name",
            "l2_security",
            "fast_transition",
            "fast_transition_over_the_ds",
            "wpa_encryption",
            "auth_key_management",
            "aaa",
            "protected_management_frame",
            "nas_id",
            "client_rate_limit",
        ]

        allowed_l2_security_parameters = [
            "l2_auth_type",
            "open_ssid",
            "passphrase",
            "mpsk_settings",
        ]

        allowed_aaa_parameters = [
            "auth_servers_ip_address_list",
            "accounting_servers_ip_address_list",
            "aaa_override",
            "mac_filtering",
        ]

        # Iterate over each site-specific override
        for idx, site_override in enumerate(sites_specific_override_settings):
            self.log(
                "Validating site-specific override {0} for SSID: '{1}'".format(
                    idx + 1, ssid_name
                ),
                "DEBUG",
            )

            site_name_hierarchy = site_override.get("site_name_hierarchy")
            if not site_name_hierarchy:
                self.msg = "For SSID: '{0}', Entry {1}: 'site_name_hierarchy' is required.".format(
                    ssid_name, idx + 1
                )
                self.fail_and_exit(self.msg)

            if site_name_hierarchy == "Global":
                self.msg = (
                    "For SSID: '{0}', Entry {1}: 'site_name_hierarchy' is set to 'Global', which is invalid. "
                    "Site-specific overrides require a site name other than 'Global'."
                ).format(ssid_name, idx + 1)
                self.fail_and_exit(self.msg)

            # Validate parameters in the site override
            for key, value in site_override.items():
                self.log(
                    "Validating parameter '{0}' for site-specific override {1} in SSID: '{2}'".format(
                        key, idx + 1, ssid_name
                    ),
                    "DEBUG",
                )

                # Handle nested l2_security validation
                if key == "l2_security" and isinstance(value, dict):
                    for l2_key, l2_value in value.items():
                        if l2_key not in allowed_l2_security_parameters:
                            self.msg = (
                                "For SSID: '{0}', Entry {1}, Site '{2}': 'l2_security.{3}' is not an override eligible parameter."
                            ).format(ssid_name, idx + 1, site_name_hierarchy, l2_key)
                            self.fail_and_exit(self.msg)

                        if l2_key == "mpsk_settings" and isinstance(l2_value, list):
                            for mpsk_idx, mpsk_setting in enumerate(l2_value):
                                if isinstance(mpsk_setting, dict):
                                    for mpsk_key in mpsk_setting:
                                        if mpsk_key not in [
                                            "mpsk_priority",
                                            "mpsk_passphrase_type",
                                            "mpsk_passphrase",
                                        ]:
                                            self.msg = (
                                                "For SSID: '{0}', Entry {1}, Site '{2}': MPSK setting {3}, '{4}' is not an allowed parameter."
                                            ).format(
                                                ssid_name,
                                                idx + 1,
                                                site_name_hierarchy,
                                                mpsk_idx + 1,
                                                mpsk_key,
                                            )
                                            self.fail_and_exit(self.msg)

                # Handle nested aaa validation
                if key == "aaa" and isinstance(value, dict):
                    for aaa_key in value:
                        if aaa_key not in allowed_aaa_parameters:
                            self.msg = (
                                "For SSID: '{0}', Entry {1}, Site '{2}': 'aaa.{3}' is not an override eligible parameter."
                            ).format(ssid_name, idx + 1, site_name_hierarchy, aaa_key)
                            self.fail_and_exit(self.msg)

                if key not in allowed_parameters:
                    self.msg = (
                        "For SSID: '{0}', Entry {1}, Site '{2}': '{3}' is not an override eligible parameter."
                    ).format(ssid_name, idx + 1, site_name_hierarchy, key)
                    self.fail_and_exit(self.msg)

        self.log(
            "Validation of site-specific override settings for SSID: {0} completed successfully.".format(
                ssid_name
            ),
            "DEBUG",
        )

    def validate_ssids_params(self, ssids, state):
        """
        Validates the parameters for SSIDs based on the specified state.
        Args:
            ssids (list): A list of dictionaries containing SSID parameters.
            state (str): The state of the operation, either "merged" or "deleted".
        """
        # Handle 'deleted' state separately
        if state == "deleted":
            if ssids:
                self.log("Validating SSID(s) parameters in 'deleted' state.", "DEBUG")
                for ssid in ssids:
                    ssid_name = ssid.get("ssid_name")
                    self.log(
                        "Starting validation of required parameters for SSID: {0}".format(
                            ssid_name
                        ),
                        "DEBUG",
                    )
                    self.validate_required_ssid_params(ssid, state="deleted")
                    self.log(
                        "Completed validation of required parameters for SSID: {0}".format(
                            ssid_name
                        ),
                        "DEBUG",
                    )
                # Exit after handling the 'deleted' state
                return

        # Iterate through each SSID for validation
        for ssid in ssids:
            self.log(
                "Starting validation of parameters for SSID: '{0}' .".format(ssid),
                "DEBUG",
            )
            ssid_name = ssid.get("ssid_name")
            ssid_type = ssid.get("ssid_type")

            # Validate required parameters for the SSID
            self.log(
                "Starting validation of required parameters for SSID: {0}".format(
                    ssid_name
                ),
                "DEBUG",
            )
            self.validate_required_ssid_params(ssid)
            self.log(
                "Completed validation of required parameters for SSID: {0}".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate SSID type parameters
            l2_security = ssid.get("l2_security")
            l3_security = ssid.get("l3_security")
            self.log(
                "Starting validation of SSID type parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            self.validate_ssid_type_params(ssid_type, l2_security, l3_security)
            self.log(
                "Completed validation of SSID type parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate radio policy parameters
            self.log(
                "Starting validation of radio policy parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            radio_policy = ssid.get("radio_policy")
            self.log(
                "'radio_policy' for SSID: {0} - {1}".format(ssid_name, radio_policy),
                "DEBUG",
            )
            if radio_policy:
                self.validate_ssid_radio_policy_params(ssid_name, radio_policy)
            else:
                self.log(
                    "Radio policy parameters not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of radio policy parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate quality of service parameters
            self.log(
                "Starting validation of quality of service parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            quality_of_service = ssid.get("quality_of_service")
            self.log(
                "'quality_of_service' for SSID: {0} - {1}".format(
                    ssid_name, quality_of_service
                ),
                "DEBUG",
            )
            if quality_of_service:
                fast_lane_enabled = ssid.get("fast_lane", False)
                self.log(
                    "'fast_lane_enabled' for SSID: {0} - {1}".format(
                        ssid_name, quality_of_service
                    ),
                    "DEBUG",
                )
                self.validate_qos_params(
                    ssid_name, quality_of_service, fast_lane_enabled
                )
            else:
                self.log(
                    "Quality of Service parameters not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of quality of service parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate L2 security and related parameters
            self.log(
                "Starting validation of L2 security, fast transition, fast transition over the DS, WPA encryption and AKM parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            if l2_security:
                fast_transition = ssid.get("fast_transition", "DISABLE")
                self.log(
                    "'fast_transition' for SSID: {0} - {1}".format(
                        ssid_name, fast_transition
                    ),
                    "DEBUG",
                )
                fast_transition_over_the_ds = ssid.get("fast_transition_over_the_ds")
                self.log(
                    "'fast_transition_over_the_ds' for SSID: {0} - {1}".format(
                        ssid_name, fast_transition_over_the_ds
                    ),
                    "DEBUG",
                )
                wpa_encryption = ssid.get("wpa_encryption")
                self.log(
                    "'wpa_encryption' for SSID: {0} - {1}".format(
                        ssid_name, wpa_encryption
                    ),
                    "DEBUG",
                )
                auth_key_management = ssid.get("auth_key_management")
                self.log(
                    "'auth_key_management' for SSID: {0} - {1}".format(
                        ssid_name, auth_key_management
                    ),
                    "DEBUG",
                )
                cckm_timestamp_tolerance = ssid.get("cckm_timestamp_tolerance")
                self.log(
                    "'cckm_timestamp_tolerance' for SSID: {0} - {1}".format(
                        ssid_name, cckm_timestamp_tolerance
                    ),
                    "DEBUG",
                )
                self.validate_l2_security_params(
                    ssid_name,
                    l2_security,
                    fast_transition,
                    fast_transition_over_the_ds,
                    wpa_encryption,
                    auth_key_management,
                    cckm_timestamp_tolerance,
                )
            else:
                self.log(
                    "Global L2 security configuration parameters not provided for SSID: '{0}'.".format(
                        ssid_name
                    )
                )
            self.log(
                "Completed validation of L2 security and related parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate L3 security and AAA configuration parameters
            self.log(
                "Starting validation of L3 security and AAA parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            aaa = ssid.get("aaa")
            self.validate_l3_security_aaa_params(ssid_name, ssid_type, l3_security, aaa)
            self.log(
                "Completed validation of L3 security and AAA parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate MFP Client Protection parameters
            self.log(
                "Starting validation of MFP client protection parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            mfp_client_protection = ssid.get("mfp_client_protection")
            if mfp_client_protection:
                radio_bands = radio_policy.get("radio_bands") if radio_policy else []
                self.validate_mfp_client_protection_params(
                    ssid_name, mfp_client_protection, radio_bands
                )
            else:
                self.log(
                    "MFP Client Protection not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of MFP client protection for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate Protected Management Frame (802.11w) parameters
            self.log(
                "Starting validation of Protected Management Frame parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            protected_management_frame = ssid.get("protected_management_frame")
            if protected_management_frame:
                self.validate_protected_management_frame_params(
                    ssid_name, protected_management_frame
                )
            else:
                self.log(
                    "Protected Management Frame params not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of Protected Management Frame parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate WLAN timeouts parameters
            self.log(
                "Starting validation of WLAN timeouts for SSID: {0}.".format(ssid_name),
                "DEBUG",
            )
            wlan_timeouts = ssid.get("wlan_timeouts", {})
            if wlan_timeouts:
                self.validate_wlan_timeouts_params(ssid_name, wlan_timeouts)
            else:
                self.log(
                    "WLAN timeouts params not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of WLAN timeouts for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate 11v BSS Transition Support parameters
            self.log(
                "Starting validation of 11v BSS Transition Support parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            bss_transition_support = ssid.get("11v_bss_transition_support", {})
            if bss_transition_support:
                self.validate_11v_bss_transition_support_params(
                    self, ssid_name, bss_transition_support
                )
            else:
                self.log(
                    "11v BSS Transition Support parameters not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of 11v BSS Transition Support parameters for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate NAS ID
            self.log(
                "Starting validation of NAS ID parameter for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            nas_id = ssid.get("nas_id", [])
            if nas_id:
                self.validate_nas_id_param(ssid_name, nas_id)
            else:
                self.log(
                    "NAS ID parameters not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of NAS ID parameter for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate Client Rate Limit
            self.log(
                "Starting validation of Client Rate Limit for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            client_rate_limit = ssid.get("client_rate_limit")
            if client_rate_limit:
                self.validate_client_rate_limit_param(ssid_name, client_rate_limit)
            self.log(
                "Completed validation of Client Rate Limit for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

            # Validate site-specific override settings parameters
            self.log(
                "Starting validation of site-specific override settings for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )
            sites_specific_override_settings = ssid.get(
                "sites_specific_override_settings"
            )
            if sites_specific_override_settings:
                global_l2_security = l2_security
                global_l3_security = l3_security
                self.validate_sites_specific_override_settings_params(
                    ssid_name,
                    ssid_type,
                    sites_specific_override_settings,
                    global_l3_security,
                    global_l2_security,
                )
            else:
                self.log(
                    "Site-specific override settings parameters not provided hence validation is not required.",
                    "INFO",
                )
            self.log(
                "Completed validation of site-specific override settings for SSID: {0}.".format(
                    ssid_name
                ),
                "DEBUG",
            )

    def validate_interfaces_params(self, interfaces, state):
        """
        Validates the required parameters for interfaces based on the specified state.
        Args:
            interfaces (list): A list of dictionaries, each containing parameters for an interface.
            state (str): The state of the operation, either "merged" or "deleted".
        """
        self.log(
            "Starting validation for interfaces with state: {0}".format(state), "INFO"
        )

        # Determine required parameters based on state
        if state == "merged":
            required_params = ["interface_name", "vlan_id"]
        elif state == "deleted":
            required_params = ["interface_name"]

        # Iterate over each interface dictionary
        for interface in interfaces:
            # Check for missing required parameters
            missing_params = [
                param for param in required_params if param not in interface
            ]
            if missing_params:
                self.msg = (
                    "The following required parameters for interface configuration are missing: {}. "
                    "Provided parameters: {}"
                ).format(", ".join(missing_params), interface)
                self.fail_and_exit(self.msg)

            # Validate interface_name
            interface_name = interface.get("interface_name")
            self.log(
                "Validating 'interface_name' for interface: {0}".format(interface_name),
                "DEBUG",
            )
            if interface_name:
                if not (1 <= len(interface_name) <= 31):
                    self.msg = (
                        "The 'interface_name' length must be between 1 and 31 characters. "
                        "Provided 'interface_name': {} (length: {})"
                    ).format(interface_name, len(interface_name))
                    self.fail_and_exit(self.msg)

            # Validate vlan_id if state is "merged"
            if state == "merged":
                vlan_id = interface.get("vlan_id")
                self.log(
                    "Validating 'vlan_id' for interface: {0}".format(interface_name),
                    "DEBUG",
                )
                if vlan_id is not None:
                    if not (1 <= vlan_id <= 4094):
                        self.msg = (
                            "The 'vlan_id' must be between 1 and 4094. "
                            "Provided 'vlan_id': {}"
                        ).format(vlan_id)
                        self.fail_and_exit(self.msg)

        self.log(
            "Required interface parameters validated successfully for all interfaces.",
            "DEBUG",
        )

    def validate_power_profiles_params(self, power_profiles, state):
        """
        Validates the parameters for power profiles based on the specified state.
        Args:
            power_profiles (list): A list of dictionaries containing power profile parameters.
            state (str): The state of the operation, either "merged" or "deleted".
        Raises:
            Exception: If any validation fails, an exception is raised with a descriptive message.
        """
        # Define required parameters based on state
        if state == "merged":
            required_params = ["power_profile_name", "rules"]
        elif state == "deleted":
            required_params = ["power_profile_name"]
        else:
            self.msg = "Invalid state provided: {}. Allowed states are 'merged' or 'deleted'.".format(
                state
            )
            self.fail_and_exit(self.msg)

        # Define valid choices for various parameters
        valid_interface_types = ["ETHERNET", "RADIO", "USB"]
        valid_interface_ids = [
            "GIGABITETHERNET0",
            "GIGABITETHERNET1",
            "LAN1",
            "LAN2",
            "LAN3",
            "6GHZ",
            "5GHZ",
            "SECONDARY_5GHZ",
            "2_4GHZ",
            "USB0",
        ]
        valid_parameter_types = ["SPEED", "SPATIALSTREAM", "STATE"]
        valid_parameter_values = [
            "5000MBPS",
            "2500MBPS",
            "1000MBPS",
            "100MBPS",
            "EIGHT_BY_EIGHT",
            "FOUR_BY_FOUR",
            "THREE_BY_THREE",
            "TWO_BY_TWO",
            "ONE_BY_ONE",
            "DISABLE",
        ]

        # Iterate through each power profile for validation
        for profile in power_profiles:
            self.validate_power_profile_required_params(profile, required_params)
            self.validate_power_profile_name(profile)
            self.validate_power_profile_description(profile)
            self.validate_power_profile_rules(
                profile,
                state,
                valid_interface_types,
                valid_interface_ids,
                valid_parameter_types,
                valid_parameter_values,
            )

        self.log("Power Profile parameters validated successfully.", "INFO")

    def validate_power_profile_required_params(self, profile, required_params):
        """
        Validates if any required parameters are missing in the power profile.
        Args:
            profile (dict): The power profile to be validated.
            required_params (list): A list of required parameters.
        Raises:
            Exception: If any required parameter is missing.
        """
        self.log(
            "Validating missing parameters for power profile: {0}".format(profile),
            "DEBUG",
        )

        missing_params = [param for param in required_params if param not in profile]
        if missing_params:
            self.msg = (
                "The following required parameters for Power Profile are missing: {}. "
                "Provided parameters: {}"
            ).format(", ".join(missing_params), profile)
            self.fail_and_exit(self.msg)

        self.log(
            "All required parameters are present for power profile: {0}".format(
                profile
            ),
            "INFO",
        )

    def validate_power_profile_name(self, profile):
        """
        Validates the name of the power profile.
        Args:
            profile (dict): The power profile to be validated.
        Raises:
            Exception: If the name is invalid.
        """
        self.log(
            "Validating 'power_profile_name' for power profile: {0}".format(profile),
            "DEBUG",
        )

        power_profile_name = profile.get("power_profile_name")
        if power_profile_name and len(power_profile_name) > 128:
            self.msg = (
                "The 'power_profile_name' exceeds the maximum length of 128 characters. "
                "Provided 'power_profile_name': {} (length: {})"
            ).format(power_profile_name, len(power_profile_name))
            self.fail_and_exit(self.msg)

        self.log(
            "'power_profile_name' is valid for power profile: {0}".format(profile),
            "INFO",
        )

    def validate_power_profile_description(self, profile):
        """
        Validates the description of the power profile.
        Args:
            profile (dict): The power profile to be validated.
        Raises:
            Exception: If the description is invalid.
        """
        self.log(
            "Validating 'power_profile_description' for power profile: {0}".format(
                profile
            ),
            "DEBUG",
        )

        power_profile_description = profile.get("power_profile_description")
        if power_profile_description and len(power_profile_description) > 128:
            self.msg = (
                "The 'power_profile_description' exceeds the maximum length of 128 characters. "
                "Provided 'power_profile_description': {} (length: {})"
            ).format(power_profile_description, len(power_profile_description))
            self.fail_and_exit(self.msg)

        self.log(
            "'power_profile_description' is valid for power profile: {0}".format(
                profile
            ),
            "INFO",
        )

    def validate_power_profile_rules(
        self,
        profile,
        state,
        valid_interface_types,
        valid_interface_ids,
        valid_parameter_types,
        valid_parameter_values,
    ):
        """
        Validates the rules of the power profile.
        Args:
            profile (dict): The power profile to be validated.
            state (str): The state of the operation, either "merged" or "deleted".
            valid_interface_types (list): A list of valid interface types.
            valid_interface_ids (list): A list of valid interface IDs.
            valid_parameter_types (list): A list of valid parameter types.
            valid_parameter_values (list): A list of valid parameter values.
        Raises:
            Exception: If any rule validation fails.
        """
        self.log("Validating rules for power profile: {0}".format(profile), "DEBUG")
        rules = profile.get("rules", [])
        if state == "merged" and not rules:
            self.msg = "Rules are required for the 'merged' state but are missing."
            self.fail_and_exit(self.msg)

        for rule in rules:
            if "interface_type" not in rule:
                self.msg = (
                    "'interface_type' is required in each rule. Provided rule: {}"
                ).format(rule)
                self.fail_and_exit(self.msg)

            # Normalize values to uppercase for case-insensitive validation
            interface_type = rule.get("interface_type", "").upper()
            interface_id = rule.get("interface_id", "").upper()
            parameter_type = rule.get("parameter_type", "").upper()
            parameter_value = rule.get("parameter_value", "").upper()

            if interface_type not in valid_interface_types:
                self.msg = ("Invalid 'interface_type': {}. Must be one of {}.").format(
                    interface_type, valid_interface_types
                )
                self.fail_and_exit(self.msg)

            # Additional validation for USB interface
            if interface_type == "USB":
                if 'interface_id' in rule and interface_id != "USB0":
                    self.msg = (
                        "For 'USB' interface_type, if provided, 'interface_id' must be 'USB0'. Provided rule: {}"
                    ).format(rule)
                    self.fail_and_exit(self.msg)
                if 'parameter_type' in rule and parameter_type != "STATE":
                    self.msg = (
                        "For 'USB' interface_type, if provided, 'parameter_type' must be 'STATE'. Provided rule: {}"
                    ).format(rule)
                    self.fail_and_exit(self.msg)
                if 'parameter_value' in rule and parameter_value != "DISABLE":
                    self.msg = (
                        "For 'USB' interface_type, if provided, 'parameter_value' must be 'DISABLE'. Provided rule: {}"
                    ).format(rule)
                    self.fail_and_exit(self.msg)

            # Additional validation for ETHERNET interface
            if interface_type == "ETHERNET":
                if "parameter_type" in rule and parameter_type not in [
                    "SPEED",
                    "STATE",
                ]:
                    self.msg = (
                        "For 'ETHERNET' interface_type, if provided, 'parameter_type' must be 'SPEED'. Provided rule: {}"
                    ).format(rule)
                    self.fail_and_exit(self.msg)

            # Validate interface_id
            if interface_id and interface_id not in valid_interface_ids:
                self.msg = ("Invalid 'interface_id': {}. Must be one of {}.").format(
                    interface_id, valid_interface_ids
                )
                self.fail_and_exit(self.msg)

            # Validate parameter_type
            if parameter_type and parameter_type not in valid_parameter_types:
                self.msg = ("Invalid 'parameter_type': {}. Must be one of {}.").format(
                    parameter_type, valid_parameter_types
                )
                self.fail_and_exit(self.msg)

            # Validate parameter_value
            if parameter_value and parameter_value not in valid_parameter_values:
                self.msg = ("Invalid 'parameter_value': {}. Must be one of {}.").format(
                    parameter_value, valid_parameter_values
                )
                self.fail_and_exit(self.msg)

        self.log("Rules are valid for power profile: {0}".format(profile), "INFO")

    def is_valid_password(self, password):
        """
        Validates whether a password meets security criteria.
        Args:
            password (str): The password to validate.
        Returns:
            bool: True if the password is valid, False otherwise.
        """
        # Define a set of default or weak passwords to check against
        default_passwords = {"Cisco", "Ocsic", "cisco", "ocsic"}

        # Check if the password matches any default or weak passwords
        if password in default_passwords:
            self.log(
                "Password matches a default or weak password: {0}".format(password),
                "DEBUG",
            )
            return False

        # Check if the password contains repeated characters
        if any(
            password[i] == password[i + 1] == password[i + 2]
            for i in range(len(password) - 2)
        ):
            self.log(
                "Password contains repeated characters: {0}".format(password), "DEBUG"
            )
            return False

        # Check if the password contains simple sequences
        if "abc" in password or "123" in password:
            self.log(
                "Password contains simple sequences: {0}".format(password), "DEBUG"
            )
            return False

        # If all checks pass, the password is considered valid
        return True

    def validate_ap_profile_management_settings(
        self, management_settings, access_point_profile_name
    ):
        """
        Validates the management settings of an access point profile.
        Args:
            management_settings (dict): Management settings of the profile.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If any validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Validating management settings for AP Profile: {0}".format(
                access_point_profile_name
            ),
            "INFO",
        )

        # Validate access_point_authentication choice
        valid_auth_choices = ["NO-AUTH", "EAP-TLS", "EAP-PEAP", "EAP-FAST"]
        access_point_authentication = management_settings.get(
            "access_point_authentication"
        )
        self.log(
            "Checking 'access_point_authentication' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, access_point_authentication
            ),
            "DEBUG",
        )

        if (
            access_point_authentication
            and access_point_authentication not in valid_auth_choices
        ):
            self.msg = (
                "For AP Profile: {0}, the 'access_point_authentication' is invalid: {1}. "
                "Valid choices are: {2}."
            ).format(
                access_point_profile_name,
                access_point_authentication,
                ", ".join(valid_auth_choices),
            )
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'access_point_authentication' value for AP Profile: {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

    def validate_ap_profile_security_settings(self, security_settings, access_point_profile_name):
        """
        Validates the security settings of an access point profile.
        Args:
            security_settings (dict): Security settings of the profile.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If any validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Validating security settings for AP Profile: {0}".format(
                access_point_profile_name
            ),
            "INFO",
        )

        # Validate minimum_rssi
        minimum_rssi = security_settings.get("minimum_rssi")
        self.log(
            "Checking 'minimum_rssi' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, minimum_rssi
            ),
            "DEBUG",
        )
        if minimum_rssi is not None:
            # Check if the minimum_rssi value is within the valid range
            if not (-128 <= minimum_rssi <= -70):
                self.msg = (
                    "For AP Profile: {0}, the 'minimum_rssi' value is out of range. Provided value: {1}. "
                    "Valid range is -128 to -70 decibel milliwatts."
                ).format(access_point_profile_name, minimum_rssi)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'minimum_rssi' value for AP Profile: {0} is within the valid range.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

        # Validate transient_interval
        transient_interval = security_settings.get("transient_interval")
        self.log(
            "Checking 'transient_interval' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, transient_interval
            ),
            "DEBUG",
        )
        if transient_interval is not None:
            # Check if the transient_interval value is within the valid range
            if not (transient_interval == 0 or 120 <= transient_interval <= 1800):
                self.msg = (
                    "For AP Profile: {0}, the 'transient_interval' value is out of range. Provided value: {1}. "
                    "Valid values are 0 or between 120 and 1800."
                ).format(access_point_profile_name, transient_interval)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'transient_interval' value for AP Profile: {0} is within the valid range.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

        # Validate report_interval
        report_interval = security_settings.get("report_interval")
        self.log(
            "Checking 'report_interval' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, report_interval
            ),
            "DEBUG",
        )
        if report_interval is not None:
            # Check if the report_interval value is within the valid range
            if not (10 <= report_interval <= 300):
                self.msg = (
                    "For AP Profile: {0}, the 'report_interval' value is out of range. Provided value: {1}. "
                    "Valid range is 10 to 300."
                ).format(access_point_profile_name, report_interval)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'report_interval' value for AP Profile: {0} is within the valid range.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

    def validate_ap_profile_mesh_settings(
        self, mesh_settings, access_point_profile_name
    ):
        """
        Validates the mesh settings of an access point profile.
        Args:
            mesh_settings (dict): Mesh settings of the profile.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If any validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Validating mesh settings for AP Profile: {0}".format(
                access_point_profile_name
            ),
            "INFO",
        )

        # Define valid choices for parameters
        valid_rap_downlink_backhaul_choices = ["5 GHz", "2.4 GHz"]
        valid_radio_band_types_5ghz = [
            "auto",
            "802.11abg",
            "802.12ac",
            "802.11ax",
            "802.11n",
        ]
        valid_radio_band_types_2_4ghz = ["auto", "802.11abg", "802.11ax", "802.11n"]

        # Validate range
        mesh_range = mesh_settings.get("range")
        self.log(
            "Checking 'range' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, mesh_range
            ),
            "DEBUG",
        )
        if mesh_range is not None:
            if not (150 <= mesh_range <= 132000):
                self.msg = (
                    "For Profile: {0}, the 'range' value in mesh settings is out of range. Provided value: {1}. "
                    "Valid range is 150 to 132000."
                ).format(access_point_profile_name, mesh_range)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'range' value for AP Profile: {0} is within the valid range.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

        # Validate rap_downlink_backhaul
        rap_downlink_backhaul = mesh_settings.get("rap_downlink_backhaul")
        self.log(
            "Checking 'rap_downlink_backhaul' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, rap_downlink_backhaul
            ),
            "DEBUG",
        )
        if (
            rap_downlink_backhaul
            and rap_downlink_backhaul not in valid_rap_downlink_backhaul_choices
        ):
            self.msg = (
                "For Profile: {0}, the 'rap_downlink_backhaul' is invalid: {1}. "
                "Valid choices are: {2}."
            ).format(
                access_point_profile_name,
                rap_downlink_backhaul,
                ", ".join(valid_rap_downlink_backhaul_choices),
            )
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'rap_downlink_backhaul' value for AP Profile: {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

        # Validate ghz_5_backhaul_data_rates
        ghz_5_backhaul_data_rates = mesh_settings.get("ghz_5_backhaul_data_rates")
        self.log(
            "Checking 'ghz_5_backhaul_data_rates' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, ghz_5_backhaul_data_rates
            ),
            "DEBUG",
        )
        if (
            ghz_5_backhaul_data_rates
            and ghz_5_backhaul_data_rates not in valid_radio_band_types_5ghz
        ):
            self.msg = (
                "For Profile: {0}, the 'ghz_5_backhaul_data_rates' is invalid: {1}. "
                "Valid choices are: {2}."
            ).format(
                access_point_profile_name,
                ghz_5_backhaul_data_rates,
                ", ".join(valid_radio_band_types_5ghz),
            )
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'ghz_5_backhaul_data_rates' value for AP Profile: {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

        # Validate ghz_2_4_backhaul_data_rates
        ghz_2_4_backhaul_data_rates = mesh_settings.get("ghz_2_4_backhaul_data_rates")
        self.log(
            "Checking 'ghz_2_4_backhaul_data_rates' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, ghz_2_4_backhaul_data_rates
            ),
            "DEBUG",
        )
        if (
            ghz_2_4_backhaul_data_rates
            and ghz_2_4_backhaul_data_rates not in valid_radio_band_types_2_4ghz
        ):
            self.msg = (
                "For Profile: {0}, the 'ghz_2_4_backhaul_data_rates' is invalid: {1}. "
                "Valid choices are: {2}."
            ).format(
                access_point_profile_name,
                ghz_2_4_backhaul_data_rates,
                ", ".join(valid_radio_band_types_2_4ghz),
            )
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'ghz_2_4_backhaul_data_rates' value for AP Profile: {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

        # Validate the bridge_group_name length
        bridge_group_name = mesh_settings.get("bridge_group_name")
        self.log(
            "Checking 'bridge_group_name' for AP Profile: {0} - Provided value: {1}".format(
                access_point_profile_name, bridge_group_name
            ),
            "DEBUG",
        )
        if bridge_group_name is not None:
            if not (0 <= len(bridge_group_name) <= 10):
                self.msg = (
                    "For Profile: {0}, the 'bridge_group_name' length in mesh settings is out of range. Provided value: '{1}' "
                    "with length {2}. Valid length range is 0 to 10 characters."
                ).format(
                    access_point_profile_name, bridge_group_name, len(bridge_group_name)
                )
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'bridge_group_name' for AP Profile: {0} is within the valid length range.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

    def validate_ap_profile_power_settings(
        self, power_settings, access_point_profile_name
    ):
        """
        Validates the power settings of an access point profile.
        Args:
            power_settings (dict): Power settings of the profile.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If any validation fails, an exception is raised with a descriptive message.
        """
        self.log(
            "Validating power settings for AP Profile: {0}".format(
                access_point_profile_name
            ),
            "INFO",
        )

        # Check if power settings are provided
        if not power_settings:
            self.log(
                "No power settings provided for AP Profile: {0}".format(
                    access_point_profile_name
                ),
                "INFO",
            )
            return

        # Check if calendar power profiles are provided
        calendar_power_profiles = power_settings.get("calendar_power_profiles")
        if not calendar_power_profiles:
            self.log(
                "No calendar power profiles provided for AP Profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )
            return

        # Iterate over each calendar power profile
        for profile in calendar_power_profiles:
            self.log(
                "Validating calendar power profile for AP Profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )

            # Check if 'ap_power_profile_name' is provided
            if "ap_power_profile_name" not in profile:
                self.msg = "For AP Profile: {0}, 'ap_power_profile_name' is required in calendar power profiles.".format(
                    access_point_profile_name
                )
                self.fail_and_exit(self.msg)

            # Check if 'scheduler_type' is provided and valid
            scheduler_type = profile.get("scheduler_type")
            valid_scheduler_types = ["DAILY", "WEEKLY", "MONTHLY"]
            if not scheduler_type or scheduler_type not in valid_scheduler_types:
                self.msg = (
                    "For AP Profile: {0}, 'scheduler_type' is invalid or not provided. "
                    "Valid choices are: {1}."
                ).format(access_point_profile_name, ", ".join(valid_scheduler_types))
                self.fail_and_exit(self.msg)

            self.log(
                "The 'scheduler_type' for AP Profile: {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

            # Validate fields based on scheduler_type
            if scheduler_type == "DAILY":
                if not profile.get("scheduler_start_time") or not profile.get(
                    "scheduler_end_time"
                ):
                    self.msg = (
                        "For AP Profile: {0}, 'scheduler_start_time' and 'scheduler_end_time' are required for DAILY scheduler."
                    ).format(access_point_profile_name)
                    self.fail_and_exit(self.msg)

            elif scheduler_type == "WEEKLY":
                if (
                    not profile.get("scheduler_start_time")
                    or not profile.get("scheduler_end_time")
                    or not profile.get("scheduler_days_list")
                ):
                    self.msg = (
                        "For AP Profile: {0}, 'scheduler_start_time', 'scheduler_end_time', and 'scheduler_days' are required for WEEKLY scheduler."
                    ).format(access_point_profile_name)
                    self.fail_and_exit(self.msg)

            elif scheduler_type == "MONTHLY":
                if (
                    not profile.get("scheduler_dates_list")
                    or not profile.get("scheduler_start_time")
                    or not profile.get("scheduler_end_time")
                ):
                    self.msg = (
                        "For AP Profile: {0}, 'scheduler_start_date', 'scheduler_end_date', 'scheduler_start_time', and "
                        "'scheduler_end_time' are required for MONTHLY scheduler."
                    ).format(access_point_profile_name)
                    self.fail_and_exit(self.msg)

            # Validate the format of scheduler_start_time and scheduler_end_time
            time_pattern = re.compile(r"^(1[0-2]|0?[1-9]):([0-5][0-9])\s?(AM|PM)$")
            start_time = profile.get("scheduler_start_time")
            end_time = profile.get("scheduler_end_time")
            if start_time and not time_pattern.match(start_time):
                self.msg = (
                    "For  AP Profile: {0}, 'scheduler_start_time' is not in the correct format. "
                    "Provided value: '{1}'. Expected format: 'hh:mm AM/PM'."
                ).format(access_point_profile_name, start_time)
                self.fail_and_exit(self.msg)

            if end_time and not time_pattern.match(end_time):
                self.msg = (
                    "For AP Profile: {0}, 'scheduler_end_time' is not in the correct format. "
                    "Provided value: '{1}'. Expected format: 'hh:mm AM/PM'."
                ).format(access_point_profile_name, end_time)
                self.fail_and_exit(self.msg)

    def validate_access_point_profiles_params(self, access_point_profiles, state):
        """
        Validates the parameters for access point profiles based on the specified state.
        Args:
            access_point_profiles (list): A list of dictionaries containing access point profile parameters.
            state (str): The state of the operation, either "merged" or "deleted".
        """
        self.log(
            "Starting validation for Access Point Profiles with state: {0}".format(
                state
            ),
            "INFO",
        )

        for profile in access_point_profiles:
            self.log(
                "Validating profile: {0}".format(
                    profile.get("access_point_profile_name", "Unknown")
                ),
                "DEBUG",
            )
            self.validate_ap_profiles_name(profile)
            if state == "merged":
                self.validate_ap_profiles_merged_state(profile)

    def validate_ap_profiles_name(self, profile):
        """
        Validates the name of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        self.log("Validating 'access_point_profile_name' for profile.", "DEBUG")

        # Check if 'access_point_profile_name' is provided
        if "access_point_profile_name" not in profile:
            self.msg = "Required parameter 'access_point_profile_name' not provided for the Access Point Profile: {0}.".format(
                profile
            )
            self.fail_and_exit(self.msg)

        access_point_profile_name = profile["access_point_profile_name"]

        # Check if the length of 'access_point_profile_name' is within the valid range
        if len(access_point_profile_name) > 32:
            self.msg = (
                "The 'access_point_profile_name' exceeds the maximum length of 32 characters. "
                "Provided 'access_point_profile_name': {0} (length: {1})"
            ).format(access_point_profile_name, len(access_point_profile_name))
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'access_point_profile_name' for profile {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

    def validate_ap_profiles_merged_state(self, profile):
        """
        Validates the access point profile in the 'merged' state.
        Args:
            profile (dict): The access point profile to be validated.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        access_point_profile_name = profile["access_point_profile_name"]
        self.log(
            "Performing 'merged' state validation for profile: {0}".format(
                access_point_profile_name
            ),
            "INFO",
        )

        # Validate various aspects of the access point profile
        self.validate_ap_profiles_description(profile, access_point_profile_name)
        self.validate_ap_profiles_management_settings(
            profile, access_point_profile_name
        )
        self.validate_ap_profiles_security_settings(profile, access_point_profile_name)
        self.validate_ap_profiles_mesh_settings(profile, access_point_profile_name)
        self.validate_ap_profiles_power_settings(profile, access_point_profile_name)
        self.validate_ap_profiles_country_code(profile, access_point_profile_name)
        self.validate_ap_profiles_time_zone(profile, access_point_profile_name)
        self.validate_ap_profiles_time_zone_offset_hour(
            profile, access_point_profile_name
        )
        self.validate_ap_profiles_time_zone_offset_minutes(
            profile, access_point_profile_name
        )
        self.validate_ap_profiles_maximum_client_limit(
            profile, access_point_profile_name
        )

    def validate_ap_profiles_description(self, profile, access_point_profile_name):
        """
        Validates the description of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        access_point_profile_description = profile.get(
            "access_point_profile_description"
        )
        if access_point_profile_description:
            self.log(
                "Validating 'access_point_profile_description' for profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )

            # Check if the length of 'access_point_profile_description' is within the valid range
            if len(access_point_profile_description) > 241:
                self.msg = (
                    "For AP Profile: {0} the 'access_point_profile_description' exceeds the maximum length of 241 characters. "
                    "Provided 'access_point_profile_description': {1} (length: {2})"
                ).format(
                    access_point_profile_name,
                    access_point_profile_description,
                    len(access_point_profile_description),
                )
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'access_point_profile_description' for profile {0} is valid.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

    def validate_ap_profiles_management_settings(
        self, profile, access_point_profile_name
    ):
        """
        Validates the management settings of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        management_settings = profile.get("management_settings")
        if management_settings:
            self.log(
                "Validating 'management_settings' for profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )
            self.validate_ap_profile_management_settings(
                management_settings, access_point_profile_name
            )

    def validate_ap_profiles_security_settings(
        self, profile, access_point_profile_name
    ):
        """
        Validates the security settings of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        security_settings = profile.get("security_settings")
        if security_settings:
            self.log(
                "Validating 'security_settings' for profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )
            self.validate_ap_profile_security_settings(
                security_settings, access_point_profile_name
            )

    def validate_ap_profiles_mesh_settings(self, profile, access_point_profile_name):
        """
        Validates the mesh settings of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        mesh_settings = profile.get("mesh_settings")
        if mesh_settings:
            self.log(
                "Validating 'mesh_settings' for profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )
            self.validate_ap_profile_mesh_settings(
                mesh_settings, access_point_profile_name
            )

    def validate_ap_profiles_power_settings(self, profile, access_point_profile_name):
        """
        Validates the power settings of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        power_settings = profile.get("power_settings")
        if power_settings:
            self.log(
                "Validating 'power_settings' for profile: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )
            self.validate_ap_profile_power_settings(
                power_settings, access_point_profile_name
            )

    def validate_ap_profiles_country_code(self, profile, access_point_profile_name):
        """
        Validates the country code of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        country_code = profile.get("country_code")
        valid_country_codes = [
            "Afghanistan",
            "Albania",
            "Algeria",
            "Angola",
            "Argentina",
            "Australia",
            "Austria",
            "Bahamas",
            "Bahrain",
            "Bangladesh",
            "Barbados",
            "Belarus",
            "Belgium",
            "Bhutan",
            "Bolivia",
            "Bosnia",
            "Botswana",
            "Brazil",
            "Brunei",
            "Bulgaria",
            "Burundi",
            "Cambodia",
            "Cameroon",
            "Canada",
            "Chile",
            "China",
            "Colombia",
            "Costa Rica",
            "Croatia",
            "Cuba",
            "Cyprus",
            "Czech Republic",
            "Democratic Republic of the Congo",
            "Denmark",
            "Dominican Republic",
            "Ecuador",
            "Egypt",
            "El Salvador",
            "Estonia",
            "Ethiopia",
            "Fiji",
            "Finland",
            "France",
            "Gabon",
            "Georgia",
            "Germany",
            "Ghana",
            "Gibraltar",
            "Greece",
            "Guatemala",
            "Honduras",
            "Hong Kong",
            "Hungary",
            "Iceland",
            "India",
            "Indonesia",
            "Iraq",
            "Ireland",
            "Isle of Man",
            "Israel",
            "Israel (Outdoor)",
            "Italy",
            "Ivory Coast (Cote dIvoire)",
            "Jamaica",
            "Japan 2(P)",
            "Japan 4(Q)",
            "Jersey",
            "Jordan",
            "Kazakhstan",
            "Kenya",
            "Korea Extended (CK)",
            "Kosovo",
            "Kuwait",
            "Laos",
            "Latvia",
            "Lebanon",
            "Libya",
            "Liechtenstein",
            "Lithuania",
            "Luxembourg",
            "Macao",
            "Macedonia",
            "Malaysia",
            "Malta",
            "Mauritius",
            "Mexico",
            "Moldova",
            "Monaco",
            "Mongolia",
            "Montenegro",
            "Morocco",
            "Myanmar",
            "Namibia",
            "Nepal",
            "Netherlands",
            "New Zealand",
            "Nicaragua",
            "Nigeria",
            "Norway",
            "Oman",
            "Pakistan",
            "Panama",
            "Paraguay",
            "Peru",
            "Philippines",
            "Poland",
            "Portugal",
            "Puerto Rico",
            "Qatar",
            "Romania",
            "Russian Federation",
            "San Marino",
            "Saudi Arabia",
            "Serbia",
            "Singapore",
            "Slovak Republic",
            "Slovenia",
            "South Africa",
            "Spain",
            "Sri Lanka",
            "Sudan",
            "Sweden",
            "Switzerland",
            "Taiwan",
            "Thailand",
            "Trinidad",
            "Tunisia",
            "Turkey",
            "Uganda",
            "Ukraine",
            "United Arab Emirates",
            "United Kingdom",
            "United Republic of Tanzania",
            "United States",
            "Uruguay",
            "Uzbekistan",
            "Vatican City State",
            "Venezuela",
            "Vietnam",
            "Yemen",
            "Zambia",
            "Zimbabwe",
        ]

        self.log(
            "Validating 'country_code' for profile: {0} - Provided value: {1}".format(
                access_point_profile_name, country_code
            ),
            "DEBUG",
        )

        # Check if the provided 'country_code' is valid
        if country_code and country_code not in valid_country_codes:
            self.msg = (
                "For Profile: {0}, the 'country_code' is invalid: {1}. "
                "Valid choices are: {2}."
            ).format(
                access_point_profile_name, country_code, ", ".join(valid_country_codes)
            )
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'country_code' for profile {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

    def validate_ap_profiles_time_zone(self, profile, access_point_profile_name):
        """
        Validates the time zone of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        time_zone = profile.get("time_zone")
        valid_time_zones = ["NOT CONFIGURED", "CONTROLLER", "DELTA FROM CONTROLLER"]
        self.log(
            "Validating 'time_zone' for profile: {0} - Provided value: {1}".format(
                access_point_profile_name, time_zone
            ),
            "DEBUG",
        )

        # Check if the provided 'time_zone' is valid
        if time_zone and time_zone not in valid_time_zones:
            self.msg = (
                "For Profile: {0}, the 'time_zone' is invalid: {1}. "
                "Valid choices are: {2}."
            ).format(access_point_profile_name, time_zone, ", ".join(valid_time_zones))
            self.fail_and_exit(self.msg)
        else:
            self.log(
                "The 'time_zone' for profile {0} is valid.".format(
                    access_point_profile_name
                ),
                "INFO",
            )

    def validate_ap_profiles_time_zone_offset_hour(
        self, profile, access_point_profile_name
    ):
        """
        Validates the time zone offset hour of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        time_zone_offset_hour = profile.get("time_zone_offset_hour")
        self.log(
            "Validating 'time_zone_offset_hour' for profile: {0} - Provided value: {1}".format(
                access_point_profile_name, time_zone_offset_hour
            ),
            "DEBUG",
        )

        # Check if the provided 'time_zone_offset_hour' is within the valid range
        if time_zone_offset_hour is not None:
            if not (-12 <= time_zone_offset_hour <= 14):
                self.msg = (
                    "For Profile: {0}, the 'time_zone_offset_hour' is out of range. "
                    "Provided value: {1}. Valid range is -12 to 14."
                ).format(access_point_profile_name, time_zone_offset_hour)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'time_zone_offset_hour' for profile {0} is valid.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

    def validate_ap_profiles_time_zone_offset_minutes(
        self, profile, access_point_profile_name
    ):
        """
        Validates the time zone offset minutes of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        time_zone_offset_minutes = profile.get("time_zone_offset_minutes")
        self.log(
            "Validating 'time_zone_offset_minutes' for profile: {0} - Provided value: {1}".format(
                access_point_profile_name, time_zone_offset_minutes
            ),
            "DEBUG",
        )

        # Check if the provided 'time_zone_offset_minutes' is within the valid range
        if time_zone_offset_minutes is not None:
            if not (0 <= time_zone_offset_minutes < 60):
                self.msg = (
                    "For Profile: {0}, the 'time_zone_offset_minutes' is out of range. "
                    "Provided value: {1}. Valid range is 0 to 59."
                ).format(access_point_profile_name, time_zone_offset_minutes)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'time_zone_offset_minutes' for profile {0} is valid.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

    def validate_ap_profiles_maximum_client_limit(
        self, profile, access_point_profile_name
    ):
        """
        Validates the maximum client limit of the access point profile.
        Args:
            profile (dict): The access point profile to be validated.
            access_point_profile_name (str): The name of the access point profile.
        Raises:
            Exception: If the validation fails, an exception is raised with a descriptive message.
        """
        maximum_client_limit = profile.get("maximum_client_limit")
        self.log(
            "Validating 'maximum_client_limit' for profile: {0} - Provided value: {1}".format(
                access_point_profile_name, maximum_client_limit
            ),
            "DEBUG",
        )

        # Check if the provided 'maximum_client_limit' is within the valid range
        if maximum_client_limit is not None:
            if not (0 <= maximum_client_limit <= 1200):
                self.msg = (
                    "For Profile: {0}, the 'maximum_client_limit' is out of range. "
                    "Provided value: {1}. Valid range is 0 to 1200."
                ).format(access_point_profile_name, maximum_client_limit)
                self.fail_and_exit(self.msg)
            else:
                self.log(
                    "The 'maximum_client_limit' for profile {0} is valid.".format(
                        access_point_profile_name
                    ),
                    "INFO",
                )

    def validate_list_values(self, values, allowed_values, param_name, profile_name):
        """
        Validate that all values in a given list exist in the set of allowed values.
        Args:
            values (list): The list of values to validate.
            allowed_values (set): The set of allowed values.
            param_name (str): The parameter name being validated.
            profile_name (str): The profile name associated with the validation.
        Raises:
            Calls self.fail_and_exit with an error message if validation fails.
        """
        self.log(
            "Validating {0} in profile {1} against allowed values: {2}".format(
                param_name, profile_name, allowed_values
            ),
            "DEBUG",
        )

        # Check if all values are within the set of allowed values
        if not set(values).issubset(allowed_values):
            self.msg = (
                "Invalid values in {0} for profile {1}. Allowed values: {2}".format(
                    param_name, profile_name, allowed_values
                )
            )
            self.fail_and_exit(self.msg)

        self.log(
            "Validation successful for {0} in profile {1}".format(
                param_name, profile_name
            ),
            "INFO",
        )

    def validate_range(self, value, min_val, max_val, param_name, profile_name):
        """
        Validate that a given value falls within the specified range.
        Args:
            value (int/float): The value to validate.
            min_val (int/float): The minimum acceptable value.
            max_val (int/float): The maximum acceptable value.
            param_name (str): The parameter name being validated.
            profile_name (str): The profile name associated with the validation.
        """
        self.log(
            "Validating {0} in profile {1}. Expected range: {2} to {3}".format(
                param_name, profile_name, min_val, max_val
            ),
            "DEBUG",
        )

        # Ensure the correct interpretation of the range
        if min_val > max_val:
            min_val, max_val = max_val, min_val

        # Check if the value is within the specified range
        if not (min_val <= value <= max_val):
            self.msg = "{0} in profile {1} must be between {2} and {3}".format(
                param_name, profile_name, min_val, max_val
            )
            self.fail_and_exit(self.msg)

        self.log(
            "Validation successful for {0} in profile {1}".format(
                param_name, profile_name
            ),
            "INFO",
        )

    def validate_rf_profile_mandatory_data_rates(
        self, mandatory_list, supported_list, param_name, profile_name
    ):
        """
        Validate that mandatory data rates are a subset of supported data rates
        and do not exceed the maximum allowed length.
        Args:
            mandatory_list (list): The list of mandatory data rates.
            supported_list (list): The list of supported data rates.
            param_name (str): The parameter name being validated.
            profile_name (str): The profile name associated with the validation.
        Raises:
            Calls self.fail_and_exit with an error message if validation fails.
        """
        self.log(
            "Validating {0} in profile {1}. Checking subset and max length constraints.".format(
                param_name, profile_name
            ),
            "DEBUG",
        )

        # Check if the number of mandatory data rates exceeds the allowed limit
        if len(mandatory_list) > 2:
            self.msg = "{0} in profile {1} should not exceed 2 values. Current count: {2}".format(
                param_name, profile_name, len(mandatory_list)
            )
            self.fail_and_exit(self.msg)

        # Check if all mandatory data rates are a subset of the supported data rates
        if not set(mandatory_list).issubset(supported_list):
            self.msg = "Values in {0} must be a subset of supported data rates in profile {1}".format(
                param_name, profile_name
            )
            self.fail_and_exit(self.msg)

        self.log(
            "Validation successful for {0} in profile {1}".format(
                param_name, profile_name
            ),
            "INFO",
        )

    def validate_single_default_rf_profile(self, radio_frequency_profiles):
        """
        Validates that there is only one RF profile set as default across the entire Catalyst Center.
        Args:
            radio_frequency_profiles (list): A list of dictionaries containing RF profile parameters.
        Raises:
            Exception: If more than one RF profile is set as default.
        """
        self.log("Validating that only one RF profile is set as default across RF profiles in the config.", "INFO")

        # Variable to track the default RF profile
        default_profile = None

        # Iterate over each RF profile
        for profile in radio_frequency_profiles:
            profile_name = profile.get("radio_frequency_profile_name", "Unknown")
            is_default = profile.get("default_rf_profile", False)
            self.log(
                "Processing RF Profile: Name='{0}', Default='{1}'.".format(profile_name, is_default),
                "DEBUG",
            )

            # If the profile is set as default, check for conflicts
            if is_default:
                if default_profile:
                    self.msg = (
                        "Validation failed: Multiple RF profiles are set as default. "
                        "Conflicting profiles: '{0}' and '{1}'.".format(default_profile, profile_name)
                    )
                    self.fail_and_exit(self.msg)
                default_profile = profile_name
                # Set a global parameter for default RF profile
                self.is_default_rf_profile_in_config = True

        if default_profile:
            self.log("Validation successful: '{0}' is the only default RF profile.".format(default_profile), "INFO")
        else:
            self.log("No default RF profile is set.", "INFO")

    def validate_radio_frequency_profiles_params(self, radio_frequency_profiles, state):
        """
        Validate the parameters for radio frequency profiles based on the specified state.
        Args:
            radio_frequency_profiles (list): A list of dictionaries containing radio frequency profile parameters.
            state (str): The state of the operation, either "merged" or "deleted".
        Raises:
            ValueError: If any validation fails.
        """
        self.log(
            "Starting validation for Radio Frequency Profiles with state: {0}".format(
                state
            ),
            "INFO",
        )
        # Define validation rules for different radio frequency profile parameters
        VALIDATION_RULES = {
            "common": {
                "parent_profile": ["HIGH", "TYPICAL", "LOW", "CUSTOM"],
                "rx_sop_threshold": ["HIGH", "MEDIUM", "LOW", "AUTO", "CUSTOM"],
                "custom_rx_sop_threshold": (-85, -60),
                "tpc_power_threshold": (-80, -50),
                "minimum_power_level": (-10, 30),
                "maximum_power_level": (-10, 30),
                "client_limit": (0, 500),
                "coverage_hole_detection": {
                    "minimum_client_level": (1, 200),
                    "data_rssi_threshold": (-90, -60),
                    "voice_rssi_threshold": (-90, -60),
                    "exception_level": (0, 100),
                },
                "spatial_reuse": {
                    "non_srg_obss_pd_max_threshold": (-82, -62),
                    "srg_obss_pd_min_threshold": (-82, -62),
                    "srg_obss_pd_max_threshold": (-82, -62),
                },
            },
            "radio_bands_2_4ghz_settings": {
                "dca_channels_list": {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14},
                "supported_data_rates_list": {
                    1,
                    2,
                    5.5,
                    6,
                    9,
                    11,
                    12,
                    18,
                    24,
                    36,
                    48,
                    54,
                },
                "mandatory_data_rates_list": {
                    "max_length": 2,
                    "subset_of": "supported_data_rates_list",
                },
            },
            "radio_bands_5ghz_settings": {
                "channel_width": ["20", "40", "80", "160", "best"],
                "dca_channels_list": {
                    36,
                    40,
                    44,
                    48,
                    52,
                    56,
                    60,
                    64,
                    100,
                    104,
                    108,
                    112,
                    116,
                    120,
                    124,
                    128,
                    132,
                    136,
                    140,
                    144,
                    149,
                    153,
                    157,
                    161,
                    165,
                    169,
                    173,
                    177,
                },
                "supported_data_rates_list": {6, 9, 12, 18, 24, 36, 48, 54},
                "mandatory_data_rates_list": {
                    "max_length": 2,
                    "subset_of": "supported_data_rates_list",
                },
                "flexible_radio_assignment": {
                    "client_select": (0, 100),
                    "client_reset": (0, 100),
                },
            },
            "radio_bands_6ghz_settings": {
                "parent_profile": ["CUSTOM"],
                "minimum_dbs_channel_width": {20, 40, 80, 160, 320},
                "maximum_dbs_channel_width": {20, 40, 80, 160, 320},
                "dca_channels_list": {
                    1,
                    5,
                    9,
                    13,
                    17,
                    21,
                    25,
                    29,
                    33,
                    37,
                    41,
                    45,
                    49,
                    53,
                    57,
                    61,
                    65,
                    69,
                    73,
                    77,
                    81,
                    85,
                    89,
                    93,
                    97,
                    101,
                    105,
                    109,
                    113,
                    117,
                    121,
                    125,
                    129,
                    133,
                    137,
                    141,
                    145,
                    149,
                    153,
                    157,
                    161,
                    165,
                    169,
                    173,
                    177,
                    181,
                    185,
                    189,
                    193,
                    197,
                    201,
                    205,
                    209,
                    213,
                    217,
                    221,
                    225,
                    229,
                    233,
                },
                "supported_data_rates_list": {6, 9, 12, 18, 24, 36, 48, 54},
                "mandatory_data_rates_list": {
                    "max_length": 2,
                    "subset_of": "supported_data_rates_list",
                },
                "discovery_frames_6ghz": [
                    "None",
                    "Broadcast Probe Response",
                    "FILS Discovery",
                ],
                "flexible_radio_assignment": {
                    "client_reset_count": (1, 10),
                    "client_utilization_threshold": (1, 100),
                },
            },
        }

        # Iterate over each profile in the list
        for profile in radio_frequency_profiles:
            profile_name = profile.get("radio_frequency_profile_name", "Unknown")
            self.log("Validating profile: {0}".format(profile_name), "DEBUG")

            # Check if the profile name exceeds the maximum allowed length
            if len(profile_name) > 30:
                self.msg = "Profile name '{0}' exceeds max length.".format(profile_name)
                self.fail_and_exit(self.msg)

            if state == "merged":
                # Ensure required parameters are present
                if "default_rf_profile" not in profile or "radio_bands" not in profile:
                    self.msg = "Required parameters missing for profile: {0}. Required parameters are 'default_rf_profile', 'radio_bands'.".format(
                        profile_name
                    )
                    self.fail_and_exit(self.msg)

                # Validate radio_bands
                valid_radio_bands = {2.4, 5, 6}
                radio_bands = profile["radio_bands"]
                if not set(radio_bands).issubset(valid_radio_bands):
                    self.msg = "Invalid values in 'radio_bands' for profile {0}. Allowed values: {1}".format(
                        profile_name, valid_radio_bands
                    )
                    self.fail_and_exit(self.msg)

                for band_key, band_rules in VALIDATION_RULES.items():
                    if band_key in profile:
                        band_settings = profile[band_key]
                        # Validate common parameters
                        for param, rule in VALIDATION_RULES["common"].items():
                            if param in band_settings:
                                self.validate_profile_params(
                                    profile_name, band_settings, band_key, rule, param
                                )
                        # Validate band-specific parameters
                        for param, rule in band_rules.items():
                            if param in band_settings:
                                self.validate_profile_params(
                                    profile_name, band_settings, band_key, rule, param
                                )

            self.log(
                "Completed validation for profile: {0}".format(profile_name), "INFO"
            )

        # Perform validation for a single default RF profile only if the state is "merged"
        if state == "merged":
            self.log(
                "Validating that only one RF profile is set as default for state: 'merged'.",
                "DEBUG",
            )
            self.validate_single_default_rf_profile(radio_frequency_profiles)

        self.log(
            "Completed validation for Radio Frequency Profiles with state: {0}".format(
                state
            ),
            "INFO",
        )

    def validate_profile_params(
        self, profile_name, band_settings, band_key, rule, param
    ):
        """
        Helper function to validate the parameters based on the defined rules.
        """
        value = band_settings.get(param)

        # Check if the value is missing or invalid
        if not value:
            self.log(
                "No value provided for {0} in profile {1}. Skipping validation.".format(
                    param, profile_name
                ),
                "INFO",
            )
            return

        # Check if the parameter value is within the allowed list
        if isinstance(rule, list) and value not in rule:
            self.msg = "Invalid {0} in profile {1}. Allowed: {2}".format(
                param, profile_name, rule
            )
            self.fail_and_exit(self.msg)

        # Validate set constraints
        elif isinstance(rule, set):
            if not isinstance(value, list):
                value = [value]
            self.validate_list_values(value, rule, param, profile_name)

        # Validate range constraints
        elif isinstance(rule, tuple):
            self.validate_range(value, *rule, param, profile_name)

        # Validate mandatory data rates
        elif isinstance(rule, dict) and "subset_of" in rule:
            self.validate_rf_profile_mandatory_data_rates(
                value, band_settings[rule["subset_of"]], param, profile_name
            )

        # Validate dict constraints
        elif isinstance(rule, dict) and isinstance(value, dict):
            for sub_param, sub_rule in rule.items():
                if sub_param in value:
                    sub_value = value[sub_param]
                    min_val, max_val = sub_rule
                    self.validate_range(
                        sub_value, min_val, max_val, sub_param, profile_name
                    )

    def validate_anchor_groups_params(self, anchor_groups, state):
        """
        Validates the parameters of anchor groups based on specified conditions and state.
        Args:
            anchor_groups (list): A list of dictionaries containing parameters for each anchor group.
            state (str): The state of the operation, either "merged" or "deleted".
        """
        required_params = self.get_anchor_groups_required_params(state)

        for anchor_group in anchor_groups:
            self.validate_anchor_groups_missing_params(anchor_group, required_params)
            self.validate_anchor_groups_name(anchor_group)

            if state == "merged":
                self.validate_anchor_groups_mobility_anchors(anchor_group)

        self.log(
            "Required anchor group parameters validated successfully for all anchor groups.",
            "DEBUG",
        )

    def get_anchor_groups_required_params(self, state):
        """
        Returns the required parameters for anchor groups based on the state.
        Args:
            state (str): The state of the operation, either "merged" or "deleted".
        Returns:
            list: A list of required parameters.
        """
        self.log(
            "Getting required parameters for anchor groups with state: {0}".format(
                state
            ),
            "DEBUG",
        )

        required_params = []
        if state == "merged":
            required_params = ["anchor_group_name", "mobility_anchors"]
        elif state == "deleted":
            required_params = ["anchor_group_name"]

        self.log(
            "Required parameters for anchor groups with state '{0}': {1}".format(
                state, required_params
            ),
            "INFO",
        )
        return required_params

    def validate_anchor_groups_missing_params(self, anchor_group, required_params):
        """
        Validates if any required parameters are missing in the anchor group.
        Args:
            anchor_group (dict): The anchor group to be validated.
            required_params (list): A list of required parameters.
        Raises:
            Exception: If any required parameter is missing.
        """
        self.log(
            "Validating missing parameters for anchor group: {0}".format(anchor_group),
            "DEBUG",
        )

        missing_params = [
            param for param in required_params if param not in anchor_group
        ]
        if missing_params:
            self.msg = (
                "The following required parameters for anchor group configuration are missing: {0}. "
                "Provided parameters: {1}"
            ).format(", ".join(missing_params), anchor_group)
            self.fail_and_exit(self.msg)

        self.log(
            "All required parameters are present for anchor group: {0}".format(
                anchor_group
            ),
            "INFO",
        )

    def validate_anchor_groups_name(self, anchor_group):
        """
        Validates the name of the anchor group.
        Args:
            anchor_group (dict): The anchor group to be validated.
        Raises:
            Exception: If the name is invalid.
        """
        self.log(
            "Validating 'anchor_group_name' for anchor group: {0}".format(anchor_group),
            "DEBUG",
        )

        anchor_group_name = anchor_group.get("anchor_group_name")
        if anchor_group_name and not (1 <= len(anchor_group_name) <= 32):
            self.msg = (
                "The 'anchor_group_name' length must be between 1 and 32 characters. "
                "Provided 'anchor_group_name': {0} (length: {1})"
            ).format(anchor_group_name, len(anchor_group_name))
            self.fail_and_exit(self.msg)

        self.log(
            "'anchor_group_name' is valid for anchor group: {0}".format(anchor_group),
            "INFO",
        )

    def validate_anchor_groups_mobility_anchors(self, anchor_group):
        """
        Validates the mobility anchors of the anchor group.
        Args:
            anchor_group (dict): The anchor group to be validated.
        Raises:
            Exception: If the mobility anchors are invalid.
        """
        self.log(
            "Validating 'mobility_anchors' for anchor group: {0}".format(anchor_group),
            "DEBUG",
        )

        mobility_anchors = anchor_group.get("mobility_anchors")
        if mobility_anchors is not None:
            if not isinstance(mobility_anchors, list) or len(mobility_anchors) > 3:
                self.msg = (
                    "The 'mobility_anchors' list must not exceed 3 entries. "
                    "Provided 'mobility_anchors': {0}"
                ).format(mobility_anchors)
                self.fail_and_exit(self.msg)
            for anchor in mobility_anchors:
                self.validate_anchor_groups_device_name_or_ip(anchor)
                self.validate_anchor_groups_device_ip_address(anchor)
                self.validate_anchor_groups_device_mac_address(anchor)
                self.validate_anchor_groups_device_priority(anchor)
                self.validate_anchor_groups_device_nat_ip_address(anchor)
                self.validate_anchor_groups_mobility_group_name(anchor)
                self.validate_anchor_groups_device_type(anchor)
                self.validate_anchor_groups_required_fields(anchor)

        self.log(
            "'mobility_anchors' are valid for anchor group: {0}".format(anchor_group),
            "INFO",
        )

    def validate_anchor_groups_device_name_or_ip(self, anchor):
        """
        Validates that either device name or IP address is provided for the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If neither device name nor IP address is provided.
        """
        self.log(
            "Validating 'device_name' or 'device_ip_address' for mobility anchor: {0}".format(
                anchor
            ),
            "DEBUG",
        )

        if not anchor.get("device_name") and not anchor.get("device_ip_address"):
            self.msg = (
                "Either 'device_name' or 'device_ip_address' is required for each mobility anchor. "
                "Provided anchor: {0}"
            ).format(anchor)
            self.fail_and_exit(self.msg)

        self.log(
            "'device_name' or 'device_ip_address' is valid for mobility anchor: {0}".format(
                anchor
            ),
            "INFO",
        )

    def validate_anchor_groups_device_ip_address(self, anchor):
        """
        Validates the device IP address of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If the device IP address is invalid.
        """
        self.log(
            "Validating 'device_ip_address' for mobility anchor: {0}".format(anchor),
            "DEBUG",
        )

        device_ip_address = anchor.get("device_ip_address")
        if device_ip_address and not self.is_valid_ipv4(device_ip_address):
            self.msg = (
                "Device IP Address '{0}' is not in a valid IPv4 format."
            ).format(device_ip_address)
            self.fail_and_exit(self.msg)

        self.log(
            "'device_ip_address' is valid for mobility anchor: {0}".format(anchor),
            "INFO",
        )

    def validate_anchor_groups_device_mac_address(self, anchor):
        """
        Validates the device MAC address of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If the device MAC address is invalid.
        """
        self.log(
            "Validating 'device_mac_address' for mobility anchor: {0}".format(anchor),
            "DEBUG",
        )

        device_mac_address = anchor.get("device_mac_address")
        self.log(
            "Validating device MAC address: {0}".format(device_mac_address), "DEBUG"
        )
        if device_mac_address:
            # Define regex patterns for valid MAC address formats
            valid_mac_patterns = [
                # Format: 00:11:22:33:44:55 or 00-11-22-33-44-55
                r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
                # Format: 0a0b.0c01.0211
                r"^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$",
                # Format: 0a0b0c010211
                r"^[0-9A-Fa-f]{12}$",
            ]

            # Check if the MAC address matches any of the valid patterns
            if not any(
                re.match(pattern, device_mac_address) for pattern in valid_mac_patterns
            ):
                self.msg = (
                    "Device MAC Address '{0}' is not in a valid format."
                ).format(device_mac_address)
                self.fail_and_exit(self.msg)

        self.log(
            "'device_mac_address' is valid for mobility anchor: {0}".format(anchor),
            "INFO",
        )

    def validate_anchor_groups_device_priority(self, anchor):
        """
        Validates the device priority of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If the device priority is invalid.
        """
        self.log(
            "Validating 'device_priority' for mobility anchor: {0}".format(anchor),
            "DEBUG",
        )

        device_priority = anchor.get("device_priority")
        if device_priority is not None and not (1 <= device_priority <= 3):
            self.msg = ("Device priority '{0}' must be between 1 and 3.").format(
                device_priority
            )
            self.fail_and_exit(self.msg)

        self.log(
            "'device_priority' is valid for mobility anchor: {0}".format(anchor), "INFO"
        )

    def validate_anchor_groups_device_nat_ip_address(self, anchor):
        """
        Validates the device NAT IP address of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If the device NAT IP address is invalid.
        """
        self.log(
            "Validating 'device_nat_ip_address' for mobility anchor: {0}".format(
                anchor
            ),
            "DEBUG",
        )

        device_nat_ip_address = anchor.get("device_nat_ip_address")
        if device_nat_ip_address and not self.is_valid_ipv4(device_nat_ip_address):
            self.msg = (
                "Device NAT IP Address '{0}' is not in a valid IPv4 format."
            ).format(device_nat_ip_address)
            self.fail_and_exit(self.msg)

        self.log(
            "'device_nat_ip_address' is valid for mobility anchor: {0}".format(anchor),
            "INFO",
        )

    def validate_anchor_groups_mobility_group_name(self, anchor):
        """
        Validates the mobility group name of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If the mobility group name is invalid.
        """
        self.log(
            "Validating 'mobility_group_name' for mobility anchor: {0}".format(anchor),
            "DEBUG",
        )

        mobility_group_name = anchor.get("mobility_group_name")
        if mobility_group_name and not re.match(
            r"^[a-zA-Z0-9_]{1,31}$", mobility_group_name
        ):
            self.msg = (
                "Mobility Group Name must be alphanumeric without {{!,<,space,?/}} and maximum of 31 characters. "
                "Provided: {0}"
            ).format(mobility_group_name)
            self.fail_and_exit(self.msg)

        self.log(
            "'mobility_group_name' is valid for mobility anchor: {0}".format(anchor),
            "INFO",
        )

    def validate_anchor_groups_device_type(self, anchor):
        """
        Validates the device type of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If the device type is invalid.
        """
        self.log(
            "Validating 'device_type' for mobility anchor: {0}".format(anchor), "DEBUG"
        )

        device_type = anchor.get("device_type")
        if device_type and device_type not in ["IOS-XE", "AIREOS"]:
            self.msg = (
                "Device Type '{0}' is not valid. Must be 'IOS-XE' or 'AIREOS'."
            ).format(device_type)
            self.fail_and_exit(self.msg)

        self.log(
            "'device_type' is valid for mobility anchor: {0}".format(anchor), "INFO"
        )

    def validate_anchor_groups_required_fields(self, anchor):
        """
        Validates the required fields of the mobility anchor.
        Args:
            anchor (dict): The mobility anchor to be validated.
        Raises:
            Exception: If any required field is missing.
        """
        self.log(
            "Validating required fields for mobility anchor: {0}".format(anchor),
            "DEBUG",
        )

        managed_device = anchor.get("managed_device")
        if managed_device is None:
            self.msg = (
                "The 'managed_device' is a required parameter for each mobility anchor. Provided anchor: {0}"
            ).format(anchor)
            self.fail_and_exit(self.msg)
        self.log(
            "'managed_device' is valid for mobility anchor: {0}".format(anchor), "INFO"
        )

        device_priority = anchor.get("device_priority")
        if device_priority is None:
            self.msg = (
                "The 'device_priority' is a required parameter for each mobility anchor. Provided anchor: {0}"
            ).format(anchor)
            self.fail_and_exit(self.msg)
        self.log(
            "'device_priority' is valid for mobility anchor: {0}".format(anchor), "INFO"
        )

    def validate_params(self, config, state):
        """
        Validate configuration parameters based on their type and state.
        Args:
            config (dict): The configuration dictionary containing parameters to validate.
            state (str): The state of the parameters, For example, "merged" or "deleted".
        """
        self.log("Starting validation of the input parameters.", "INFO")

        # Define a mapping of configuration keys to their corresponding validation functions
        validation_mapping = {
            "ssids": self.validate_ssids_params,
            "interfaces": self.validate_interfaces_params,
            "power_profiles": self.validate_power_profiles_params,
            "access_point_profiles": self.validate_access_point_profiles_params,
            "radio_frequency_profiles": self.validate_radio_frequency_profiles_params,
            "anchor_groups": self.validate_anchor_groups_params,
        }

        # Iterate over each configuration component and validate if present
        for config_key, validation_function in validation_mapping.items():
            config_value = config.get(config_key)
            if config_value:
                self.log(
                    "Config Key: {0}, Validation Function: {1}, Config Value: {2}".format(
                        config_key, validation_function.__name__, config_value
                    ),
                    "DEBUG",
                )

                # Perform validation and log the process
                self.log(
                    "Validating {0} parameters in '{1}' state.".format(
                        config_key, state
                    ),
                    "DEBUG",
                )
                validation_function(config_value, state)
                self.log(
                    "Completed validation of {0} parameters in '{1}' state.".format(
                        config_key, state
                    ),
                    "DEBUG",
                )

        self.log("Completed validation of all input parameters.", "INFO")

    def execute_get_with_pagination(self, api_family, api_function, params):
        """
        Executes a paginated GET request using the specified API family, function, and parameters.
        Args:
            api_family (str): The API family to use for the call (For example, 'wireless', 'network', etc.).
            api_function (str): The specific API function to call for retrieving data (For example, 'get_ssid_by_site', 'get_interfaces').
            params (dict): Parameters for filtering the data.
        Returns:
            list: A list of dictionaries containing the retrieved data based on the filtering parameters.
        """
        def update_params(offset, limit, use_strings=False):
            """Update the params dictionary with pagination info."""
            params.update({
                "offset": str(offset) if use_strings else offset,
                "limit": str(limit) if use_strings else limit
            })

        try:
            # Initialize pagination variables
            offset = 1
            limit = 500
            results = []
            use_strings = api_function in {"get_ap_profiles", "get_anchor_groups"}

            # Start the loop for paginated API calls
            while True:
                # Update parameters for pagination
                update_params(offset, limit, use_strings)

                try:
                    # Execute the API call
                    self.log(
                        "Attempting API call with {0} offset and limit for family '{1}', function '{2}': {3}".format(
                            "string" if use_strings else "integer", api_family, api_function, params
                        ),
                        "INFO"
                    )

                    # Execute the API call
                    response = self.dnac._exec(
                        family=api_family,
                        function=api_function,
                        op_modifies=False,
                        params=params,
                    )

                except Exception as e:
                    # Retry with integer offset/limit for specific cases
                    if api_function == "get_ap_profiles" and use_strings:
                        self.log(
                            "API call failed with string offset and limit. Retrying with integer values. Error: {0}".format(
                                str(e)
                            ),
                            "WARNING",
                        )
                        use_strings = False
                        continue

                    else:
                        self.msg = (
                            "An error occurred while retrieving data using family '{0}', function '{1}'. "
                            "Details using API call. Error: {2}".format(api_family, api_function, str(e))
                        )
                        self.fail_and_exit(self.msg)

                self.log(
                    "Response received from API call for family '{0}', function '{1}': {2}".format(
                        api_family, api_function, response
                    ),
                    "INFO",
                )

                # Process the response if available
                response = response.get("response")
                if not response:
                    self.log(
                        "Exiting the loop because no data was returned after increasing the offset. "
                        "Current offset: {0}".format(offset),
                        "INFO",
                    )
                    break

                # Extend the results list with the response data
                results.extend(response)

                # Check if the response size is less than the limit
                if len(response) < limit:
                    self.log(
                        "Received less than limit ({0}) results, assuming last page. Exiting pagination.".format(
                            limit
                        ),
                        "DEBUG",
                    )
                    break

                # Increment the offset for the next iteration
                offset += limit

            if results:
                self.log(
                    "Data retrieved for family '{0}', function '{1}': {2}".format(
                        api_family, api_function, results
                    ),
                    "DEBUG",
                )
            else:
                self.log(
                    "No data found for family '{0}', function '{1}'.".format(
                        api_family, api_function
                    ),
                    "DEBUG",
                )

            # Return the list of retrieved data
            return results

        except Exception as e:
            self.msg = (
                "An error occurred while retrieving data using family '{0}', function '{1}'. "
                "Details using API call. Error: {2}".format(
                    api_family, api_function, str(e)
                )
            )
            self.fail_and_exit(self.msg)

    def get_ssids_params(
        self,
        site_id,
        ssid_name=None,
        ssid_type=None,
        l2_auth_type=None,
        l3_auth_type=None,
    ):
        """
        Generates the parameters for retrieving SSIDs, mapping optional user parameters
        to the API's expected parameter names.
        Args:
            site_id (str): The ID of the site for which SSIDs are to be retrieved.
            ssid_name (str, optional): The name of the SSID.
            ssid_type (str, optional): The type of the SSID.
            l2_auth_type (str, optional): The Layer 2 authentication type.
            l3_auth_type (str, optional): The Layer 3 authentication type.
        Returns:
            dict: A dictionary of parameters for the API call, populated with any provided values.
        """
        # Initialize an empty dictionary to hold the parameters for the API call
        get_ssids_params = {}
        self.log("Initialized parameters dictionary for API call.", "DEBUG")

        # Map the site ID to the expected API parameter
        get_ssids_params["site_id"] = site_id
        self.log("Mapped 'site_id' to '{0}'.".format(site_id), "DEBUG")

        # Map the user-provided SSID name to the expected API parameter
        if ssid_name:
            get_ssids_params["ssid"] = ssid_name
            self.log("Mapped 'ssid' to '{0}'.".format(ssid_name), "DEBUG")

        # Map the user-provided SSID type to the expected API parameter
        if ssid_type:
            get_ssids_params["wlanType"] = ssid_type
            self.log("Mapped 'ssid_type' to '{0}'.".format(ssid_type), "DEBUG")

        # Map the user-provided Layer 2 authentication type to the expected API parameter
        if l2_auth_type:
            get_ssids_params["authType"] = l2_auth_type
            self.log("Mapped 'l2_auth_type' to '{0}'.".format(l2_auth_type), "DEBUG")

        # Map the user-provided Layer 3 authentication type to the expected API parameter
        if l3_auth_type:
            get_ssids_params["l3AuthType"] = l3_auth_type
            self.log("Mapped 'l3_auth_type' to '{0}'.".format(l3_auth_type), "DEBUG")

        # Return the constructed parameters dictionary
        self.log("Constructed get_ssids_params: {0}".format(get_ssids_params), "DEBUG")
        return get_ssids_params

    def get_ssids(self, site_id, get_ssids_params):
        """
        Retrieves SSIDs for a specified site using pagination.
        Args:
            site_id (str): The identifier of the site for which SSIDs are to be retrieved.
            get_ssids_params (dict): Parameters for filtering the SSIDs.
        Returns:
            list: A list of dictionaries containing details of SSIDs for the specified site.
        """
        # Add the site ID to the parameters
        # get_ssids_params["site_id"] = site_id
        # self.log("Added 'site_id' to parameters: {0}".format(site_id), "DEBUG")

        # Execute the paginated API call to retrieve SSIDs
        self.log(
            "Executing paginated API call to retrieve ssids for site ID: {}.".format(
                site_id
            ),
            "DEBUG",
        )
        return self.execute_get_with_pagination(
            "wireless", "get_ssid_by_site", get_ssids_params
        )

    def update_ssid_parameter_mappings(self, ssid_name, ssid_type, ssid_settings):
        """
        Updates SSID parameters by mapping provided settings to the required format.
        Args:
            ssid_name (str): The name of the SSID.
            ssid_type (str): The type of the SSID.
            ssid_settings (dict): A dictionary containing various SSID settings to be mapped.
        Returns:
            dict: A dictionary of the SSID parameters mapped to the required format.
        """
        # Initialize modified SSID dictionary
        modified_ssid = {"ssid": ssid_name, "wlanType": ssid_type}

        # Mapping of user-facing WLAN config keys to corresponding API payload field names
        mappings = {
            "basic": {
                "wlan_profile_name": "profileName",
                "fast_lane": "isFastLaneEnabled",
                "fast_transition": "fastTransition",
                "fast_transition_over_the_ds": "fastTransitionOverTheDistributedSystemEnable",
                "cckm_timestamp_tolerance": "cckmTsfTolerance",
                "mfp_client_protection": "managementFrameProtectionClientprotection",
                "protected_management_frame": "protectedManagementFrame",
                "11k_neighbor_list": "neighborListEnable",
                "coverage_hole_detection": "coverageHoleDetectionEnable",
                "nas_id": "nasOptions",
                "client_rate_limit": "clientRateLimit",
            },
            "quality_of_service": {"egress": "egressQos", "ingress": "ingressQos"},
            "ssid_state": {
                "admin_status": "isEnabled",
                "broadcast_ssid": "isBroadcastSSID",
            },
            "l2_security": {
                "l2_auth_type": "authType",
                "ap_beacon_protection": "isApBeaconProtectionEnabled",
                "passphrase_type": "isHex",
                "passphrase": "passphrase",
                "open_ssid": "openSsid",
            },
            "radio_policy": {
                "band_select": "wlanBandSelectEnable",
                "6_ghz_client_steering": "ghz6PolicyClientSteering",
            },
            "wlan_timeouts": {
                "enable_session_timeout": "sessionTimeOutEnable",
                "session_timeout": "sessionTimeOut",
                "enable_client_exclusion_timeout": "clientExclusionEnable",
                "client_exclusion_timeout": "clientExclusionTimeout",
            },
            "bss_transition_support": {
                "bss_max_idle_service": "basicServiceSetMaxIdleEnable",
                "bss_idle_client_timeout": "basicServiceSetClientIdleTimeout",
                "directed_multicast_service": "directedMulticastServiceEnable",
            },
            "aaa": {
                "auth_servers_ip_address_list": "authServers",
                "accounting_servers_ip_address_list": "acctServers",
                "aaa_override": "aaaOverride",
                "mac_filtering": "isMacFilteringEnabled",
                "deny_rcm_clients": "isRandomMacFilterEnabled",
                "enable_posture": "isPosturingEnabled",
                "pre_auth_acl_name": "aclName",
            },
        }

        # Apply basic mappings directly from ssid_settings
        self.log("Applying Basic Settings.", "DEBUG")
        for key, ssid_key in mappings["basic"].items():
            if key in ssid_settings:
                value = ssid_settings[key]
                modified_ssid[ssid_key] = value
                self.log("Mapped '{0}' to '{1}'.".format(ssid_key, value), "DEBUG")

        # Apply mappings
        for category, mapping in mappings.items():
            if category == "basic":
                continue

            settings = ssid_settings.get(category, {})
            if not settings:
                self.log(
                    "No settings found for category '{0}'. Skipping.".format(
                        category.replace("_", " ").title()
                    ),
                    "DEBUG",
                )

            if settings:
                self.log(
                    "Applying {0} settings.".format(category.replace("_", " ").title()),
                    "DEBUG",
                )
                for key, ssid_key in mapping.items():
                    if key in settings:
                        value = settings[key]
                        if key == "passphrase_type":
                            value = value == "HEX"
                        modified_ssid[ssid_key] = value
                        self.log(
                            "Mapped '{0}' to '{1}'.".format(ssid_key, value), "DEBUG"
                        )

        # mpsk_settings keys and entry keys
        mpsk_key_mappings = [
            ("mpsk_priority", "priority"),
            ("mpsk_passphrase_type", "passphraseType"),
            ("mpsk_passphrase", "passphrase"),
        ]

        # Handle multiPSKSettings separately
        mpsk_settings = ssid_settings.get("l2_security", {}).get("mpsk_settings")
        if mpsk_settings:
            self.log("Processing MPSK settings: {0}".format(mpsk_settings), "DEBUG")
            modified_ssid["multiPSKSettings"] = []
            for setting in mpsk_settings:
                self.log("Processing MPSK setting: {0}".format(setting), "DEBUG")
                entry = {}
                self.log(
                    "Creating entry for MPSK setting: {0}".format(setting), "DEBUG"
                )
                for mpsk_key, entry_key in mpsk_key_mappings:
                    value = setting.get(mpsk_key)
                    if value is not None:
                        entry[entry_key] = value
                        self.log(
                            "Mapped '{0}' to '{1}'.".format(entry_key, value), "DEBUG"
                        )

                modified_ssid["multiPSKSettings"].append(entry)
                self.log("Added MPSK entry: {0}".format(entry), "DEBUG")

            self.log("MPSK Settings updated.", "DEBUG")

        # Auth Key Management settings
        auth_key_management = ssid_settings.get("auth_key_management", [])
        if auth_key_management:
            self.log("Applying AKM settings.", "DEBUG")
            key_management_mapping = {
                "SAE": "isAuthKeySae",
                "SAE-EXT-KEY": "isAuthKeySaeExt",
                "FT+SAE": "isAuthKeySaePlusFT",
                "FT+SAE-EXT-KEY": "isAuthKeySaeExtPlusFT",
                "OWE": "isAuthKeyOWE",
                "PSK": "isAuthKeyPSK",
                "FT+PSK": "isAuthKeyPSKPlusFT",
                "EASY-PSK": "isAuthKeyEasyPSK",
                "PSK-SHA2": "isAuthKeyPSKSHA256",
                "802.1X-SHA1": "isAuthKey8021x",
                "802.1X-SHA2": "isAuthKey8021x_SHA256",
                "FT+802.1X": "isAuthKey8021xPlusFT",
                "SUITE-B-1X": "isAuthKeySuiteB1x",
                "SUITE-B-192X": "isAuthKeySuiteB1921x",
                "CCKM": "isCckmEnabled",
            }

            for key in auth_key_management:
                key_upper = key.upper()
                if key_upper in key_management_mapping:
                    modified_ssid[key_management_mapping[key_upper]] = True
                    self.log(
                        "Mapped '{0}' to True.".format(
                            key_management_mapping[key_upper]
                        ),
                        "DEBUG",
                    )

        # Radio Bands and Policies
        radio_policy = ssid_settings.get("radio_policy", {})
        if radio_policy:
            radio_bands = set(radio_policy.get("radio_bands", [2.4, 5, 6]))
            radio_band_mapping = {
                frozenset({2.4, 5, 6}): "Triple band operation(2.4GHz, 5GHz and 6GHz)",
                frozenset({5}): "5GHz only",
                frozenset({2.4}): "2.4GHz only",
                frozenset({6}): "6GHz only",
                frozenset({2.4, 5}): "2.4 and 5 GHz",
                frozenset({2.4, 6}): "2.4 and 6 GHz",
                frozenset({5, 6}): "5 and 6 GHz",
            }

            radio_type = radio_band_mapping.get(frozenset(radio_bands))
            if radio_type:
                modified_ssid["ssidRadioType"] = radio_type
                self.log("Mapped 'ssidRadioType' to '{0}'.".format(radio_type), "DEBUG")

            ghz24_policy_mapping = {
                "802.11-bg": "dot11-bg-only",
                "802.11-g": "dot11-g-only",
            }
            ghz24_policy = radio_policy.get("2_dot_4_ghz_band_policy")
            if ghz24_policy in ghz24_policy_mapping:
                modified_ssid["ghz24Policy"] = ghz24_policy_mapping[ghz24_policy]
                self.log(
                    "Mapped 'ghz24Policy' to '{0}'.".format(
                        ghz24_policy_mapping[ghz24_policy]
                    ),
                    "DEBUG",
                )

        # Encryption settings
        self.log("Applying Encryption settings.", "DEBUG")
        wpa_encryption = ssid_settings.get("wpa_encryption", [])
        if wpa_encryption:
            encryption_mapping = {
                "GCMP256": "rsnCipherSuiteGcmp256",
                "CCMP256": "rsnCipherSuiteCcmp256",
                "GCMP128": "rsnCipherSuiteGcmp128",
                "CCMP128": "rsnCipherSuiteCcmp128",
            }

            for enc_type in wpa_encryption:
                enc_type_upper = enc_type.upper()
                if enc_type_upper in encryption_mapping:
                    modified_ssid[encryption_mapping[enc_type_upper]] = True
                    self.log(
                        "Enabled encryption type '{}'.".format(
                            encryption_mapping[enc_type_upper]
                        ),
                        "DEBUG",
                    )

        # L3 Security settings
        self.log("Applying L3 Security settings.", "DEBUG")
        l3_security = ssid_settings.get("l3_security", {})
        if l3_security:
            l3_auth_type = l3_security.get("l3_auth_type")
            if l3_auth_type:
                modified_ssid["l3AuthType"] = {
                    "WEB_AUTH": "web_auth",
                    "OPEN": "open",
                }.get(l3_auth_type, l3_auth_type)
                self.log(
                    "Mapped 'l3AuthType' to '{}'.".format(modified_ssid["l3AuthType"]),
                    "DEBUG",
                )

            auth_server_mapping = {
                "central_web_authentication": "auth_ise",
                "web_authentication_internal": "auth_internal",
                "web_authentication_external": "auth_external",
                "web_passthrough_internal": "auth_internal",
                "web_passthrough_external": "auth_external",
            }
            auth_server = l3_security.get("auth_server")
            if auth_server:
                modified_ssid["authServer"] = auth_server_mapping.get(auth_server)
                modified_ssid["webPassthrough"] = auth_server in [
                    "web_passthrough_internal",
                    "web_passthrough_external",
                ]
                self.log(
                    "Mapped 'authServer' to '{0}', 'webPassthrough': {1}.".format(
                        modified_ssid["authServer"], modified_ssid["webPassthrough"]
                    ),
                    "DEBUG",
                )

            l3_security_mapping = {
                "web_auth_url": "externalAuthIpAddress",
                "enable_sleeping_client": "sleepingClientEnable",
                "sleeping_client_timeout": "sleepingClientTimeout",
            }

            for key, value in l3_security.items():
                if key in l3_security_mapping:
                    modified_ssid[l3_security_mapping[key]] = value
                    self.log(
                        "Mapped '{0}' to '{1}'.".format(
                            l3_security_mapping[key], value
                        ),
                        "DEBUG",
                    )

        self.log("Final modified SSID: {}".format(modified_ssid), "INFO")
        return modified_ssid

    def reset_encryption_and_auth_params(self, auth_type, updated_ssid, requested_ssid):
        """
        Resets WPA encryption and authentication key management parameters to False.
        Args:
            auth_type (str): The type of authentication being used (e.g., "WPA2", "WPA3").
            updated_ssid (dict): The existing SSID dictionary to update.
            requested_ssid (dict): The requested SSID dictionary containing the desired parameters.
        """
        self.log("Starting reset of encryption and authentication parameters.", "INFO")

        # Log the initial state of updated_ssid and requested_ssid
        self.log("Initial updated_ssid: {0}".format(updated_ssid), "DEBUG")
        self.log("Initial requested_ssid: {0}".format(requested_ssid), "DEBUG")

        # WPA Encryption parameters
        wpa_encryption_params = [
            "rsnCipherSuiteGcmp256",
            "rsnCipherSuiteCcmp256",
            "rsnCipherSuiteGcmp128",
            "rsnCipherSuiteCcmp128"
        ]

        # Authentication key management parameters
        auth_key_management_params = [
            "isAuthKeySae",
            "isAuthKeySaeExt",
            "isAuthKeySaePlusFT",
            "isAuthKeySaeExtPlusFT",
            "isAuthKeyOWE",
            "isAuthKeyPSK",
            "isAuthKeyPSKPlusFT",
            "isAuthKeyEasyPSK",
            "isAuthKeyPSKSHA256",
            "isAuthKey8021x",
            "isAuthKey8021x_SHA256",
            "isAuthKey8021xPlusFT",
            "isAuthKeySuiteB1x",
            "isAuthKeySuiteB1921x",
            "isCckmEnabled"
        ]

        def reset_parameters(params, param_type, reset_all=False):
            """
            Resets the given parameters in `updated_ssid` to False.
            Args:
                params (list): List of parameter names to reset.
                param_type (str): Type of parameters being reset (e.g., "WPA encryption", "authentication key management").
                reset_all (bool): If True, resets all parameters regardless of their presence in `requested_ssid`.
            """
            # Log the start of the reset process for the given parameter type
            if reset_all or any(param in requested_ssid for param in params):
                self.log("Resetting {0} parameters.".format(param_type), "DEBUG")
                for param in params:
                    # If reset_all is True or the parameter is not in requested_ssid, reset it to False
                    if reset_all or param not in requested_ssid:
                        self.log("Setting {0} parameter '{1}' to False.".format(param_type, param), "DEBUG")
                        updated_ssid[param] = False
                    else:
                        self.log("{0} parameter '{1}' already present in requested SSID. No reset required.".format(param_type, param), "DEBUG")
            else:
                self.log("No {0} parameters found in requested_ssid. Skipping reset for {0} parameters.".format(param_type), "DEBUG")

        # Reset WPA encryption and authentication key management parameters
        if auth_type == "OPEN":
            self.log("Auth type is 'OPEN'. Resetting both WPA encryption and authentication key management parameters.", "DEBUG")
            # Reset all WPA encryption parameters to False
            reset_parameters(wpa_encryption_params, "WPA encryption", reset_all=True)
            # Reset all authentication key management parameters to False
            reset_parameters(auth_key_management_params, "authentication key management", reset_all=True)
        else:
            self.log("Auth type is not 'OPEN'. Proceeding with conditional resets based on requested_ssid.", "DEBUG")
            # Reset WPA encryption parameters only if they exist in requested_ssid
            reset_parameters(wpa_encryption_params, "WPA encryption")
            # Reset authentication key management parameters only if they exist in requested_ssid
            reset_parameters(auth_key_management_params, "authentication key management")

        # Reset passphrase, multiPSKSettings, and openSsid based on auth_type
        self.log("Checking auth_type '{0}' for resetting additional parameters.".format(auth_type), "DEBUG")
        if auth_type == "OPEN" or auth_type != "OPEN-SECURED":
            self.log("Resetting 'openSsid' due to auth_type '{0}'.".format(auth_type), "DEBUG")
            updated_ssid["openSsid"] = ""

        if auth_type == "OPEN" or auth_type not in ["WPA2_PERSONAL", "WPA3_PERSONAL", "WPA2_WPA3_PERSONAL"]:
            self.log("Resetting 'passphrase' due to auth_type '{0}'.".format(auth_type), "DEBUG")
            updated_ssid["passphrase"] = ""

        if auth_type == "OPEN" or auth_type != "WPA2_PERSONAL":
            self.log("Resetting 'multiPSKSettings' due to auth_type '{0}'.".format(auth_type), "DEBUG")
            updated_ssid["multiPSKSettings"] = []

        self.log("Completed reset of encryption and authentication parameters.", "INFO")

    def compare_global_ssids(self, existing_ssids, requested_ssid):
        """
        Compares global SSIDs to determine if they exist and whether updates are required.
        Args:
            existing_ssids (list): A list of dictionaries representing existing SSIDs.
            requested_ssid (dict): A dictionary containing the requested SSID parameters.
        Returns:
            tuple: A tuple containing four elements:
                - ssid_exists (bool): Whether the SSID exists in the existing list.
                - update_required (bool): Whether an update is needed for the SSID.
                - updated_ssid (dict): The updated SSID parameters, if an update is required, otherwise None.
                - ssid_id (str): The ID of the matching SSID, if it exists.
        """
        # Initialize flags and result variables
        ssid_exists = False
        update_required = False
        updated_ssid = None
        ssid_id = ""

        # Extract the name and type from the requested SSID
        requested_ssid_name = requested_ssid.get("ssid")
        requested_ssid_type = requested_ssid.get("wlanType")

        self.log(
            "Starting comparison for requested SSID: '{0}' of type '{1}'.".format(
                requested_ssid_name, requested_ssid_type
            ),
            "INFO",
        )

        # Iterate over the list of existing SSIDs
        for existing_ssid in existing_ssids:
            self.log(
                "Checking existing SSID: '{0}' of type '{1}'.".format(
                    existing_ssid.get("ssid"), existing_ssid.get("wlanType")
                ),
                "DEBUG",
            )

            # Check if there is an SSID with the same name and type
            if (
                existing_ssid.get("ssid") == requested_ssid_name
                and existing_ssid.get("wlanType") == requested_ssid_type
            ):
                self.log(
                    "Matching SSID found: '{0}'. Proceeding with parameter comparison.".format(
                        requested_ssid_name
                    ),
                    "INFO",
                )
                ssid_exists = True
                ssid_id = existing_ssid.get("id")

                # Start with a copy of the existing SSID
                updated_ssid = existing_ssid.copy()

                # Iterate over the parameters of the requested SSID
                for key, requested_value in requested_ssid.items():
                    # Ignore 'sites_specific_override_settings', 'site_id', and 'id'
                    if key in ["sites_specific_override_settings", "site_id", "id"]:
                        continue

                    # Check if the parameter differs in the existing SSID
                    existing_value = existing_ssid.get(key)
                    self.log(
                        "Comparing parameter '{0}': existing value '{1}' vs requested value '{2}'.".format(
                            key, existing_value, requested_value
                        ),
                        "DEBUG",
                    )

                    if existing_value != requested_value:
                        self.log(
                            "Mismatch found for parameter '{0}': existing value '{1}' vs requested value '{2}'.".format(
                                key, existing_value, requested_value
                            ),
                            "DEBUG",
                        )
                        # Update the parameter in the updated SSID
                        updated_ssid[key] = requested_value
                        update_required = True

                # Add site_id and id if necessary
                updated_ssid["id"] = ssid_id
                if "site_id" in requested_ssid:
                    updated_ssid["site_id"] = requested_ssid["site_id"]

                if update_required:
                    self.log(
                        "Update required for SSID '{0}'. Updated parameters: {1}".format(
                            requested_ssid_name, updated_ssid
                        ),
                        "INFO",
                    )

                    # Determine auth_type based on requested_ssid or existing_ssid
                    auth_type = updated_ssid.get("authType", existing_ssid.get("authType"))
                    self.log("Determined auth_type: {0}".format(auth_type), "DEBUG")

                    # Reset encryption and auth params based on authType
                    self.log("Resetting encryption and authentication key management parameters.", "DEBUG")
                    self.reset_encryption_and_auth_params(auth_type, updated_ssid, requested_ssid)
                    self.log("Final updated SSID parameters: {0}".format(updated_ssid), "DEBUG")

                # Exit the loop after handling the match
                break

        if ssid_exists:
            if update_required:
                self.log(
                    "Update required for SSID '{0}'.".format(requested_ssid_name),
                    "INFO",
                )
            else:
                self.log(
                    "No update required for SSID '{0}'.".format(requested_ssid_name),
                    "INFO",
                )
        else:
            self.log(
                "No matching SSID found for '{0}'.".format(requested_ssid_name), "INFO"
            )

        # Return whether the SSID exists, if an update is required, the updated SSID parameters, and the SSID ID
        return ssid_exists, update_required, updated_ssid, ssid_id

    def compare_site_specific_ssids(
        self,
        site_id,
        requested_ssid_name,
        requested_ssid_type,
        existing_ssids,
        requested_ssid,
    ):
        """
        Compares site-specific SSIDs to determine if they exist and whether updates are required.
        Args:
            site_id (str): The site ID where the SSID is located.
            requested_ssid_name (str): The name of the SSID being requested.
            requested_ssid_type (str): The type of the SSID being requested.
            existing_ssids (list): A list of existing SSIDs to compare against.
            requested_ssid (dict): The SSID parameters being requested.
        Returns:
            tuple: A tuple containing three elements:
                - ssid_exists (bool): Whether the SSID exists in the existing list.
                - update_required (bool): Whether an update is needed for the SSID.
                - updated_ssid (dict): The updated SSID parameters, if an update is required, otherwise None.
        """
        # Initialize flags and result dictionary
        ssid_exists = False
        update_required = False
        updated_ssid = None

        self.log(
            "Starting comparison for SSID: '{0}' of type '{1}'.".format(
                requested_ssid_name, requested_ssid_type
            ),
            "INFO",
        )

        # Iterate over the list of existing SSIDs
        for existing_ssid in existing_ssids:
            self.log(
                "Checking existing SSID: '{0}' of type '{1}'.".format(
                    existing_ssid.get("ssid"), existing_ssid.get("wlanType")
                ),
                "DEBUG",
            )

            # Check if there is an SSID with the same name and type
            if (
                existing_ssid.get("ssid") == requested_ssid_name
                and existing_ssid.get("wlanType") == requested_ssid_type
            ):
                self.log(
                    "Matching SSID found: '{0}'.".format(requested_ssid_name), "INFO"
                )
                ssid_exists = True

                # Compare each parameter in the requested SSID with the existing SSID
                for key, value in requested_ssid.items():
                    if existing_ssid.get(key) != value:
                        self.log(
                            "Mismatch found for parameter '{0}': existing value '{1}' vs requested value '{2}'.".format(
                                key, existing_ssid.get(key), value
                            ),
                            "DEBUG",
                        )
                        update_required = True
                        break  # Exit loop on first mismatch

                # If an update is required, prepare the updated SSID
                if update_required:
                    self.log(
                        "Update required for site specific SSID: '{0}'. Preparing updated SSID.".format(
                            requested_ssid_name
                        ),
                        "INFO",
                    )
                    updated_ssid = requested_ssid.copy()  # Copy the requested SSID
                    updated_ssid["id"] = existing_ssid.get(
                        "id"
                    )  # Copy the ID from the existing SSID
                    updated_ssid["site_id"] = site_id  # Add site_id
                else:
                    self.log(
                        "No update required for SSID: '{0}'.".format(
                            requested_ssid_name
                        ),
                        "INFO",
                    )

                # Exit the loop once the matching SSID is found
                break

        if not ssid_exists:
            self.log(
                "SSID: '{0}' of type '{1}' does not exist in the provided list.".format(
                    requested_ssid_name, requested_ssid_type
                ),
                "INFO",
            )

        # Return whether the SSID exists, if an update is required, and the updated SSID parameters
        return ssid_exists, update_required, updated_ssid

    def process_ssid_entry(
        self, ssid_entry, ssid_params, site_id, ssid_id, operation_list
    ):
        """
        Process the SSID entry by updating its parameters and appending it to the appropriate list.
        Args:
            ssid_entry (dict): The dictionary representing the SSID entry.
            ssid_params (dict): The parameters to be updated in the SSID entry.
            site_id (str): The site ID to be added.
            ssid_id (str): The SSID ID to be added.
            operation_list (list): The list to which the processed SSID entry should be appended.
        """
        self.log(
            "Processing SSID entry with initial parameters: {0}".format(ssid_entry),
            "DEBUG",
        )

        # Assign parameters to ssid_entry
        ssid_entry["ssid_params"] = ssid_params
        self.log("Updated SSID parameters: {0}".format(ssid_params), "DEBUG")

        # Add site_id and ssid_id to ssid_params
        ssid_entry["ssid_params"]["site_id"] = site_id
        self.log("Added site_id '{0}' to SSID parameters.".format(site_id), "DEBUG")
        ssid_entry["ssid_params"]["id"] = ssid_id
        self.log("Added ssid_id '{0}' to SSID parameters.".format(ssid_id), "DEBUG")

        # Set the SSID name in ssid_entry
        ssid_entry["ssid_name"] = ssid_entry["ssid_params"].get("ssid")
        self.log("SSID name set to '{0}'.".format(ssid_entry["ssid_name"]), "DEBUG")

        # Set the wlanType in ssid_entry
        ssid_entry["wlanType"] = ssid_entry["ssid_params"].get("wlanType")
        self.log("SSID wlanType set to '{0}'.".format(ssid_entry["wlanType"]), "DEBUG")

        # Remove "ssid" and "wlanType" from ssid_params
        removed_ssid = ssid_entry["ssid_params"].pop("ssid", None)
        removed_wlan_type = ssid_entry["ssid_params"].pop("wlanType", None)
        if removed_ssid is not None or removed_wlan_type is not None:
            self.log("Removed 'ssid' and/or 'wlanType' from SSID parameters.", "DEBUG")

        # Append the entry to the operation list
        operation_list.append(ssid_entry)
        self.log("Appended processed SSID entry to the operation list.", "DEBUG")

    def verify_create_update_ssids_requirement(self, ssids, global_site_details):
        """
        Determines whether SSIDs need to be created, updated, or require no updates based on provided parameters.
        Args:
            ssids (list): A list of dictionaries containing the requested SSID parameters.
            global_site_details (dict): A dictionary containing details of the global site, including site name and ID.
        Returns:
            tuple: Three lists containing SSIDs to be created, updated, and not updated.
        """
        # Initialize lists to track SSIDs for creation, update, and no update
        create_ssids_list, update_ssids_list, no_update_ssids_list = [], [], []

        # Get Global Site ID and name
        global_site_name = global_site_details["site_name"]
        global_site_id = global_site_details["site_id"]
        self.log(
            "Global site details retrieved: Name={0}, ID={1}".format(
                global_site_name, global_site_id
            ),
            "DEBUG",
        )

        # Retrieve all existing SSIDs in the Global site
        get_ssids_params = self.get_ssids_params(global_site_id)
        existing_ssids = self.get_ssids(global_site_id, get_ssids_params)
        self.log("Existing SSIDs retrieved: {0}".format(existing_ssids), "DEBUG")

        # Iterate over each requested SSID to determine the operation required
        for ssid in ssids:
            requested_ssid_name = ssid.get("ssid_name")
            requested_ssid_type = ssid.get("ssid_type")
            site_specific_overrides = ssid.get("sites_specific_override_settings", [])

            self.log(
                "Processing SSID: '{0}' of type '{1}'.".format(
                    requested_ssid_name, requested_ssid_type
                ),
                "INFO",
            )

            # Prepare structures for create, update, and no-update operations
            self.log(
                "Preparing structures for create, update, and no-update operations.",
                "DEBUG",
            )
            create_ssid = {
                "global_ssid": {
                    "site_details": {"site_name": "Global", "site_id": global_site_id},
                    "ssid_params": {},
                },
                "site_specific_ssid": [],
            }
            update_ssid = {
                "global_ssid": {
                    "site_details": {"site_name": "Global", "site_id": global_site_id},
                    "ssid_params": {},
                },
                "site_specific_ssid": [],
            }
            no_update_ssid = {
                "global_ssid": {
                    "site_details": {"site_name": "Global", "site_id": global_site_id},
                    "ssid_params": {},
                },
                "site_specific_ssid": [],
            }

            # Retrieve and log SSID parameters
            l2_security = ssid.get("l2_security")
            l3_security = ssid.get("l3_security")

            # Update request and log modified parameters
            modified_requested_ssid = self.update_ssid_parameter_mappings(
                requested_ssid_name, requested_ssid_type, ssid
            )
            modified_requested_ssid["site_id"] = global_site_id
            self.log(
                "Modified parameters of the requested SSID: {0}".format(
                    modified_requested_ssid
                ),
                "DEBUG",
            )

            # Verify existence and need for update
            self.log(
                "Verifying if SSID: '{0}' exists in the Catalyst Center and if it needs an UPDATE.".format(
                    requested_ssid_name
                ),
                "INFO",
            )
            ssid_exists, update_required, update_ssid_settings, ssid_id = (
                self.compare_global_ssids(existing_ssids, modified_requested_ssid)
            )

            # Determine operation based on existence and update requirement
            if ssid_exists:
                if update_required:
                    self.log(
                        "SSID '{0}' exists globally and UPDATE operation is required.".format(
                            requested_ssid_name
                        ),
                        "INFO",
                    )
                    update_ssid["global_ssid"]["ssid_params"] = update_ssid_settings
                else:
                    self.log(
                        "SSID '{0}' exists globally but doesn't require an UPDATE.".format(
                            requested_ssid_name
                        ),
                        "INFO",
                    )
                    no_update_ssid["global_ssid"][
                        "ssid_params"
                    ] = modified_requested_ssid

                # Handle site-specific overrides
                if site_specific_overrides:
                    for site_override_settings in site_specific_overrides:
                        self.log(
                            "Processing Site Override Settings: {0}".format(
                                site_override_settings
                            ),
                            "DEBUG",
                        )
                        site_name_hierarchy = site_override_settings.get(
                            "site_name_hierarchy"
                        )
                        site_exists, site_id = self.get_site_id(site_name_hierarchy)
                        self.validate_site_name_hierarchy(
                            site_exists, site_id, site_name_hierarchy
                        )

                        site_details = {
                            "site_name": site_name_hierarchy,
                            "site_id": site_id,
                        }
                        modified_requested_site_specific_ssid = (
                            self.update_ssid_parameter_mappings(
                                requested_ssid_name,
                                requested_ssid_type,
                                site_override_settings,
                            )
                        )
                        self.log(
                            "Modified parameters of the requested SSID: {0}".format(
                                modified_requested_site_specific_ssid
                            ),
                            "DEBUG",
                        )

                        ssid_entry = {"site_details": site_details}

                        get_ssids_params = self.get_ssids_params(
                            site_id,
                            requested_ssid_name,
                            requested_ssid_type
                        )
                        existing_site_ssids = self.get_ssids(site_id, get_ssids_params)

                        ssid_exists, update_required, update_ssid_settings = (
                            self.compare_site_specific_ssids(
                                site_id,
                                requested_ssid_name,
                                requested_ssid_type,
                                existing_site_ssids,
                                modified_requested_site_specific_ssid,
                            )
                        )

                        # Determine site-specific operation
                        if ssid_exists:
                            if update_required:
                                self.log(
                                    "Site Specific SSID '{0}' exists for site '{1}' and UPDATE operation is required.".format(
                                        requested_ssid_name, site_name_hierarchy
                                    ),
                                    "INFO",
                                )
                                self.process_ssid_entry(
                                    ssid_entry,
                                    update_ssid_settings,
                                    site_id,
                                    ssid_id,
                                    update_ssid["site_specific_ssid"],
                                )
                            else:
                                self.log(
                                    "Site Specific SSID '{0}' exists for site '{1}' but doesn't require an UPDATE.".format(
                                        requested_ssid_name, site_name_hierarchy
                                    ),
                                    "INFO",
                                )
                                ssid_entry["ssid_params"] = (
                                    modified_requested_site_specific_ssid
                                )
                                no_update_ssid["site_specific_ssid"].append(ssid_entry)
                        else:
                            self.log(
                                "Site Specific SSID '{0}' does not exist for site '{1}' and CREATE operation is required.".format(
                                    requested_ssid_name, site_name_hierarchy
                                ),
                                "INFO",
                            )
                            self.process_ssid_entry(
                                ssid_entry,
                                modified_requested_site_specific_ssid,
                                site_id,
                                ssid_id,
                                update_ssid["site_specific_ssid"],
                            )

                        self.log(
                            "Site specific SSID entry for SSID: {0} is {1}".format(
                                requested_ssid_name, ssid_entry
                            ),
                            "INFO",
                        )

            else:
                self.log(
                    "SSID '{0}' does not exist globally. A create operation is required.".format(
                        requested_ssid_name
                    ),
                    "INFO",
                )
                create_ssid["global_ssid"]["ssid_params"] = modified_requested_ssid

                # Handle site-specific overrides for creation
                if site_specific_overrides:
                    for site_override_settings in site_specific_overrides:
                        site_name_hierarchy = site_override_settings.get(
                            "site_name_hierarchy"
                        )

                        site_exists, site_id = self.get_site_id(site_name_hierarchy)
                        self.validate_site_name_hierarchy(
                            site_exists, site_id, site_name_hierarchy
                        )

                        modified_requested_site_specific_ssid = (
                            self.update_ssid_parameter_mappings(
                                requested_ssid_name,
                                requested_ssid_type,
                                site_override_settings,
                            )
                        )
                        self.log(
                            "Modified parameters of the requested SSID: {0}".format(
                                modified_requested_site_specific_ssid
                            ),
                            "DEBUG",
                        )
                        site_details = {
                            "site_name": site_name_hierarchy,
                            "site_id": site_id,
                        }
                        ssid_entry = {"site_details": site_details}

                        self.log(
                            "SSID '{0}' for site '{1}'. A create operation is required.".format(
                                requested_ssid_name, site_name_hierarchy
                            ),
                            "INFO",
                        )
                        self.process_ssid_entry(
                            ssid_entry,
                            modified_requested_site_specific_ssid,
                            site_id,
                            "",
                            create_ssid["site_specific_ssid"],
                        )

            # Append the results to the respective lists
            if (
                create_ssid["global_ssid"]["ssid_params"]
                or create_ssid["site_specific_ssid"]
            ):
                self.log(
                    "SSID '{0}' added to the create list.".format(requested_ssid_name),
                    "DEBUG",
                )
                create_ssids_list.append(create_ssid)
            if (
                update_ssid["global_ssid"]["ssid_params"]
                or update_ssid["site_specific_ssid"]
            ):
                self.log(
                    "SSID '{0}' added to the update list.".format(requested_ssid_name),
                    "DEBUG",
                )
                update_ssids_list.append(update_ssid)
            if (
                no_update_ssid["global_ssid"]["ssid_params"]
                or no_update_ssid["site_specific_ssid"]
            ):
                self.log(
                    "SSID '{0}' added to the no-update list.".format(
                        requested_ssid_name
                    ),
                    "DEBUG",
                )
                no_update_ssids_list.append(no_update_ssid)

        self.log("Create SSIDs List: {0}".format(create_ssids_list), "INFO")
        self.log("Update SSIDs List: {0}".format(update_ssids_list), "INFO")
        self.log("No Update SSIDs List: {0}".format(no_update_ssids_list), "INFO")

        self.log("Completed processing all SSIDs.", "INFO")
        return create_ssids_list, update_ssids_list, no_update_ssids_list

    def verify_delete_ssids_requirement(self, ssids, global_site_details):
        """
        Verifies the requirement for deleting SSIDs based on global and site-specific settings.
        Args:
            ssids (list): A list of dictionaries containing SSID information for potential deletion.
            global_site_details (dict): A dictionary containing details of the global site, including site name and ID.
        Returns:
            list: A list of SSIDs marked for deletion, including their parameters.
        """
        # Initialize the list to hold SSIDs scheduled for deletion
        delete_ssids_list = []

        self.log("Starting verification of SSID deletions.", "DEBUG")

        # Get Global Site ID and name
        global_site_name = global_site_details["site_name"]
        global_site_id = global_site_details["site_id"]
        self.log(
            "Global site details retrieved: Name={0}, ID={1}".format(
                global_site_name, global_site_id
            ),
            "DEBUG",
        )

        # Retrieve all existing SSIDs in the Global site
        get_ssids_params = self.get_ssids_params(global_site_id)
        existing_global_ssids = self.get_ssids(global_site_id, get_ssids_params)
        self.log("Retrieved existing global SSIDs.", "DEBUG")

        # Iterate over each SSID to verify deletion requirements
        for index, ssid in enumerate(ssids):
            ssid_name = ssid.get("ssid_name")
            sites_specific_override_settings = ssid.get(
                "sites_specific_override_settings", []
            )

            # Check for global SSID deletion
            if not sites_specific_override_settings:
                self.log(
                    "Checking global SSID deletion for '{0}'.".format(ssid_name),
                    "DEBUG",
                )

                # Find the SSID to delete from the global SSIDs
                ssid_to_delete = next(
                    (
                        existing
                        for existing in existing_global_ssids
                        if existing.get("ssid") == ssid_name
                    ),
                    None,
                )
                if ssid_to_delete:
                    delete_entry = {
                        index: {
                            "ssid_name": ssid_name,
                            "site_name": global_site_name,
                            "delete_ssid_params": {
                                "site_id": global_site_id,
                                "id": ssid_to_delete.get("id"),
                                "remove_override_in_hierarchy": True,
                            },
                        }
                    }
                    delete_ssids_list.append(delete_entry)
                    self.log(
                        "Global SSID '{0}' marked for deletion.".format(ssid_name),
                        "INFO",
                    )
                else:
                    self.log(
                        "Global SSID '{0}' does not exist; deletion not required.".format(
                            ssid_name
                        ),
                        "INFO",
                    )

            # Check for site-specific SSID deletions
            for site_override in sites_specific_override_settings:
                site_name_hierarchy = site_override.get("site_name_hierarchy")
                remove_override_in_hierarchy = site_override.get(
                    "remove_override_in_hierarchy", False
                )

                # Validate the site existence and retrieve the site ID
                site_exists, site_id = self.get_site_id(site_name_hierarchy)
                self.validate_site_name_hierarchy(
                    site_exists, site_id, site_name_hierarchy
                )

                self.log(
                    "Checking site-specific SSID deletion for '{0}' in site '{1}'.".format(
                        ssid_name, site_name_hierarchy
                    ),
                    "DEBUG",
                )
                get_ssids_params = self.get_ssids_params(site_id, ssid_name)
                existing_site_ssids = self.get_ssids(site_id, get_ssids_params)

                # Find the SSID to delete from the site-specific SSIDs
                ssid_to_delete = next(
                    (
                        existing
                        for existing in existing_site_ssids
                        if existing.get("ssid") == ssid_name
                    ),
                    None,
                )
                if ssid_to_delete:
                    delete_entry = {
                        index: {
                            "ssid_name": ssid_name,
                            "site_name": site_name_hierarchy,
                            "delete_ssid_params": {
                                "site_id": site_id,
                                "id": ssid_to_delete.get("id"),
                                "remove_override_in_hierarchy": remove_override_in_hierarchy,
                            },
                        }
                    }
                    delete_ssids_list.append(delete_entry)
                    self.log(
                        "Site-specific SSID '{0}' in site '{1}' marked for deletion.".format(
                            ssid_name, site_name_hierarchy
                        ),
                        "INFO",
                    )
                else:
                    self.log(
                        "Site-specific SSID '{0}' does not exist in site '{1}'; deletion not required.".format(
                            ssid_name, site_name_hierarchy
                        ),
                        "INFO",
                    )

        # Return the list of SSIDs that need to be deleted
        return delete_ssids_list

    def create_ssid(self, create_ssid_params):
        """
        Initiates the creation of an SSID using the provided parameters.
        Args:
            create_ssid_params (dict): A dictionary containing parameters required for creating an SSID.
        Returns:
            dict: The response containing the task ID for the create operation.
        """
        self.log(
            "Initiating addition of SSID with parameters: {0}".format(
                create_ssid_params
            ),
            "INFO",
        )

        # Execute the API call to create the SSID and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "create_ssid", create_ssid_params
        )

    def update_ssid(self, update_ssid_params):
        """
        Initiates the update of an SSID using the provided parameters.
        Args:
            update_ssid_params (dict): A dictionary containing parameters required for updating an SSID.
        Returns:
            dict: The response containing the task ID for the update operation.
        """
        self.log(
            "Initiating update SSID with parameters: {0}".format(update_ssid_params),
            "INFO",
        )

        # Execute the API call to update the SSID and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_ssid", update_ssid_params
        )

    def update_or_override_ssid(self, update_or_override_ssid_params):
        """
        Initiates the update or override of a site-specific SSID using the provided parameters.
        Args:
            update_or_override_ssid_params (dict): A dictionary containing parameters for updating or overriding an SSID.
        Returns:
            dict: The response containing the task ID for the update or override operation.
        """
        self.log(
            "Initiating update/override site-specific SSID with parameters: {0}".format(
                update_or_override_ssid_params
            ),
            "INFO",
        )

        # Execute the API call to update or override the SSID and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_or_overridessid", update_or_override_ssid_params
        )

    def delete_ssid(self, delete_ssid_params):
        """
        Initiates the deletion of a site-specific SSID using the provided parameters.
        Args:
            delete_ssid_params (dict): A dictionary containing parameters required for deleting an SSID.
        Returns:
            dict: The response containing the task ID for the delete operation.
        """
        self.log(
            "Initiating delete site-specific SSID with parameters: {0}".format(
                delete_ssid_params
            ),
            "INFO",
        )

        # Execute the API call to delete the SSID and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "delete_ssid", delete_ssid_params
        )

    def get_create_ssid_task_status(self, task_id, task_name, ssid_name):
        """
        Retrieves and returns the status of the SSID creation task using the provided task ID.
        Args:
            task_id (str): The task ID for tracking the create operation.
            task_name (str): The name of the task being performed.
            ssid_name (str): The name of the SSID being created.
        Returns:
            str: The status of the task.
        """
        # Construct the message for successful task completion
        msg = "{0} operation has completed successfully for Global SSID: {1}.".format(
            task_name, ssid_name
        )

        # Retrieve and return the task status using the provided task ID
        self.get_task_status_from_tasks_by_id(
            task_id, task_name, msg
        ).check_return_status()
        return self.status

    def get_update_ssid_task_status(self, task_id, task_name, ssid_name):
        """
        Retrieves and returns the status of the SSID update task using the provided task ID.
        Args:
            task_id (str): The task ID for tracking the update operation.
            task_name (str): The name of the task being performed.
            ssid_name (str): The name of the SSID being updated.
        Returns:
            str: The status of the task.
        """
        # Construct the message for successful task completion
        msg = "{0} operation has completed successfully for Global SSID: {1}.".format(
            task_name, ssid_name
        )

        # Retrieve and return the task status using the provided task ID
        self.get_task_status_from_tasks_by_id(
            task_id, task_name, msg
        ).check_return_status()
        return self.status

    def get_update_or_override_ssid_task_status(self, task_id, task_name, ssid_name):
        """
        Retrieves and returns the status of the site-specific SSID update or override task using the provided task ID.
        Args:
            task_id (str): The task ID for tracking the update or override operation.
            task_name (str): The name of the task being performed.
            ssid_name (str): The name of the site-specific SSID being updated or overridden.
        Returns:
            str: The status of the task.
        """
        # Construct the message for successful task completion
        msg = "{0} operation has completed successfully for site-specific SSID: {1}.".format(
            task_name, ssid_name
        )

        # Retrieve and return the task status using the provided task ID
        self.get_task_status_from_tasks_by_id(
            task_id, task_name, msg
        ).check_return_status()
        return self.status

    def get_delete_ssid_task_status(self, task_id, task_name, ssid_name):
        """
        Retrieves and returns the status of the SSID deletion task using the provided task ID.
        Args:
            task_id (str): The task ID for tracking the delete operation.
            task_name (str): The name of the task being performed.
            ssid_name (str): The name of the SSID being deleted.
        Returns:
            str: The status of the task.
        """
        # Construct the message for successful task completion
        msg = "{0} operation has completed successfully for SSID: {1}.".format(
            task_name, ssid_name
        )

        # Retrieve and return the task status using the provided task ID
        self.get_task_status_from_tasks_by_id(
            task_id, task_name, msg
        ).check_return_status()
        return self.status

    def process_ssids_common(
        self, ssids_params, create_or_update_ssid, get_ssid_task_status, task_name
    ):
        """
        Processes SSIDs for the specified operation (create or update).
        Args:
            ssids_params (list): A list of dictionaries containing parameters for each SSID operation.
            create_or_update_ssid (function): The function to execute for creating or updating SSIDs.
            get_ssid_task_status (function): The function to retrieve the task status for the SSID operation.
            task_name (str): The name of the task being performed, For example, "Create SSID(s) Task".
        Returns:
            self: The current instance with the updated operation result and message.
        """
        # Initialize lists to track successful and failed SSIDs
        success_ssids = []
        failed_ssids = []
        # Initialize a dictionary to store operation messages
        msg = {}

        # Iterate over each SSID parameter set for processing
        for ssid_param in ssids_params:
            ssid_successful = True
            ssid_name = None
            ssid_id = None

            # Handle global SSID operation
            global_ssid = ssid_param.get("global_ssid", {})
            site_specific_ssids = ssid_param.get("site_specific_ssid")

            if global_ssid.get("ssid_params", {}):
                ssid_name = global_ssid["ssid_params"].get("ssid")
                self.log("Processing global SSID for '{0}'.".format(ssid_name), "INFO")
                ssid_params = global_ssid["ssid_params"]

                # Execute SSID operation (create or update) and get task status
                task_id = create_or_update_ssid(ssid_params)
                self.log(
                    "SSID Task ID for '{0}': {1}".format(ssid_name, task_id), "DEBUG"
                )
                status = get_ssid_task_status(task_id, task_name, ssid_name)

                if status != "success":
                    ssid_successful = False

            # Handle site-specific SSID operation
            if site_specific_ssids:
                for site_specific_ssid in site_specific_ssids:
                    update_params = site_specific_ssid["ssid_params"]
                    if update_params:
                        ssid_name = site_specific_ssid["ssid_name"] or ssid_name
                        self.log(
                            "Processing site-specific SSID for '{0}'.".format(
                                ssid_name
                            ),
                            "INFO",
                        )

                        # Check if SSID ID needs to be retrieved
                        if not update_params["id"]:
                            site_id = global_ssid["site_details"]["site_id"]
                            get_ssids_params = self.get_ssids_params(
                                site_id, ssid_name, ssid_params.get("wlanType")
                            )
                            existing_ssids = self.get_ssids(site_id, get_ssids_params)
                            for existing_ssid in existing_ssids:
                                if existing_ssid.get("ssid") == ssid_name:
                                    ssid_id = existing_ssid.get("id")
                                    update_params["id"] = ssid_id
                                    break

                        # Execute SSID operation (update or override) and get task status
                        task_id = self.update_or_override_ssid(update_params)
                        self.log(
                            "Update SSID Task ID for '{0}': {1}".format(
                                ssid_name, task_id
                            ),
                            "DEBUG",
                        )
                        status = self.get_update_or_override_ssid_task_status(
                            task_id, task_name, ssid_name
                        )

                        if status != "success":
                            ssid_successful = False

            # Track success or failure for the SSID
            if ssid_successful:
                success_ssids.append(ssid_name)
            else:
                failed_ssids.append(ssid_name)

        if success_ssids:
            self.log(
                "{0} succeeded for the following SSID(s): {1}".format(
                    task_name, success_ssids
                ),
                "INFO",
            )
            msg["{0} succeeded for the following SSID(s)".format(task_name)] = {
                "success_count": len(success_ssids),
                "successful_ssids": success_ssids,
            }

        if failed_ssids:
            self.log(
                "{0} failed for the following SSID(s): {1}".format(
                    task_name, failed_ssids
                ),
                "ERROR",
            )
            msg["{0} failed for the following SSID(s)".format(task_name)] = {
                "failed_count": len(failed_ssids),
                "failed_ssids": failed_ssids,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_ssids and failed_ssids:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_ssids:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_ssids:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def process_add_ssids(self, add_ssids_params):
        """
        Initiates the process to add SSIDs based on the provided parameters.
        Args:
            add_ssids_params (list): A list of dictionaries containing parameters for adding SSIDs.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for creating SSIDs
        task_name_create = "Create SSID(s) Task"
        self.log(
            "Starting the creation process for SSIDs with task name: {0}".format(
                task_name_create
            ),
            "INFO",
        )

        # Call the common processing function to add SSIDs
        return self.process_ssids_common(
            add_ssids_params,
            self.create_ssid,
            self.get_create_ssid_task_status,
            task_name_create,
        )

    def process_update_ssids(self, update_ssids_params):
        """
        Initiates the process to update SSIDs based on the provided parameters.
        Args:
            update_ssids_params (list): A list of dictionaries containing parameters for updating SSIDs.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for updating SSIDs
        task_name_update = "Update SSID(s) Task"
        self.log(
            "Starting the update process for SSIDs with task name: {0}".format(
                task_name_update
            ),
            "INFO",
        )

        # Call the common processing function to update SSIDs
        return self.process_ssids_common(
            update_ssids_params,
            self.update_ssid,
            self.get_update_ssid_task_status,
            task_name_update,
        )

    def process_delete_ssids(self, delete_ssids_params):
        """
        Processes the deletion of SSIDs based on the provided parameters.
        Args:
            delete_ssids_params (list): A list of dictionaries containing parameters for deleting SSIDs.
        Returns:
            self: Returns the instance with the updated operation result and message.
        """
        # Define the task name for deletion operations
        task_name = "Delete SSID(s) Task"
        # Initialize lists to track successful and failed SSID deletions
        failed_ssids = []
        success_ssids = []
        # Initialize a dictionary to store operation messages
        msg = {}

        # Iterate over each SSID parameter set for deletion
        for delete_ssid_param in delete_ssids_params:
            # Each item in the list is a dictionary with a single key-value pair
            for index, ssid_data in delete_ssid_param.items():
                ssid_name = ssid_data.get("ssid_name")
                site_name = ssid_data.get("site_name")
                delete_params = ssid_data.get("delete_ssid_params")

                self.log(
                    "Processing - index: {0}, SSID: {1}, site: {2}".format(
                        index, ssid_name, site_name
                    ),
                    "DEBUG",
                )

                # Perform the deletion operation and retrieve the task ID
                task_id = self.delete_ssid(delete_params)
                self.log(
                    "Task ID for SSID '{0}': {1}".format(ssid_name, task_id), "DEBUG"
                )

                # Check the status of the deletion task
                status = self.get_delete_ssid_task_status(task_id, task_name, ssid_name)

                # Categorize the SSID based on the task status
                if status == "success":
                    success_ssids.append(
                        {
                            "ssid_name": ssid_name,
                            "site_name": site_name,
                            "remove_override_in_hierarchy": delete_params.get(
                                "remove_override_in_hierarchy"
                            ),
                        }
                    )
                    self.log("SSID '{0}' deletion succeeded.".format(ssid_name), "INFO")
                else:
                    failed_ssids.append(ssid_name)
                    self.log("SSID '{0}' deletion failed.".format(ssid_name), "ERROR")

        # Set the final message for successful operations
        if success_ssids:
            self.log(
                "{0} succeeded for the following SSID(s): {1}".format(
                    task_name, ", ".join(ssid["ssid_name"] for ssid in success_ssids)
                ),
                "INFO",
            )
            msg["{0} succeeded for the following SSID(s)".format(task_name)] = {
                "success_count": len(success_ssids),
                "successful_ssids": success_ssids,
            }

        # Set the final message for failed operations
        if failed_ssids:
            self.log(
                "{0} failed for the following SSID(s): {1}".format(
                    task_name, ", ".join(failed_ssids)
                ),
                "ERROR",
            )
            msg["{0} failed for the following SSID(s)".format(task_name)] = {
                "failed_count": len(failed_ssids),
                "failed_ssids": failed_ssids,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_ssids and failed_ssids:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_ssids:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_ssids:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def verify_add_ssids_operation(self, add_ssids_params):
        """
        Verifies the success of the ADD SSIDs operation.
        Args:
            add_ssids_params (list): A list of dictionaries containing parameters for adding SSIDs.
        Returns:
            tuple: A tuple containing two lists - successfully created SSIDs and failed SSIDs.
        """
        # Extract global site details
        global_site_details = self.have.get("global_site_details")
        global_site_id = global_site_details.get("site_id")
        self.log("Retrieved global site ID: {0}".format(global_site_id), "DEBUG")

        # Retrieve all existing SSIDs in the Global site
        get_ssids_params = self.get_ssids_params(global_site_id)
        existing_ssids = self.get_ssids(global_site_id, get_ssids_params)
        self.log(
            "Existing SSIDs in the Global site: {0}".format(existing_ssids), "INFO"
        )

        # Extract existing SSID names for comparison
        existing_ssid_names = {ssid.get("ssid") for ssid in existing_ssids}
        self.log("Extracted existing SSID names for comparison.", "DEBUG")

        # Initialize lists to track created and failed SSIDs
        created_ssids = []
        failed_ssids = []

        # Iterate over the global SSIDs in add_ssids_params to verify creation
        for ssid_param in add_ssids_params:
            ssid_name = ssid_param["global_ssid"]["ssid_params"].get("ssid")
            self.log("Verifying creation of SSID '{0}'.".format(ssid_name), "DEBUG")

            # Check if the SSID was successfully created
            if ssid_name in existing_ssid_names:
                self.log(
                    "SSID '{0}' was successfully created.".format(ssid_name), "INFO"
                )
                created_ssids.append(ssid_name)
            else:
                self.log(
                    "SSID '{0}' was not found in the Global site; creation may have failed.".format(
                        ssid_name
                    ),
                    "WARNING",
                )
                failed_ssids.append(ssid_name)

        if failed_ssids:
            self.log(
                "The ADD SSID(s) operation may not have been successful since some SSIDs were not successfully created: {0}".format(
                    ", ".join(failed_ssids)
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of ADD SSID(s) operation for parameters: {0}.".format(
                    add_ssids_params
                ),
                "INFO",
            )

        # Return lists of created and failed SSIDs
        return created_ssids, failed_ssids

    def verify_update_ssids_operation(self, update_ssids_params):
        """
        Verifies the update operation for SSIDs based on the provided update parameters.
        Args:
            update_ssids_params (list): A list of dictionaries containing parameters for updating SSIDs.
        """
        # Retrieve global site details
        global_site_details = self.have.get("global_site_details")
        global_site_id = global_site_details.get("site_id")
        self.log("Retrieved global site ID: {0}".format(global_site_id), "DEBUG")

        # Retrieve all existing SSIDs in the global site
        existing_global_ssids = self.get_ssids(
            global_site_id, self.get_ssids_params(global_site_id)
        )
        self.log(
            "Existing SSIDs in the Global site: {0}".format(existing_global_ssids),
            "INFO",
        )

        # Function to compare SSID parameters
        def compare_ssid_params(existing_params, requested_params):
            ignored_keys = {"site_id", "id", "passphrase", "active_validation"}
            for key, requested_value in requested_params.items():
                if key in ignored_keys:
                    continue

                existing_value = existing_params.get(key)

                if key == "multiPSKSettings":
                    # Compare each setting in multiPSKSettings while ignoring the passphrase
                    if not compare_multipsk_settings(existing_value, requested_value):
                        return False
                elif existing_value != requested_value:
                    self.log(
                        "Mismatch for key '{0}': existing value '{1}' vs requested value '{2}'.".format(
                            key, existing_value, requested_value
                        ),
                        "WARNING",
                    )
                    return False

            return True

        def compare_multipsk_settings(existing_settings, requested_settings):
            if not isinstance(existing_settings, list) or not isinstance(
                requested_settings, list
            ):
                return False

            for req_setting in requested_settings:
                # Find matching setting by keys other than passphrase
                match = next(
                    (
                        ex_setting
                        for ex_setting in existing_settings
                        if all(
                            k in ex_setting and ex_setting[k] == v
                            for k, v in req_setting.items()
                            if k != "passphrase"
                        )
                    ),
                    None,
                )

                if not match:
                    self.log(
                        "Mismatch in multiPSKSettings: no matching entry found for '{0}'.".format(
                            req_setting
                        ),
                        "WARNING",
                    )
                    return False

            return True

        # Lists to track failed verifications
        failed_verifications = []

        # Iterate over the SSIDs in the update parameters
        all_updates_verified = True
        for ssid_param in update_ssids_params:
            if "global_ssid" in ssid_param:
                global_ssid_params = ssid_param["global_ssid"].get("ssid_params", {})
                ssid_name = global_ssid_params.get("ssid")
                ssid_type = global_ssid_params.get("ssid_type")
                self.log("Verifying global SSID: {0}".format(ssid_name), "DEBUG")

                if global_ssid_params:
                    # Check if SSID is in the existing global SSIDs
                    existing_global_ssid = next(
                        (
                            ssid
                            for ssid in existing_global_ssids
                            if ssid.get("ssid") == ssid_name
                            and ssid.get("wlanType") == ssid_type
                        ),
                        None,
                    )
                    if existing_global_ssid:
                        if not compare_ssid_params(
                            existing_global_ssid, global_ssid_params
                        ):
                            all_updates_verified = False
                            failed_verifications.append(
                                {"ssid_name": ssid_name, "site": "Global"}
                            )
                            continue

            # Verify site-specific SSID updates
            for site_specific in ssid_param.get("site_specific_ssid", []):
                site_specific_params = site_specific.get("ssid_params", {})
                ssid_name = site_specific.get("ssid_name")
                ssid_type = site_specific.get("ssid_type")
                site_details = site_specific.get("site_details", {})
                site_id = site_details.get("site_id")
                site_name = site_details.get("site_name")
                self.log(
                    "Verifying site-specific SSID: {0}, Type: {1} for site: {2}".format(
                        ssid_name, ssid_type, site_name
                    ),
                    "DEBUG",
                )

                if site_specific_params and site_id:
                    # Retrieve existing SSIDs for the site
                    existing_site_ssids = self.get_ssids(
                        site_id, self.get_ssids_params(site_id)
                    )
                    existing_site_ssid = next(
                        (
                            ssid
                            for ssid in existing_site_ssids
                            if ssid.get("ssid") == ssid_name
                            and ssid.get("wlanType") == ssid_type
                        ),
                        None,
                    )

                    if existing_site_ssid:
                        if not compare_ssid_params(
                            existing_site_ssid, site_specific_params
                        ):
                            all_updates_verified = False
                            failed_verifications.append(
                                {"ssid_name": ssid_name, "site": site_name}
                            )

        if all_updates_verified:
            self.log(
                "Successfully verified the update SSID(s) operation for the following SSID(s): {0}.".format(
                    update_ssids_params
                ),
                "INFO",
            )
        else:
            self.log(
                "The UPDATE SSID(s) operation may not have been successful. The following SSIDs failed verification: {0}.".format(
                    ", ".join(
                        "{0} at {1}".format(failure["ssid_name"], failure["site"])
                        for failure in failed_verifications
                    )
                ),
                "ERROR",
            )

    def verify_delete_ssids_operation(self, delete_ssids_params):
        """
        Verifies the delete operation for SSIDs based on the provided delete parameters.
        Args:
            delete_ssids_params (list): A list of dictionaries containing parameters for deleting SSIDs.
        """
        # Retrieve global site details
        global_site_details = self.have.get("global_site_details")
        global_site_id = global_site_details.get("site_id")
        self.log("Retrieved global site ID: {0}".format(global_site_id), "DEBUG")

        # Retrieve all existing SSIDs in the global site
        existing_global_ssids = self.get_ssids(
            global_site_id, self.get_ssids_params(global_site_id)
        )
        existing_global_ssid_names = {
            ssid.get("ssid") for ssid in existing_global_ssids
        }
        self.log(
            "Existing SSIDs in global site: {0}".format(existing_global_ssid_names),
            "DEBUG",
        )

        # Initialize lists to track results of deletions
        successful_deletions = []
        failed_deletions = []

        # Iterate over the delete SSIDs parameters
        for ssid_param in delete_ssids_params:
            for key, details in ssid_param.items():
                ssid_name = details.get("ssid_name")
                site_name = details.get("site_name")
                self.log(
                    "Verifying deletion for SSID: {0} in site: {1}".format(
                        ssid_name, site_name
                    ),
                    "DEBUG",
                )

                # Only verify deletion for global site SSIDs
                if site_name == "Global":
                    if ssid_name not in existing_global_ssid_names:
                        self.log(
                            "SSID '{0}' successfully deleted from global site.".format(
                                ssid_name
                            ),
                            "INFO",
                        )
                        successful_deletions.append(ssid_name)
                    else:
                        self.log(
                            "SSID '{0}' still exists in global site; deletion failed.".format(
                                ssid_name
                            ),
                            "WARNING",
                        )
                        failed_deletions.append(ssid_name)

        if not failed_deletions:
            self.log(
                "Verified the success of DELETE SSID(s) operation for parameters: {0}.".format(
                    delete_ssids_params
                ),
                "INFO",
            )
        else:
            self.log(
                "The DELETE SSID(s) operation may not have been successful since some SSIDs failed to be deleted from the global site: {0}".format(
                    ", ".join(failed_deletions)
                ),
                "ERROR",
            )

    def get_interfaces_params(self, interface_name=None, vlan_id=None):
        """
        Generates the parameters for retrieving interfaces, mapping optional user parameters
        to the API's expected parameter names.
        Args:
            interface_name (str, optional): The name of the interface to filter the retrieval.
            vlan_id (int, optional): The VLAN ID to filter the retrieval.
        Returns:
            dict: A dictionary of parameters for the API call, or an empty dictionary if no parameters are provided.
        """
        # Initialize an empty dictionary to hold the parameters for the API call
        get_interfaces_params = {}
        self.log("Initialized parameters dictionary for API call.", "DEBUG")

        # Map the user-provided interface name to the expected API parameter
        if interface_name:
            get_interfaces_params["interfaceName"] = interface_name
            self.log(
                "Mapped 'interface_name' to 'interfaceName' with value: {0}".format(
                    interface_name
                ),
                "DEBUG",
            )

        # Map the user-provided VLAN ID to the expected API parameter
        if vlan_id:
            get_interfaces_params["vlanId"] = vlan_id
            self.log(
                "Mapped 'vlan_id' to 'vlanId' with value: {0}".format(vlan_id), "DEBUG"
            )

        # Return the constructed parameters dictionary
        self.log(
            "Constructed get_interfaces_params: {0}".format(get_interfaces_params),
            "DEBUG",
        )
        return get_interfaces_params

    def get_interfaces(self, get_interfaces_params):
        """
        Retrieves interface details using pagination.
        Args:
            get_interfaces_params (dict, optional): Parameters for filtering the interfaces. Defaults to an empty dictionary.
        Returns:
            list: A list of dictionaries containing details of interfaces based on the filtering parameters.
        """
        self.log(
            "Retrieving interfaces with parameters: {0}".format(get_interfaces_params),
            "INFO",
        )

        # Execute the paginated API call to retrieve interfaces
        return self.execute_get_with_pagination(
            "wireless", "get_interfaces", get_interfaces_params
        )

    def create_interface(self, create_interface_params):
        """
        Initiates the creation of an interface using the provided parameters.
        Args:
            create_interface_params (dict): A dictionary containing parameters required for creating an interface.
        Returns:
            dict: The response containing the task ID for the create operation.
        """
        self.log(
            "Initiating addition of interface with parameters: {0}".format(
                create_interface_params
            ),
            "INFO",
        )

        # Execute the API call to create the interface and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "create_interface", create_interface_params
        )

    def update_interface(self, update_interface_params):
        """
        Initiates the update of an interface using the provided parameters.
        Args:
            update_interface_params (dict): A dictionary containing parameters required for updating an interface.
        Returns:
            dict: The response containing the task ID for the update operation.
        """
        self.log(
            "Initiating update of interface with parameters: {0}".format(
                update_interface_params
            ),
            "INFO",
        )

        # Execute the API call to update the interface and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_interface", update_interface_params
        )

    def delete_interface(self, delete_interface_params):
        """
        Initiates the deletion of an interface using the provided parameters.
        Args:
            delete_interface_params (dict): A dictionary containing parameters required for deleting an interface.
        Returns:
            dict: The response containing the task ID for the delete operation.
        """
        self.log(
            "Initiating deletion of interface with parameters: {0}".format(
                delete_interface_params
            ),
            "INFO",
        )

        # Execute the API call to delete the interface and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "delete_interface", delete_interface_params
        )

    def verify_create_update_interfaces_requirement(self, interfaces):
        """
        Determines whether interfaces need to be created, updated, or require no updates.
        Args:
            interfaces (list): A list of dictionaries containing the requested interface parameters.
        Returns:
            tuple: Three lists containing interfaces to be created, updated, and not updated.
        """
        # Retrieve all existing interfaces
        existing_interfaces = self.get_interfaces(get_interfaces_params={})
        self.log("Retrieved existing interfaces.", "DEBUG")

        self.log("Existing Interfaces: {0}".format(existing_interfaces), "DEBUG")
        self.log("Requested Interfaces: {0}".format(interfaces), "DEBUG")

        # Initialize lists to store interfaces that need to be created, updated, or not changed
        create_interfaces = []
        update_interfaces = []
        no_update_interfaces = []

        # Convert the requested interfaces to a dictionary for quick lookup by interface name
        requested_interfaces_dict = {
            interface["interface_name"]: interface for interface in interfaces
        }
        self.log(
            "Converted requested interfaces to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over existing interfaces to find matches and differences
        for existing_interface in existing_interfaces:
            interface_name = existing_interface["interfaceName"]
            vlan_id = existing_interface["vlanId"]
            self.log(
                "Evaluating existing interface: {0}, VLAN ID: {1}".format(
                    interface_name, vlan_id
                ),
                "DEBUG",
            )

            # If the interface exists in both, compare fields
            if interface_name in requested_interfaces_dict:
                requested_interface = requested_interfaces_dict[interface_name]
                requested_vlan_id = requested_interface.get("vlan_id")
                self.log(
                    "Comparing requested interface '{0}' with existing interface.".format(
                        interface_name
                    ),
                    "DEBUG",
                )

                # Check for differences
                if vlan_id != requested_vlan_id:
                    # Add the requested interface with the ID from the existing interface
                    updated_interface = requested_interface.copy()
                    updated_interface["id"] = existing_interface.get("id")
                    update_interfaces.append(updated_interface)
                    self.log(
                        "Interface '{0}' marked for update.".format(interface_name),
                        "DEBUG",
                    )
                else:
                    # If there's no difference, add to no_update_interfaces
                    no_update_interfaces.append(existing_interface)
                    self.log(
                        "Interface '{0}' requires no updates.".format(interface_name),
                        "DEBUG",
                    )

                # Remove the processed interface from the dictionary
                del requested_interfaces_dict[interface_name]

        # Remaining items in requested_interfaces_dict are new interfaces to be created
        create_interfaces.extend(requested_interfaces_dict.values())
        self.log("Identified new interfaces to be created.", "DEBUG")

        self.log(
            "Interfaces that need to be CREATED: {0} - {1}".format(
                len(create_interfaces), create_interfaces
            ),
            "DEBUG",
        )
        self.log(
            "Interfaces that need to be UPDATED: {0} - {1}".format(
                len(update_interfaces), update_interfaces
            ),
            "DEBUG",
        )
        self.log(
            "Interfaces that DON'T NEED UPDATES: {0} - {1}".format(
                len(no_update_interfaces), no_update_interfaces
            ),
            "DEBUG",
        )

        # Calculate total interfaces processed and check against requested interfaces
        total_interfaces_processed = (
            len(create_interfaces) + len(update_interfaces) + len(no_update_interfaces)
        )

        if total_interfaces_processed == len(interfaces):
            self.log(
                "Match in total counts: Processed={0}, Requested={1}.".format(
                    total_interfaces_processed, len(interfaces)
                ),
                "DEBUG",
            )
        else:
            self.log(
                "Mismatch in total counts: Processed={0}, Requested={1}.".format(
                    total_interfaces_processed, len(interfaces)
                ),
                "ERROR",
            )

        # Return the categorized interfaces
        return create_interfaces, update_interfaces, no_update_interfaces

    def verify_delete_interfaces_requirement(self, interfaces):
        """
        Determines whether interfaces need to be deleted based on the requested parameters.
        Args:
            interfaces (list): A list of dictionaries containing the requested interface parameters for deletion.
        Returns:
            list: A list of interfaces that need to be deleted, including their IDs.
        """
        # Initialize the list to hold interfaces scheduled for deletion
        delete_interfaces_list = []

        self.log("Starting verification of interfaces for deletion.", "INFO")

        # Retrieve all existing interfaces
        existing_interfaces = self.get_interfaces(get_interfaces_params={})
        self.log("Existing Interfaces: {0}".format(existing_interfaces), "DEBUG")

        # Convert existing interfaces to a dictionary for quick lookup by interface name
        existing_interfaces_dict = {
            interface["interfaceName"]: interface for interface in existing_interfaces
        }
        self.log(
            "Converted existing interfaces to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested interfaces
        for requested_interface in interfaces:
            interface_name = requested_interface.get("interface_name")
            self.log(
                "Checking deletion requirement for interface: {0}".format(
                    interface_name
                ),
                "DEBUG",
            )

            # Check if the interface exists in the existing interfaces
            if interface_name in existing_interfaces_dict:
                # Add the requested interface with the ID from the existing interface
                existing_interface = existing_interfaces_dict[interface_name]
                interface_to_delete = requested_interface.copy()
                interface_to_delete["id"] = existing_interface.get("id")
                delete_interfaces_list.append(interface_to_delete)
                self.log(
                    "Interface '{0}' scheduled for deletion.".format(interface_name),
                    "INFO",
                )
            else:
                self.log(
                    "Deletion not required for interface '{0}'. It does not exist.".format(
                        interface_name
                    ),
                    "INFO",
                )

        self.log(
            "Interfaces scheduled for deletion: {0} - {1}".format(
                len(delete_interfaces_list), delete_interfaces_list
            ),
            "DEBUG",
        )

        # Return the list of interfaces that need to be deleted
        return delete_interfaces_list

    def map_interface_params(self, interfaces):
        """
        Maps the parameters of each interface to the required format for API calls.
        Args:
            interfaces (list): A list of dictionaries containing interface parameters.
        Returns:
            list: A list of dictionaries with the parameters mapped to the required format.
        """
        # Initialize an empty list to store the mapped interfaces
        mapped_interfaces = []

        # Check if the interfaces list is empty and return the empty mapped list
        if not interfaces:
            self.log("No interfaces provided for mapping.", "DEBUG")
            return mapped_interfaces

        # Iterate over each interface to perform mappings
        for interface in interfaces:
            mapped_interface = {}
            self.log("Mapping interface: {0}".format(interface), "DEBUG")

            # Map each parameter to the required format
            if "interface_name" in interface:
                mapped_interface["interfaceName"] = interface["interface_name"]
                self.log(
                    "Mapped 'interface_name' to 'interfaceName': {0}".format(
                        interface["interface_name"]
                    ),
                    "DEBUG",
                )

            if "vlan_id" in interface:
                mapped_interface["vlanId"] = interface["vlan_id"]
                self.log(
                    "Mapped 'vlan_id' to 'vlanId': {0}".format(interface["vlan_id"]),
                    "DEBUG",
                )

            # Retain the id if it exists in the interface
            if "id" in interface:
                mapped_interface["id"] = interface["id"]
                self.log("Retained 'id': {0}".format(interface["id"]), "DEBUG")

            # Add the mapped interface to the list of mapped interfaces
            mapped_interfaces.append(mapped_interface)
            self.log("Added mapped interface: {0}".format(mapped_interface), "DEBUG")

        self.log("Mapped Interfaces: {0}".format(mapped_interfaces), "DEBUG")

        # Return the list of mapped interfaces
        return mapped_interfaces

    def process_interfaces_common(
        self, interfaces_params, create_or_update_or_delete_interface, task_name
    ):
        """
        Processes the interfaces for the specified operation (create, update, delete).
        Args:
            interfaces_params (list): A list of dictionaries containing parameters for each interface operation.
            create_or_update_or_delete_interface (function): The function to execute for each interface operation.
            task_name (str): The name of the task being performed, For example, "Create Interface(s) Task".
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Initialize lists to track successful and failed interface operations
        failed_interfaces = []
        success_interfaces = []
        msg = {}

        # Iterate over each interface parameter set for processing
        for interface in interfaces_params:
            interface_name = interface.get("interfaceName")
            self.log("Processing interface: {0}".format(interface_name), "DEBUG")

            # Prepare parameters for the operation
            if create_or_update_or_delete_interface == self.delete_interface:
                # For delete operation, extract only the id
                operation_params = {"id": interface.get("id")}
                self.log("Prepared parameters for delete operation.", "DEBUG")
            else:
                # For create or update operations, use the entire interface data
                operation_params = interface
                self.log("Prepared parameters for create/update operation.", "DEBUG")

            # Execute the operation and retrieve the task ID
            task_id = create_or_update_or_delete_interface(operation_params)
            self.log(
                "Task ID for interface '{0}': {1}".format(interface_name, task_id),
                "DEBUG",
            )

            # Check the status of the operation
            operation_msg = "{0} operation has completed successfully for interface_name: {1}.".format(
                task_name, interface_name
            )
            self.get_task_status_from_tasks_by_id(
                task_id, task_name, operation_msg
            ).check_return_status()

            # Determine if the operation was successful and categorize accordingly
            if self.status == "success":
                success_interfaces.append(interface_name)
                self.log(
                    "Interface '{0}' processed successfully.".format(interface_name),
                    "INFO",
                )
            else:
                failed_interfaces.append(interface_name)
                self.log(
                    "Interface '{0}' failed to process.".format(interface_name), "ERROR"
                )

        if success_interfaces:
            self.log(
                "{0} succeeded for the following interface(s): {1}".format(
                    task_name, ", ".join(success_interfaces)
                ),
                "INFO",
            )
            msg["{0} succeeded for the following interface(s)".format(task_name)] = {
                "success_count": len(success_interfaces),
                "successful_interfaces": success_interfaces,
            }

        if failed_interfaces:
            self.log(
                "{0} failed for the following interface(s): {1}".format(
                    task_name, ", ".join(failed_interfaces)
                ),
                "ERROR",
            )
            msg["{0} failed for the following interface(s)".format(task_name)] = {
                "failed_count": len(failed_interfaces),
                "failed_interfaces": failed_interfaces,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_interfaces and failed_interfaces:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_interfaces:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_interfaces:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def process_add_interfaces(self, add_interfaces_params):
        """
        Initiates the process to add interfaces.
        Args:
            add_interfaces_params (list): A list of dictionaries containing parameters for each interface to be added.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for creating interfaces
        task_name_create = "Create Interface(s) Task"
        self.log(
            "Starting the creation process for interfaces with task name: {0}".format(
                task_name_create
            ),
            "INFO",
        )

        # Call the common processing function to add interfaces
        return self.process_interfaces_common(
            add_interfaces_params, self.create_interface, task_name_create
        )

    def process_update_interfaces(self, update_interfaces_params):
        """
        Initiates the process to update interfaces.
        Args:
            update_interfaces_params (list): A list of dictionaries containing parameters for each interface to be updated.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for updating interfaces
        task_name_update = "Update Interface(s) Task"
        self.log(
            "Starting the update process for interfaces with task name: {0}".format(
                task_name_update
            ),
            "INFO",
        )

        # Call the common processing function to update interfaces
        return self.process_interfaces_common(
            update_interfaces_params, self.update_interface, task_name_update
        )

    def process_delete_interfaces(self, delete_interfaces_params):
        """
        Initiates the process to delete interfaces.
        Args:
            delete_interfaces_params (list): A list of dictionaries containing parameters for each interface to be deleted.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for deleting interfaces
        task_name_delete = "Delete Interface(s) Task"
        self.log(
            "Starting the deletion process for interfaces with task name: {0}".format(
                task_name_delete
            ),
            "INFO",
        )

        # Call the common processing function to delete interfaces
        return self.process_interfaces_common(
            delete_interfaces_params, self.delete_interface, task_name_delete
        )

    def verify_add_interfaces_operation(self, add_interfaces_params):
        """
        Verifies whether the specified interfaces have been successfully added.
        Args:
            add_interfaces_params (list): A list of dictionaries containing parameters for each interface to be added.
        """
        # Retrieve all existing interfaces
        existing_interfaces = self.get_interfaces(get_interfaces_params={})
        # Create a set of existing interface names for quick lookup
        existing_interface_names = {
            interface["interfaceName"] for interface in existing_interfaces
        }
        self.log("Retrieved existing interfaces.", "DEBUG")

        self.log(
            "State after performing ADD Interface operation: {0}".format(
                existing_interface_names
            ),
            "INFO",
        )
        self.log(
            "Interfaces requested to be added: {0}".format(add_interfaces_params),
            "INFO",
        )

        # Initialize a list to track interfaces that failed to add
        failed_interfaces = []

        # Iterate over the requested interfaces to verify addition
        for interface in add_interfaces_params:
            interface_name = interface.get("interfaceName")
            self.log(
                "Verifying addition for interface: {0}".format(interface_name), "DEBUG"
            )

            # Check if the interface exists in the current state
            if interface_name not in existing_interface_names:
                failed_interfaces.append(interface_name)
                self.log(
                    "Add operation failed for interface '{0}'. It does not exist in the current state.".format(
                        interface_name
                    ),
                    "WARNING",
                )

        if failed_interfaces:
            self.log(
                "The ADD operation may not have been successful since some interfaces were not successfully created: {0}".format(
                    failed_interfaces
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of ADD interface(s) operation for parameters: {0}.".format(
                    add_interfaces_params
                ),
                "INFO",
            )

    def verify_update_interfaces_operation(self, update_interfaces_params):
        """
        Verifies whether the specified interfaces have been successfully updated.
        Args:
            update_interfaces_params (list): A list of dictionaries containing parameters for each interface to be updated.
        """
        # Retrieve all existing interfaces
        existing_interfaces = self.get_interfaces(get_interfaces_params={})
        # Create a dictionary of existing interfaces for quick lookup by interface name and VLAN ID
        existing_interfaces_dict = {
            (interface["interfaceName"], interface["vlanId"]): interface
            for interface in existing_interfaces
        }
        self.log(
            "Retrieved existing interfaces and created lookup dictionary.", "DEBUG"
        )

        self.log(
            "State after performing UPDATE Interface operation: {0}".format(
                existing_interfaces_dict
            ),
            "INFO",
        )
        self.log(
            "Interfaces requested to be updated: {0}".format(update_interfaces_params),
            "INFO",
        )

        # Initialize a list to track interfaces that failed to update
        failed_interfaces = []

        # Iterate over the requested interfaces to verify updates
        for interface in update_interfaces_params:
            interface_name = interface.get("interfaceName")
            vlan_id = interface.get("vlanId")
            self.log(
                "Verifying update for interface: {0}, VLAN ID: {1}".format(
                    interface_name, vlan_id
                ),
                "DEBUG",
            )

            # Check if the interface with the specified VLAN ID exists in the current state
            if (interface_name, vlan_id) not in existing_interfaces_dict:
                failed_interfaces.append(interface_name)
                self.log(
                    "Update operation failed for interface '{0}'. It was not found with the specified VLAN ID.".format(
                        interface_name
                    ),
                    "WARNING",
                )

        if failed_interfaces:
            self.log(
                """The UPDATE operation may not have been successful for the following interfaces: {0}.
                They were not found with the specified parameters.""".format(
                    failed_interfaces
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of UPDATE interfaces operation for parameters: {0}.".format(
                    update_interfaces_params
                ),
                "INFO",
            )

    def verify_delete_interfaces_operation(self, delete_interfaces_params):
        """
        Verifies whether the specified interfaces have been successfully deleted.
        Args:
            delete_interfaces_params (list): A list of dictionaries containing parameters for each interface to be deleted.
        """
        # Retrieve all existing interfaces
        existing_interfaces = self.get_interfaces(get_interfaces_params={})
        # Create a set of existing interface names for quick lookup
        existing_interface_names = {
            interface["interfaceName"] for interface in existing_interfaces
        }
        self.log("Retrieved existing interfaces.", "DEBUG")

        self.log(
            "State after performing DELETE Interface operation: {0}".format(
                existing_interface_names
            ),
            "INFO",
        )
        self.log(
            "Interfaces requested to be deleted: {0}".format(delete_interfaces_params),
            "INFO",
        )

        # Initialize a list to track interfaces that failed deletion
        failed_interfaces = []

        # Iterate over the requested interfaces to verify deletion
        for interface in delete_interfaces_params:
            interface_name = interface.get("interfaceName")
            self.log(
                "Verifying deletion for interface: {0}".format(interface_name), "DEBUG"
            )

            # Check if the interface still exists in the current state
            if interface_name in existing_interface_names:
                failed_interfaces.append(interface_name)
                self.log(
                    "Delete operation failed for interface '{0}'. It still exists in the current state.".format(
                        interface_name
                    ),
                    "WARNING",
                )

        if failed_interfaces:
            self.log(
                "The DELETE Interface(s) operation may not have been successful since some interfaces still exist: {0}.".format(
                    failed_interfaces
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of DELETE Interface(s) operation for all requested parameters: {0}.".format(
                    delete_interfaces_params
                ),
                "INFO",
            )

    def get_power_profiles_params(self, power_profile_name=None):
        """
        Generates the parameters for retrieving power profiles, mapping optional user parameters
        to the API's expected parameter names.
        Args:
            power_profile_name (str, optional): The name of the power profile to filter the retrieval.
        Returns:
            dict: A dictionary of parameters for the API call, or an empty dictionary if no parameters are provided.
        """
        # Initialize an empty dictionary to hold the parameters for the API call
        get_power_profiles_params = {}
        self.log("Initialized parameters dictionary for API call.", "DEBUG")

        # Map the user-provided power profile name to the expected API parameter
        if power_profile_name:
            get_power_profiles_params["profile_name"] = power_profile_name
            self.log(
                "Mapped 'power_profile_name' to 'profile_name' with value: {0}".format(
                    power_profile_name
                ),
                "DEBUG",
            )
        else:
            self.log(
                "No specific power profile name provided. Returning empty parameters.",
                "DEBUG",
            )

        # Return the constructed parameters dictionary
        self.log(
            "Constructed get_power_profiles_params: {0}".format(
                get_power_profiles_params
            ),
            "DEBUG",
        )
        return get_power_profiles_params

    def get_power_profiles(self, get_power_profiles_params):
        """
        Retrieves power profile details using pagination.
        Args:
            get_power_profiles_params (dict): Parameters for filtering the power profiles.
        Returns:
            list: A list of dictionaries containing details of power profiles based on the filtering parameters.
        """
        # Execute the paginated API call to retrieve power profiles
        self.log("Executing paginated API call to retrieve power profiles.", "DEBUG")
        return self.execute_get_with_pagination(
            "wireless", "get_power_profiles", get_power_profiles_params
        )

    def update_power_profiles_with_defaults(self, power_profiles):
        """
        Updates each power profile's rules with default values based on the interface type.
        Args:
            power_profiles (list): A list of dictionaries containing power profile parameters.
        Returns:
            list: A list of power profiles with rules updated with default values.
        """
        # Iterate over each power profile to update rules with defaults
        for profile in power_profiles:
            rules = profile.get("rules", [])
            self.log(
                "Processing power profile: {0}".format(
                    profile.get("profileName", "Unnamed")
                ),
                "DEBUG",
            )

            # Iterate over each rule in the power profile
            for rule in rules:
                interface_type = rule.get("interface_type")
                self.log(
                    "Processing rule for interface type: {0}".format(interface_type),
                    "DEBUG",
                )

                # Assign default values based on the interface type if only one parameter is present
                if interface_type == "USB" and len(rule) == 1:
                    rule["interface_id"] = "USB0"
                    rule["parameter_type"] = "STATE"
                    rule["parameter_value"] = "DISABLE"
                    self.log(
                        "Assigned defaults for USB interface: interface_id=USB0, parameter_type=STATE, parameter_value=DISABLE",
                        "DEBUG",
                    )
                elif interface_type == "RADIO" and len(rule) == 1:
                    rule["interface_id"] = "6GHZ"
                    rule["parameter_type"] = "SPATIALSTREAM"
                    rule["parameter_value"] = "FOUR_BY_FOUR"
                    self.log(
                        "Assigned defaults for RADIO interface: interface_id=6GHZ, parameter_type=SPATIALSTREAM, parameter_value=FOUR_BY_FOUR",
                        "DEBUG",
                    )
                elif interface_type == "ETHERNET" and len(rule) == 1:
                    rule["interface_id"] = "GIGABITETHERNET0"
                    rule["parameter_type"] = "SPEED"
                    rule["parameter_value"] = "5000MBPS"
                    self.log(
                        "Assigned defaults for ETHERNET interface: interface_id=GIGABITETHERNET0, parameter_type=SPEED, parameter_value=5000MBPS",
                        "DEBUG",
                    )

        # Return the updated power profiles with default values applied
        self.log("Completed updating power profiles with default values.", "DEBUG")
        return power_profiles

    def verify_create_update_power_profiles_requirement(self, power_profiles):
        """
        Determines whether power profiles need to be created, updated, or require no updates.
        Args:
            power_profiles (list): A list of dictionaries containing the requested power profile parameters.
        Returns:
            tuple: Three lists containing power profiles to be created, updated, and not updated.
        """
        # Update requested profiles with default values where needed
        updated_power_profiles = self.update_power_profiles_with_defaults(
            power_profiles
        )
        self.log("Updated requested power profiles with defaults.", "DEBUG")

        # Retrieve all existing power profiles from the system
        existing_power_profiles = self.get_power_profiles(get_power_profiles_params={})
        self.log("Retrieved existing power profiles.", "DEBUG")

        self.log(
            "Existing Power Profiles: {0}".format(existing_power_profiles), "DEBUG"
        )
        self.log(
            "Requested Power Profiles: {0}".format(updated_power_profiles), "DEBUG"
        )

        # Initialize lists to store profiles that need to be created, updated, or not changed
        create_profiles = []
        update_profiles = []
        no_update_profiles = []

        # Create a dictionary of existing profiles for quick lookup using the profile name
        existing_profiles_dict = {
            profile["profileName"]: profile for profile in existing_power_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the updated requested power profiles
        for requested_profile in updated_power_profiles:
            profile_name = requested_profile["power_profile_name"]
            requested_description = requested_profile.get(
                "power_profile_description", ""
            )
            requested_rules = requested_profile.get("rules", [])
            self.log("Evaluating profile: {0}".format(profile_name), "DEBUG")

            # Check if the profile already exists
            if profile_name in existing_profiles_dict:
                existing_profile = existing_profiles_dict[profile_name]
                existing_description = existing_profile.get("description", "")
                existing_rules = existing_profile.get("rules", [])

                # Flag to determine if an update is needed
                update_needed = False

                # Compare description
                if requested_description != (existing_description or ""):
                    update_needed = True
                    self.log(
                        "Description differs for profile '{0}'.".format(profile_name),
                        "DEBUG",
                    )

                # Compare rules, considering both parameter changes and order changes
                if len(requested_rules) != len(existing_rules):
                    update_needed = True
                    self.log(
                        "Number of rules differs for profile '{0}'.".format(
                            profile_name
                        ),
                        "DEBUG",
                    )
                else:
                    for req_rule, exist_rule in zip(requested_rules, existing_rules):
                        if (
                            req_rule.get("interface_type")
                            != exist_rule.get("interfaceType")
                            or req_rule.get("interface_id")
                            != exist_rule.get("interfaceId")
                            or req_rule.get("parameter_type")
                            != exist_rule.get("parameterType")
                            or req_rule.get("parameter_value")
                            != exist_rule.get("parameterValue")
                        ):
                            update_needed = True
                            self.log(
                                "Rule differs for profile '{0}'.".format(profile_name),
                                "DEBUG",
                            )
                            break

                if update_needed:
                    # Add the requested profile with the ID from the existing profile
                    updated_profile = requested_profile.copy()
                    updated_profile["id"] = existing_profile.get("id")
                    update_profiles.append(updated_profile)
                    self.log(
                        "Profile '{0}' marked for update.".format(profile_name), "DEBUG"
                    )
                else:
                    # If there's no difference, add to no_update_profiles
                    no_update_profiles.append(existing_profile)
                    self.log(
                        "Profile '{0}' requires no updates.".format(profile_name),
                        "DEBUG",
                    )
            else:
                # If the profile does not exist, mark it for creation
                create_profiles.append(requested_profile)
                self.log(
                    "Profile '{0}' marked for creation.".format(profile_name), "DEBUG"
                )

        self.log(
            "Power Profiles that need to be CREATED: {0} - {1}".format(
                len(create_profiles), create_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Power Profiles that need to be UPDATED: {0} - {1}".format(
                len(update_profiles), update_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Power Profiles that DON'T NEED UPDATES: {0} - {1}".format(
                len(no_update_profiles), no_update_profiles
            ),
            "DEBUG",
        )

        # Calculate total profiles processed and check against requested profiles
        total_profiles_processed = (
            len(create_profiles) + len(update_profiles) + len(no_update_profiles)
        )

        if total_profiles_processed == len(updated_power_profiles):
            self.log(
                "Match in total counts: Processed={0}, Requested={1}.".format(
                    total_profiles_processed, len(updated_power_profiles)
                ),
                "DEBUG",
            )
        else:
            self.log(
                "Mismatch in total counts: Processed={0}, Requested={1}.".format(
                    total_profiles_processed, len(updated_power_profiles)
                ),
                "ERROR",
            )

        # Return the categorized profiles
        return create_profiles, update_profiles, no_update_profiles

    def verify_delete_power_profiles_requirement(self, power_profiles):
        """
        Determines whether power profiles need to be deleted based on the requested parameters.
        Args:
            power_profiles (list): A list of dictionaries containing the requested power profile parameters for deletion.
        Returns:
            list: A list of power profiles that need to be deleted, including their IDs.
        """
        # Initialize the list to hold profiles scheduled for deletion
        delete_power_profiles_list = []

        self.log("Starting verification of power profiles for deletion.", "INFO")

        # Retrieve all existing power profiles
        existing_power_profiles = self.get_power_profiles(get_power_profiles_params={})
        self.log(
            "Existing Power Profiles: {0}".format(existing_power_profiles), "DEBUG"
        )

        # Convert existing power profiles to a dictionary for quick lookup by power profile name
        existing_power_profiles_dict = {
            profile["profileName"]: profile for profile in existing_power_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested power profiles
        for requested_profile in power_profiles:
            profile_name = requested_profile.get("power_profile_name")
            self.log(
                "Checking deletion requirement for profile: {0}".format(profile_name),
                "DEBUG",
            )

            # Check if the power profile exists in the existing power profiles
            if profile_name in existing_power_profiles_dict:
                # Add the requested power profile with the ID from the existing power profile
                existing_profile = existing_power_profiles_dict[profile_name]
                profile_to_delete = requested_profile.copy()
                profile_to_delete["id"] = existing_profile.get("id")
                delete_power_profiles_list.append(profile_to_delete)
                self.log(
                    "Power Profile '{0}' scheduled for deletion.".format(profile_name),
                    "INFO",
                )
            else:
                self.log(
                    "Deletion not required for power profile '{0}'. It does not exist.".format(
                        profile_name
                    ),
                    "INFO",
                )

        self.log(
            "Power Profiles scheduled for deletion: {0} - {1}".format(
                len(delete_power_profiles_list), delete_power_profiles_list
            ),
            "DEBUG",
        )

        # Return the list of profiles that need to be deleted
        return delete_power_profiles_list

    def map_power_profiles_params(self, power_profiles):
        """
        Maps the parameters of each power profile to the required format for API calls.
        Args:
            power_profiles (list): A list of dictionaries containing power profile parameters.
        Returns:
            list: A list of dictionaries with the parameters mapped to the required format.
        """
        # Initialize an empty list to hold the mapped power profiles
        mapped_power_profiles = []

        # Check if the power profiles list is empty and return the empty mapped list
        if not power_profiles:
            self.log("No power profiles provided for mapping.", "DEBUG")
            return mapped_power_profiles

        # Iterate over each power profile to perform mappings
        for profile in power_profiles:
            mapped_profile = {}
            self.log("Mapping power profile: {0}".format(profile), "DEBUG")

            # Map each parameter to the required format
            if "power_profile_name" in profile:
                mapped_profile["profileName"] = profile["power_profile_name"]
                self.log(
                    "Mapped 'power_profile_name' to 'profileName': {0}".format(
                        profile["power_profile_name"]
                    ),
                    "DEBUG",
                )

            if "power_profile_description" in profile:
                mapped_profile["description"] = profile["power_profile_description"]
                self.log(
                    "Mapped 'power_profile_description' to 'description': {0}".format(
                        profile["power_profile_description"]
                    ),
                    "DEBUG",
                )

            if "id" in profile:
                mapped_profile["id"] = profile["id"]
                self.log("Mapped 'id': {0}".format(profile["id"]), "DEBUG")

            # Map the rules if they exist in the profile
            if "rules" in profile:
                mapped_rules = []
                self.log(
                    "Mapping rules for profile: {0}".format(
                        profile.get("power_profile_name", "Unnamed")
                    ),
                    "DEBUG",
                )

                for rule in profile["rules"]:
                    mapped_rule = {}
                    if "interface_type" in rule:
                        mapped_rule["interfaceType"] = rule["interface_type"]
                        self.log(
                            "Mapped 'interface_type' to 'interfaceType': {0}".format(
                                rule["interface_type"]
                            ),
                            "DEBUG",
                        )
                    if "interface_id" in rule:
                        mapped_rule["interfaceId"] = rule["interface_id"]
                        self.log(
                            "Mapped 'interface_id' to 'interfaceId': {0}".format(
                                rule["interface_id"]
                            ),
                            "DEBUG",
                        )
                    if "parameter_type" in rule:
                        mapped_rule["parameterType"] = rule["parameter_type"]
                        self.log(
                            "Mapped 'parameter_type' to 'parameterType': {0}".format(
                                rule["parameter_type"]
                            ),
                            "DEBUG",
                        )
                    if "parameter_value" in rule:
                        mapped_rule["parameterValue"] = rule["parameter_value"]
                        self.log(
                            "Mapped 'parameter_value' to 'parameterValue': {0}".format(
                                rule["parameter_value"]
                            ),
                            "DEBUG",
                        )

                    # Add the mapped rule to the list of mapped rules
                    mapped_rules.append(mapped_rule)

                # Assign the mapped rules to the profile
                mapped_profile["rules"] = mapped_rules

            # Add the mapped profile to the list of mapped power profiles
            mapped_power_profiles.append(mapped_profile)
            self.log("Added mapped power profile: {0}".format(mapped_profile), "DEBUG")

        self.log("Mapped Power Profiles: {0}".format(mapped_power_profiles), "DEBUG")

        # Return the list of mapped power profiles
        return mapped_power_profiles

    def create_power_profile(self, create_power_profile_params):
        """
        Initiates the creation of a power profile using the provided parameters.
        Args:
            create_power_profile_params (dict): A dictionary containing parameters required for creating a power profile.
        Returns:
            dict: The response containing the task ID for the create operation.
        """
        self.log(
            "Initiating addition of power profile with parameters: {0}".format(
                create_power_profile_params
            ),
            "INFO",
        )

        # Execute the API call to create the power profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "create_power_profile", create_power_profile_params
        )

    def update_power_profile(self, update_power_profile_params):
        """
        Initiates the update of a power profile using the provided parameters.
        Args:
            update_power_profile_params (dict): A dictionary containing parameters required for updating a power profile.
        Returns:
            dict: The response containing the task ID for the update operation.
        """
        self.log(
            "Initiating update power profile parameters: {0}".format(
                update_power_profile_params
            ),
            "INFO",
        )

        # Execute the API call to update the power profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_power_profile_by_id", update_power_profile_params
        )

    def delete_power_profile(self, delete_power_profile_params):
        """
        Initiates the deletion of a power profile using the provided parameters.
        Args:
            delete_power_profile_params (dict): A dictionary containing parameters required for deleting a power profile.
        Returns:
            dict: The response containing the task ID for the delete operation.
        """
        self.log(
            "Initiating deletion power profile parameters: {0}".format(
                delete_power_profile_params
            ),
            "INFO",
        )

        # Execute the API call to delete the power profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "delete_power_profile_by_id", delete_power_profile_params
        )

    def process_power_profiles_common(
        self, profiles_params, operation_function, task_name
    ):
        """
        Processes the power profiles for the specified operation (create, update, delete).
        Args:
            profiles_params (list): A list of dictionaries containing parameters for each power profile operation.
            operation_function (function): The function to execute for each power profile operation.
            task_name (str): The name of the task being performed, For example, "Create Power Profile(s) Task".
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Initialize lists to track successful and failed profile operations
        failed_profiles = []
        success_profiles = []
        msg = {}

        # Iterate over each profile parameter set for processing
        for profile in profiles_params:
            profile_name = profile.get("profileName")
            self.log("Processing power profile: {0}".format(profile_name), "DEBUG")

            # Prepare parameters for the operation
            if operation_function == self.delete_power_profile:
                # For delete operation, extract only the id
                operation_params = {"id": profile.get("id")}
            else:
                # For create or update operations, use the entire profile
                operation_params = profile

            # Execute the operation and retrieve the task ID
            task_id = operation_function(operation_params)
            self.log(
                "Task ID for power profile '{0}': {1}".format(profile_name, task_id),
                "DEBUG",
            )

            # Check the status of the operation
            operation_msg = "{0} operation has completed successfully for power profile: {1}.".format(
                task_name, profile_name
            )
            self.get_task_status_from_tasks_by_id(
                task_id, task_name, operation_msg
            ).check_return_status()

            # Determine if the operation was successful
            if self.status == "success":
                success_profiles.append(profile_name)
                self.log(
                    "Power Profile '{0}' processed successfully.".format(profile_name),
                    "INFO",
                )
            else:
                failed_profiles.append(profile_name)
                self.log(
                    "Power Profile '{0}' failed to process.".format(profile_name),
                    "ERROR",
                )

        # Set the final message for successful operations
        if success_profiles:
            self.log(
                "{0} succeeded for the following power profile(s): {1}".format(
                    task_name, ", ".join(success_profiles)
                ),
                "INFO",
            )
            msg[
                "{0} succeeded for the following power profile(s)".format(task_name)
            ] = {
                "success_count": len(success_profiles),
                "successful_power_profiles": success_profiles,
            }

        # Set the final message for failed operations
        if failed_profiles:
            self.log(
                "{0} failed for the following power profile(s): {1}".format(
                    task_name, ", ".join(failed_profiles)
                ),
                "ERROR",
            )
            msg["{0} failed for the following power profile(s)".format(task_name)] = {
                "failed_count": len(failed_profiles),
                "failed_power_profiles": failed_profiles,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_profiles and failed_profiles:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_profiles:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_profiles:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def process_add_power_profiles(self, add_power_profiles_params):
        """
        Initiates the process to add power profiles.
        Args:
            add_power_profiles_params (list): A list of dictionaries containing parameters for each power profile to be added.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for creating power profiles
        task_name_create = "Create Power Profile(s) Task"
        self.log(
            "Starting the creation process for power profiles with task name: {0}".format(
                task_name_create
            ),
            "INFO",
        )

        # Call the common processing function to add power profiles
        return self.process_power_profiles_common(
            add_power_profiles_params, self.create_power_profile, task_name_create
        )

    def process_update_power_profiles(self, update_power_profiles_params):
        """
        Initiates the process to update power profiles.
        Args:
            update_power_profiles_params (list): A list of dictionaries containing parameters for each power profile to be updated.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for updating power profiles
        task_name_update = "Update Power Profile(s) Task"
        self.log(
            "Starting the update process for power profiles with task name: {0}".format(
                task_name_update
            ),
            "INFO",
        )

        # Call the common processing function to update power profiles
        return self.process_power_profiles_common(
            update_power_profiles_params, self.update_power_profile, task_name_update
        )

    def process_delete_power_profiles(self, delete_power_profiles_params):
        """
        Initiates the process to delete power profiles.
        Args:
            delete_power_profiles_params (list): A list of dictionaries containing parameters for each power profile to be deleted.
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Define the task name for deleting power profiles
        task_name_delete = "Delete Power Profile(s) Task"
        self.log(
            "Starting the deletion process for power profiles with task name: {0}".format(
                task_name_delete
            ),
            "INFO",
        )

        # Call the common processing function to delete power profiles
        return self.process_power_profiles_common(
            delete_power_profiles_params, self.delete_power_profile, task_name_delete
        )

    def verify_add_power_profiles_operation(self, add_power_profiles_params):
        """
        Verifies whether the power profiles specified in add_power_profiles_params have been successfully created.
        Args:
            add_power_profiles_params (list): A list of dictionaries containing the requested power profile parameters to be added.
        Returns:
            tuple: Two lists containing successfully created power profiles and failed profiles.
        """
        # Retrieve all existing power profiles
        existing_power_profiles = self.get_power_profiles(get_power_profiles_params={})
        self.log("Retrieved existing power profiles.", "DEBUG")

        self.log(
            "Existing Power Profiles: {0}".format(existing_power_profiles), "DEBUG"
        )
        self.log(
            "Requested Power Profiles to Add: {0}".format(add_power_profiles_params),
            "DEBUG",
        )

        # Initialize lists to track successful and failed profile additions
        successful_profiles = []
        failed_profiles = []

        # Convert existing power profiles to a set for quick lookup by profile name
        existing_profiles_set = {
            profile["profileName"] for profile in existing_power_profiles
        }
        self.log("Converted existing profiles to a set for quick lookup.", "DEBUG")

        # Iterate over the requested power profiles to verify creation
        for requested_profile in add_power_profiles_params:
            profile_name = requested_profile["profileName"]
            self.log(
                "Verifying creation for profile: {0}".format(profile_name), "DEBUG"
            )

            # Check if the profile exists in the existing profiles
            if profile_name in existing_profiles_set:
                successful_profiles.append(profile_name)
                self.log(
                    "Power Profile '{0}' has been successfully created.".format(
                        profile_name
                    ),
                    "INFO",
                )
            else:
                failed_profiles.append(profile_name)
                self.log(
                    "Power Profile '{0}' failed to create.".format(profile_name),
                    "ERROR",
                )

        if failed_profiles:
            self.log(
                "The ADD Power Profile(s) operation may not have been successful since some power profiles were not successfully created: {0}".format(
                    failed_profiles
                ),
                "WARNING",
            )
        else:
            self.log(
                "Verified the success of ADD Power Profile(s) operation for the following profiles: {0}.".format(
                    successful_profiles
                ),
                "INFO",
            )

    def verify_update_power_profiles_operation(self, update_power_profiles_params):
        """
        Verifies whether the power profiles specified in update_power_profiles_params have been successfully updated.
        Args:
            update_power_profiles_params (list): A list of dictionaries containing the requested power profile parameters to be updated.
        Returns:
            tuple: Two lists containing successfully updated power profiles and failed profiles.
        """
        # Retrieve all existing power profiles
        existing_power_profiles = self.get_power_profiles(get_power_profiles_params={})
        self.log("Retrieved existing power profiles.", "DEBUG")

        self.log(
            "Existing Power Profiles: {0}".format(existing_power_profiles), "DEBUG"
        )
        self.log(
            "Requested Power Profiles to Update: {0}".format(
                update_power_profiles_params
            ),
            "DEBUG",
        )

        # Initialize lists to track successful and failed profile updates
        successful_updates = []
        failed_updates = []

        # Convert existing power profiles to a dictionary for quick lookup by profile name
        existing_profiles_dict = {
            profile["profileName"]: profile for profile in existing_power_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested power profiles to verify updates
        for requested_profile in update_power_profiles_params:
            profile_name = requested_profile["profileName"]
            requested_description = requested_profile.get("description", "")
            requested_rules = requested_profile.get("rules", [])
            self.log("Verifying update for profile: {0}".format(profile_name), "DEBUG")

            # Check if the profile exists in the existing profiles
            if profile_name in existing_profiles_dict:
                existing_profile = existing_profiles_dict[profile_name]
                existing_description = existing_profile.get("description", "")
                existing_rules = existing_profile.get("rules", [])

                # Flag to determine if an update is needed
                update_successful = True

                # Compare description
                if requested_description != (existing_description or ""):
                    update_successful = False
                    self.log(
                        "Description mismatch for profile '{0}'. Requested: {1}, Existing: {2}".format(
                            profile_name, requested_description, existing_description
                        ),
                        "DEBUG",
                    )

                # Compare rules, ignoring the sequence parameter
                if len(requested_rules) != len(existing_rules):
                    update_successful = False
                    self.log(
                        "Rule count mismatch for profile '{0}'. Requested: {1}, Existing: {2}".format(
                            profile_name, len(requested_rules), len(existing_rules)
                        ),
                        "DEBUG",
                    )
                else:
                    for req_rule, exist_rule in zip(requested_rules, existing_rules):
                        if (
                            req_rule.get("interfaceType")
                            != exist_rule.get("interfaceType")
                            or req_rule.get("interfaceId")
                            != exist_rule.get("interfaceId")
                            or req_rule.get("parameterType")
                            != exist_rule.get("parameterType")
                        ):
                            update_successful = False
                            self.log(
                                "Rule mismatch in profile '{0}'. Requested rule: {1}, Existing rule: {2}".format(
                                    profile_name, req_rule, exist_rule
                                ),
                                "DEBUG",
                            )
                            break

                if update_successful:
                    successful_updates.append(profile_name)
                    self.log(
                        "Power Profile '{0}' has been successfully updated.".format(
                            profile_name
                        ),
                        "INFO",
                    )
                else:
                    failed_updates.append(profile_name)
                    self.log(
                        "Power Profile '{0}' failed to update.".format(profile_name),
                        "ERROR",
                    )
            else:
                failed_updates.append(profile_name)
                self.log(
                    "Power Profile '{0}' does not exist and cannot be updated.".format(
                        profile_name
                    ),
                    "ERROR",
                )

        if failed_updates:
            self.log(
                "The UPDATE Power Profiles operation may not have been successful. The following power profiles failed verification: {0}.".format(
                    failed_updates
                ),
                "ERROR",
            )
        else:
            self.log(
                "Successfully verified the UPDATE Power Profiles operation for the following profiles: {0}.".format(
                    successful_updates
                ),
                "INFO",
            )

    def verify_delete_power_profiles_operation(self, delete_power_profiles_params):
        """
        Verifies whether the power profiles specified in delete_power_profiles_params have been successfully deleted.
        Args:
            delete_power_profiles_params (list): A list of dictionaries containing the requested power profile names to be deleted.
        Returns:
            bool: True if all requested power profiles were successfully deleted, False otherwise.
        """
        # Retrieve all existing power profiles
        existing_power_profiles = self.get_power_profiles(get_power_profiles_params={})
        # Convert existing profiles to a set for quick lookup
        existing_profiles_set = {
            profile["profileName"] for profile in existing_power_profiles
        }
        self.log("Retrieved current power profiles.", "DEBUG")

        self.log(
            "Current Power Profiles after DELETE operation: {0}".format(
                existing_profiles_set
            ),
            "INFO",
        )
        self.log(
            "Requested Power Profiles to Delete: {0}".format(
                delete_power_profiles_params
            ),
            "INFO",
        )

        # Initialize a list to track profiles that failed deletion
        failed_deletions = []

        # Iterate over the requested power profiles to verify deletion
        for requested_profile in delete_power_profiles_params:
            profile_name = requested_profile["profileName"]
            self.log(
                "Verifying deletion for profile: {0}".format(profile_name), "DEBUG"
            )

            # Check if the profile still exists in the existing profiles
            if profile_name in existing_profiles_set:
                # If it exists, the deletion failed
                failed_deletions.append(profile_name)
                self.log(
                    "Delete operation failed for Power Profile '{0}'. It still exists.".format(
                        profile_name
                    ),
                    "ERROR",
                )

        if failed_deletions:
            self.log(
                "The DELETE Power Profile(s) operation may not have been successful since some Power Profiles still exist: {0}.".format(
                    failed_deletions
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of DELETE Power Profile(s) operation for following parameters: {0}.".format(
                    delete_power_profiles_params
                ),
                "INFO",
            )

    def get_access_point_profiles_params(self, access_point_profile_name=None):
        """
        Constructs and returns a dictionary of parameters for retrieving access point profiles.
        Args:
            access_point_profile_name (str, optional): The name of the access point profile to filter the retrieval.
        Returns:
            dict: A dictionary containing parameters to be used for API calls to retrieve access point profiles.
        """
        # Initialize an empty dictionary to hold the parameters for the API call
        get_access_point_profiles_params = {}

        # Map the user-provided access point profile name to the expected API parameter
        if access_point_profile_name:
            get_access_point_profiles_params["ap_profile_name"] = (
                access_point_profile_name
            )
            self.log(
                "Added 'ap_profile_name' to parameters: {0}".format(
                    access_point_profile_name
                ),
                "DEBUG",
            )
        else:
            self.log("No specific access point profile name provided.", "DEBUG")

        # Return the constructed parameters dictionary
        self.log(
            "Constructed get_access_point_profiles_params: {0}".format(
                get_access_point_profiles_params
            ),
            "DEBUG",
        )
        return get_access_point_profiles_params

    def get_access_point_profiles(self, get_access_point_profiles_params):
        """
        Retrieves access point profile details using pagination.
        Args:
            get_access_point_profiles_params (dict): Parameters for filtering the access point profiles.
        Returns:
            list: A list of dictionaries containing details of access point profiles based on the filtering parameters.
        """
        # Execute the paginated API call to retrieve access point profiles
        self.log(
            "Executing paginated API call to retrieve access point profiles.", "DEBUG"
        )
        return self.execute_get_with_pagination(
            "wireless", "get_ap_profiles", get_access_point_profiles_params
        )

    def map_access_point_profiles_params(self, access_point_profiles):
        """
        Maps the provided access point profiles parameters to the parameters required for API calls.
        Args:
            access_point_profiles (list): A list of dictionaries containing access point profile parameters.
        Returns:
            list: A list of dictionaries with mapped parameters suitable for API calls.
        """
        # Initialize an empty list to store the mapped access point profiles
        mapped_access_point_profiles = []

        # Check if the access point profiles list is empty and return the empty mapped list
        if not access_point_profiles:
            self.log("No access point profiles provided for mapping.", "DEBUG")
            return mapped_access_point_profiles

        # Define a mapping from country names to country codes
        country_code_map = {
            # Country name to code mappings
            "Afghanistan": "AF",
            "Albania": "AL",
            "Algeria": "DZ",
            "Angola": "AO",
            "Argentina": "AR",
            "Australia": "AU",
            "Austria": "AT",
            "Bahamas": "BS",
            "Bahrain": "BH",
            "Bangladesh": "BD",
            "Barbados": "BB",
            "Belarus": "BY",
            "Belgium": "BE",
            "Bhutan": "BT",
            "Bolivia": "BO",
            "Bosnia": "BA",
            "Botswana": "BW",
            "Brazil": "BR",
            "Brunei": "BN",
            "Bulgaria": "BG",
            "Burundi": "BI",
            "Cambodia": "KH",
            "Cameroon": "CM",
            "Canada": "CA",
            "Chile": "CL",
            "China": "CN",
            "Colombia": "CO",
            "Costa Rica": "CR",
            "Croatia": "HR",
            "Cuba": "CU",
            "Cyprus": "CY",
            "Czech Republic": "CZ",
            "Democratic Republic of the Congo": "CD",
            "Denmark": "DK",
            "Dominican Republic": "DO",
            "Ecuador": "EC",
            "Egypt": "EG",
            "El Salvador": "SV",
            "Estonia": "EE",
            "Ethiopia": "ET",
            "Fiji": "FJ",
            "Finland": "FI",
            "France": "FR",
            "Gabon": "GA",
            "Georgia": "GE",
            "Germany": "DE",
            "Ghana": "GH",
            "Gibraltar": "GI",
            "Greece": "GR",
            "Guatemala": "GT",
            "Honduras": "HN",
            "Hong Kong": "HK",
            "Hungary": "HU",
            "Iceland": "IS",
            "India": "IN",
            "Indonesia": "ID",
            "Iraq": "IQ",
            "Ireland": "IE",
            "Isle of Man": "IM",
            "Israel": "IL",
            "Italy": "IT",
            "Ivory Coast (Cote dIvoire)": "CI",
            "Jamaica": "JM",
            "Japan 2(P)": "J2",
            "Japan 4(Q)": "J4",
            "Jersey": "JE",
            "Jordan": "JO",
            "Kazakhstan": "KZ",
            "Kenya": "KE",
            "Korea Extended (CK)": "KR",
            "Kosovo": "XK",
            "Kuwait": "KW",
            "Laos": "LA",
            "Latvia": "LV",
            "Lebanon": "LB",
            "Libya": "LY",
            "Liechtenstein": "LI",
            "Lithuania": "LT",
            "Luxembourg": "LU",
            "Macao": "MO",
            "Macedonia": "MK",
            "Malaysia": "MY",
            "Malta": "MT",
            "Mauritius": "MU",
            "Mexico": "MX",
            "Moldova": "MD",
            "Monaco": "MC",
            "Mongolia": "MN",
            "Montenegro": "ME",
            "Morocco": "MA",
            "Myanmar": "MM",
            "Namibia": "NA",
            "Nepal": "NP",
            "Netherlands": "NL",
            "New Zealand": "NZ",
            "Nicaragua": "NI",
            "Nigeria": "NG",
            "Norway": "NO",
            "Oman": "OM",
            "Pakistan": "PK",
            "Panama": "PA",
            "Paraguay": "PY",
            "Peru": "PE",
            "Philippines": "PH",
            "Poland": "PL",
            "Portugal": "PT",
            "Puerto Rico": "PR",
            "Qatar": "QA",
            "Romania": "RO",
            "Russian Federation": "RU",
            "San Marino": "SM",
            "Saudi Arabia": "SA",
            "Serbia": "RS",
            "Singapore": "SG",
            "Slovak Republic": "SK",
            "Slovenia": "SI",
            "South Africa": "ZA",
            "Spain": "ES",
            "Sri Lanka": "LK",
            "Sudan": "SD",
            "Sweden": "SE",
            "Switzerland": "CH",
            "Taiwan": "TW",
            "Thailand": "TH",
            "Trinidad": "TT",
            "Tunisia": "TN",
            "Turkey": "TR",
            "Uganda": "UG",
            "Ukraine": "UA",
            "United Arab Emirates": "AE",
            "United Kingdom": "GB",
            "United Republic of Tanzania": "TZ",
            "United States": "US",
            "Uruguay": "UY",
            "Uzbekistan": "UZ",
            "Vatican City State": "VA",
            "Venezuela": "VE",
            "Vietnam": "VN",
            "Yemen": "YE",
            "Zambia": "ZM",
            "Zimbabwe": "ZW",
        }

        # Iterate over each access point profile
        for profile in access_point_profiles:
            mapped_profile = {}

            # Map the ID if it exists
            if "id" in profile:
                mapped_profile["id"] = profile["id"]
                self.log("Mapped 'id' to '{0}'.".format(profile["id"]), "DEBUG")

            # Define mappings for basic profile attributes
            mappings = {
                "apProfileName": profile.get("access_point_profile_name"),
                "description": profile.get("access_point_profile_description"),
                "remoteWorkerEnabled": profile.get("remote_teleworker"),
                "awipsEnabled": profile.get("security_settings", {}).get("awips"),
                "awipsForensicEnabled": profile.get("security_settings", {}).get(
                    "awips_forensic"
                ),
                "pmfDenialEnabled": profile.get("security_settings", {}).get(
                    "pmf_denial"
                ),
                "meshEnabled": profile.get("mesh_enabled"),
                "apPowerProfileName": profile.get("power_settings", {}).get(
                    "ap_power_profile_name"
                ),
                "countryCode": profile.get("country_code"),
                "timeZone": profile.get("time_zone"),
                "timeZoneOffsetHour": profile.get("time_zone_offset_hour"),
                "timeZoneOffsetMinutes": profile.get("time_zone_offset_minutes"),
                "clientLimit": profile.get("maximum_client_limit"),
            }

            # Apply basic mappings
            self.log("Applying basic mappings.", "DEBUG")
            for key, value in mappings.items():
                if value is not None:
                    mapped_profile[key] = value
                    self.log("Mapped '{0}' to '{1}'.".format(key, value), "DEBUG")

            # Map the country code if provided
            if "country_code" in profile:
                mapped_profile["countryCode"] = country_code_map.get(
                    profile["country_code"]
                )
                self.log(
                    "Mapped 'country_code' to '{0}'.".format(
                        mapped_profile["countryCode"]
                    ),
                    "DEBUG",
                )

            # Define mappings for management settings
            management_mapping = {
                "access_point_authentication": "authType",
                "dot1x_username": "dot1xUsername",
                "dot1x_password": "dot1xPassword",
                "ssh_enabled": "sshEnabled",
                "telnet_enabled": "telnetEnabled",
                "management_username": "managementUserName",
                "management_password": "managementPassword",
                "management_enable_password": "managementEnablePassword",
                "cdp_state": "cdpState",
            }

            # Map the management settings if they exist
            if "management_settings" in profile and profile["management_settings"]:
                management_settings = profile["management_settings"]
                mapped_profile["managementSetting"] = {}

                for key, original_key in management_mapping.items():
                    if key in management_settings:
                        mapped_profile["managementSetting"][original_key] = (
                            management_settings[key]
                        )
                        self.log(
                            "Mapped '{0}' to '{1}'.".format(key, original_key), "DEBUG"
                        )
                    else:
                        self.log(
                            "Key '{0}' not found in management_settings.".format(key),
                            "WARNING",
                        )

            # Define mappings for rogue detection settings
            rogue_detection_mapping = {
                "rogue_detection_enabled": "rogueDetection",
                "minimum_rssi": "rogueDetectionMinRssi",
                "transient_interval": "rogueDetectionTransientInterval",
                "report_interval": "rogueDetectionReportInterval",
            }

            # Map the rogue detection settings if they exist
            if "security_settings" in profile and profile["security_settings"]:
                security_settings = profile["security_settings"]
                if security_settings.get("rogue_detection_enabled", False):
                    mapped_profile["rogueDetectionSetting"] = {}

                    for key, original_key in rogue_detection_mapping.items():
                        if key in security_settings:
                            mapped_profile["rogueDetectionSetting"][original_key] = (
                                security_settings[key]
                            )
                            self.log(
                                "Mapped '{0}' to '{1}'.".format(key, original_key),
                                "DEBUG",
                            )
                        else:
                            self.log(
                                "Key '{0}' not found in security_settings.".format(key),
                                "WARNING",
                            )

            # Define mappings for mesh settings
            mesh_mapping = {
                "bridge_group_name": "bridgeGroupName",
                "backhaul_client_access": "backhaulClientAccess",
                "range": "range",
                "ghz_5_backhaul_data_rates": "ghz5BackhaulDataRates",
                "ghz_2_4_backhaul_data_rates": "ghz24BackhaulDataRates",
                "rap_downlink_backhaul": "rapDownlinkBackhaul",
            }

            # Map the mesh settings if they exist
            if "mesh_settings" in profile and profile["mesh_settings"]:
                self.log("Found 'mesh_settings': {0} in profile: {1}".format(profile["mesh_settings"], profile), "DEBUG")
                mesh_settings = profile["mesh_settings"]
                mapped_profile["meshSetting"] = {}

                for key, original_key in mesh_mapping.items():
                    if key in mesh_settings:
                        mapped_profile["meshSetting"][original_key] = mesh_settings[key]
                        self.log(
                            "Mapped '{0}' to '{1}'.".format(key, original_key), "DEBUG"
                        )
                    else:
                        self.log(
                            "Key '{0}' not found in mesh_settings.".format(key),
                            "WARNING",
                        )

            # Map calendar power profiles if they exist
            if "power_settings" in profile and profile["power_settings"]:
                power_settings = profile["power_settings"]

                calendar_power_profiles_mapping = {
                    "ap_power_profile_name": "powerProfileName",
                    "scheduler_type": "schedulerType",
                }

                scheduler_mapping = {
                    "scheduler_start_time": "schedulerStartTime",
                    "scheduler_end_time": "schedulerEndTime",
                    "scheduler_days_list": "schedulerDay",
                    "scheduler_dates_list": "schedulerDate",
                }

                # Initialize the API-compatible structure for calendar power profiles
                api_calendar_profiles = []

                # Iterate over each calendar power profile in the provided settings
                for calendar_profile in power_settings.get(
                    "calendar_power_profiles", []
                ):
                    # Map the main calendar power profile fields
                    api_calendar_profile = {}
                    for (
                        provided_key,
                        api_key,
                    ) in calendar_power_profiles_mapping.items():
                        if provided_key in calendar_profile:
                            api_calendar_profile[api_key] = calendar_profile[
                                provided_key
                            ]
                            self.log(
                                "Mapped '{0}' to '{1}'.".format(provided_key, api_key),
                                "DEBUG",
                            )

                    # Map the scheduler fields
                    api_calendar_profile["duration"] = {}
                    for provided_key, api_key in scheduler_mapping.items():
                        if provided_key in calendar_profile:
                            api_calendar_profile["duration"][api_key] = (
                                calendar_profile[provided_key]
                            )
                            self.log(
                                "Mapped '{0}' to '{1}'.".format(provided_key, api_key),
                                "DEBUG",
                            )

                    # Add the mapped calendar profile to the list
                    api_calendar_profiles.append(api_calendar_profile)
                    self.log(
                        "Added mapped calendar power profile: {0}".format(
                            api_calendar_profile
                        ),
                        "DEBUG",
                    )

                mapped_profile["calendarPowerProfiles"] = api_calendar_profiles

            # Add the mapped profile to the list
            mapped_access_point_profiles.append(mapped_profile)
            self.log(
                "Added mapped access point profile: {0}".format(mapped_profile), "DEBUG"
            )

        self.log(
            "Mapped Access Point Profiles: {0}".format(mapped_access_point_profiles),
            "DEBUG",
        )

        # Return the list of mapped access point profiles
        return mapped_access_point_profiles

    def normalize_time(self, time_str):
        """
        Normalize the time string to a standard format (HH:MM AM/PM).
        Args:
            time_str (str): The time string to be normalized.
        Returns:
            str: The normalized time string in the format HH:MM AM/PM.
        """
        # Use regex to match the time string pattern
        self.log("Attempting to normalize time string: {0}".format(time_str), "DEBUG")
        match = re.match(r"(\d{1,2}):(\d{2})\s?(AM|PM)", time_str, re.IGNORECASE)

        if match:
            # Extract hour, minute, and period from the matched groups
            hour, minute, period = match.groups()
            self.log(
                "Matched time components - Hour: {0}, Minute: {1}, Period: {2}".format(
                    hour, minute, period
                ),
                "DEBUG",
            )

            # Ensure two digits for the hour
            hour = hour.zfill(2)
            normalized_time = "{0}:{1} {2}".format(hour, minute, period.upper())
            self.log("Normalized time string: {0}".format(normalized_time), "DEBUG")
            return normalized_time

        # Return the original time string if no match is found
        self.log(
            "No match found for time string. Returning original: {0}".format(time_str),
            "DEBUG",
        )
        return time_str

    def compare_values(self, requested_value, existing_value):
        """
        Compare requested and existing values, handling different types.
        Args:
            requested_value: The value requested for comparison.
            existing_value: The existing value to compare against.
        Returns:
            bool: True if values match, False otherwise.
        """
        # Compare dictionaries key by key
        if isinstance(requested_value, dict) and isinstance(existing_value, dict):
            self.log(
                "Comparing dictionaries. Requested: {0}, Existing: {1}".format(
                    requested_value, existing_value
                ),
                "DEBUG",
            )
            for sub_key in requested_value:
                if not self.compare_values(
                    requested_value[sub_key], existing_value.get(sub_key)
                ):
                    self.log(
                        "Mismatch found in dictionary comparison for key: {0}. Requested: {1}, Existing: {2}".format(
                            sub_key,
                            requested_value[sub_key],
                            existing_value.get(sub_key),
                        ),
                        "DEBUG",
                    )
                    return False

        # Compare lists by sorting and comparing elements
        elif isinstance(requested_value, list) and isinstance(existing_value, list):
            self.log(
                "Comparing lists. Requested: {0}, Existing: {1}".format(
                    requested_value, existing_value
                ),
                "DEBUG",
            )
            # Check if one list is empty while the other is not
            if not requested_value and existing_value:
                self.log(
                    "Mismatch: Requested value is an empty list, but existing value contains: {0}".format(
                        existing_value
                    ),
                    "DEBUG",
                )
                return False

            if requested_value and not existing_value:
                self.log(
                    "Mismatch: Existing value is an empty list, but requested value contains: {0}".format(
                        requested_value
                    ),
                    "DEBUG",
                )
                return False

            # Compare lists element by element
            requested_sorted = sorted(requested_value, key=str)
            existing_sorted = sorted(existing_value, key=str)
            for r, e in zip(requested_sorted, existing_sorted):
                if not self.compare_values(r, e):
                    self.log(
                        "Mismatch found in list comparison. Requested element: {0}, Existing element: {1}".format(
                            r, e
                        ),
                        "DEBUG",
                    )
                    return False

            self.log("Lists match after comparison.", "DEBUG")
            return True

        # Normalize and compare time strings
        elif isinstance(requested_value, str) and isinstance(existing_value, str):
            self.log(
                "Comparing string values. Requested: {0}, Existing: {1}".format(
                    requested_value, existing_value
                ),
                "DEBUG",
            )
            requested_value = self.normalize_time(requested_value)
            existing_value = self.normalize_time(existing_value)
            comparison_result = requested_value == existing_value
            self.log(
                "String comparison result: {0}. Normalized Requested: {1}, Normalized Existing: {2}".format(
                    comparison_result, requested_value, existing_value
                ),
                "DEBUG",
            )
            return comparison_result

        # Direct comparison for other types
        else:
            self.log(
                "Directly comparing values. Requested: {0}, Existing: {1}".format(
                    requested_value, existing_value
                ),
                "DEBUG",
            )
            return requested_value == existing_value

        return True

    def recursive_update(self, existing, updates):
        """
        Recursively update the existing dictionary or list with the updates provided.
        Args:
            existing (dict or list): The existing data structure to be updated.
            updates (dict or list): The updates to apply.
        """
        for key, value in updates.items():
            if (
                isinstance(value, dict)
                and key in existing
                and isinstance(existing[key], dict)
            ):
                self.log(f"Recursively updating dictionary for key: {key}", "DEBUG")
                self.recursive_update(existing[key], value)
            elif (
                isinstance(value, list)
                and key in existing
                and isinstance(existing[key], list)
            ):
                self.log("Handling list for key: '{0}'".format(key), "DEBUG")
                # If the requested list is empty, replace the existing list
                if not value:
                    self.log(
                        "Requested list for key '{0}' is empty. Replacing existing list.".format(
                            key
                        ),
                        "DEBUG",
                    )
                    existing[key] = value
                else:
                    # Handle list of dictionaries
                    for update_dict in value:
                        if isinstance(update_dict, dict):
                            matched = False
                            for existing_dict in existing[key]:
                                if all(
                                    existing_dict.get(id_key) == update_dict.get(id_key)
                                    for id_key in update_dict.keys()
                                    if id_key in existing_dict
                                ):
                                    self.log(
                                        "Match found for update in list for key '{0}'. Recursively updating.".format(
                                            key
                                        ),
                                        "DEBUG",
                                    )
                                    self.recursive_update(existing_dict, update_dict)
                                    matched = True
                                    break

                            if not matched:
                                self.log(
                                    f"No match found. Appending new dictionary to list for key '{key}'.",
                                    "DEBUG",
                                )
                                existing[key].append(update_dict)
            else:
                # Directly update the value
                self.log(
                    f"Updating value for key: {key}. Requested: {value}, Existing: {existing.get(key)}",
                    "DEBUG",
                )
                existing[key] = value

    def verify_create_update_access_point_profiles_requirement(self, access_point_profiles):
        """
        Determines whether access point profiles need to be created, updated, or require no updates.
        Args:
            access_point_profiles (list): A list of dictionaries containing the requested access point profile parameters.
        Returns:
            tuple: Three lists containing access point profiles to be created, updated, and not updated.
        """

        # Update requested profiles with default values where needed
        updated_access_point_profiles = self.map_access_point_profiles_params(
            access_point_profiles
        )
        self.log("Mapped requested profiles to include default values.", "DEBUG")

        # Retrieve all existing access point profiles from the system
        existing_access_point_profiles = self.get_access_point_profiles(
            get_access_point_profiles_params={}
        )
        self.log("Retrieved existing access point profiles from the system.", "DEBUG")

        self.log(
            "Existing Access Point Profiles: {0}".format(existing_access_point_profiles),
            "DEBUG",
        )
        self.log(
            "Requested Access Point Profiles: {0}".format(updated_access_point_profiles),
            "DEBUG",
        )

        create_profiles = []
        update_profiles = []
        no_update_profiles = []

        existing_profiles_dict = {
            profile["apProfileName"]: profile for profile in existing_access_point_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        for requested_profile in updated_access_point_profiles:
            profile_name = requested_profile["apProfileName"]
            self.log("Checking profile: {0}".format(profile_name), "DEBUG")
            update_needed = False

            tz = requested_profile.get("timeZone")
            if tz in ("NOT CONFIGURED", "CONTROLLER"):
                requested_profile["timeZoneOffsetHour"] = 0
                requested_profile["timeZoneOffsetMinutes"] = 0
                self.log(
                    "For profile '{0}', 'timeZone' is set to '{1}'. Setting 'timeZoneOffsetHour' and 'timeZoneOffsetMinutes' to 0.".format(
                        profile_name, tz
                    ),
                    "DEBUG",
                )

            if "calendarPowerProfiles" in requested_profile and isinstance(
                requested_profile["calendarPowerProfiles"], list
            ):
                self.log(
                    "Normalizing calendarPowerProfiles for profile '{0}'.".format(
                        profile_name
                    ),
                    "DEBUG",
                )
                for calendar_profile in requested_profile["calendarPowerProfiles"]:
                    if "duration" not in calendar_profile or calendar_profile["duration"] is None:
                        calendar_profile["duration"] = {}

                    scheduler_type = calendar_profile.get("schedulerType")
                    if scheduler_type == "DAILY":
                        calendar_profile["duration"]["schedulerDay"] = None
                        calendar_profile["duration"]["schedulerDate"] = None
                        self.log(
                            "Set 'schedulerDay' and 'schedulerDate' to None for schedulerType 'DAILY' in profile '{0}'.".format(
                                profile_name
                            ),
                            "DEBUG",
                        )
                    elif scheduler_type == "WEEKLY":
                        calendar_profile["duration"]["schedulerDate"] = None
                        self.log(
                            "Set 'schedulerDate' to None for schedulerType 'WEEKLY' in profile '{0}'.".format(
                                profile_name
                            ),
                            "DEBUG",
                        )
                    elif scheduler_type == "MONTHLY":
                        calendar_profile["duration"]["schedulerDay"] = None
                        self.log(
                            "Set 'schedulerDay' to None for schedulerType 'MONTHLY' in profile '{0}'.".format(
                                profile_name
                            ),
                            "DEBUG",
                        )
                    else:
                        self.log(
                            "Unknown schedulerType '{0}' in calendarPowerProfiles for profile '{1}'.".format(
                                scheduler_type, profile_name
                            ),
                            "DEBUG",
                        )

            if profile_name in existing_profiles_dict:
                existing_profile = existing_profiles_dict[profile_name]
                self.log(
                    "Profile '{0}' exists in the system.".format(profile_name), "DEBUG"
                )

                for key, requested_value in requested_profile.items():
                    if key in existing_profile:
                        existing_value = existing_profile[key]
                        if not self.compare_values(requested_value, existing_value):
                            update_needed = True
                            self.log(
                                "Mismatch found in parameter '{0}' for profile '{1}'. Requested: {2}, Existing: {3}".format(
                                    key, profile_name, requested_value, existing_value
                                ),
                                "DEBUG",
                            )
                    else:
                        update_needed = True
                        self.log(
                            "Requested key '{0}' not present in existing profile '{1}', marking for update.".format(
                                key, profile_name
                            ),
                            "DEBUG",
                        )

                if update_needed:
                    updated_profile = existing_profile.copy()
                    self.recursive_update(updated_profile, requested_profile)
                    update_profiles.append(updated_profile)
                    self.log(
                        "Profile '{0}' marked for update.".format(profile_name), "DEBUG"
                    )
                else:
                    # No changes needed for this profile
                    no_update_profiles.append(existing_profile)
                    self.log(
                        "Profile '{0}' requires no updates.".format(profile_name), "DEBUG"
                    )

            else:
                # The profile does not exist and needs to be created
                create_profiles.append(requested_profile)
                self.log(
                    "Profile '{0}' marked for creation.".format(profile_name), "DEBUG"
                )

        self.log(
            "Access Point Profiles that need to be CREATED: {0} - {1}".format(
                len(create_profiles), create_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Access Point Profiles that need to be UPDATED: {0} - {1}".format(
                len(update_profiles), update_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Access Point Profiles that DON'T NEED UPDATES: {0} - {1}".format(
                len(no_update_profiles), no_update_profiles
            ),
            "DEBUG",
        )

        total_profiles_processed = (
            len(create_profiles) + len(update_profiles) + len(no_update_profiles)
        )

        if total_profiles_processed == len(updated_access_point_profiles):
            self.log(
                "Match in total counts: Processed={0}, Requested={1}.".format(
                    total_profiles_processed, len(updated_access_point_profiles)
                ),
                "DEBUG",
            )
        else:
            self.log(
                "Mismatch in total counts: Processed={0}, Requested={1}.".format(
                    total_profiles_processed, len(updated_access_point_profiles)
                ),
                "ERROR",
            )

        return create_profiles, update_profiles, no_update_profiles

    def verify_delete_access_point_profiles_requirement(self, access_point_profiles):
        """
        Determines whether access point profiles need to be deleted based on the requested parameters.
        Args:
            access_point_profiles (list): A list of dictionaries containing the requested access point profile parameters for deletion.
        Returns:
            list: A list of access point profiles that need to be deleted, including their IDs.
        """
        # Initialize the list to hold profiles scheduled for deletion
        delete_access_point_profiles_list = []

        self.log("Starting verification of access point profiles for deletion.", "INFO")

        # Retrieve all existing access point profiles
        existing_access_point_profiles = self.get_access_point_profiles(
            get_access_point_profiles_params={}
        )
        self.log(
            "Existing Access Point Profiles: {0}".format(
                existing_access_point_profiles
            ),
            "DEBUG",
        )

        # Convert existing access point profiles to a dictionary for quick lookup by profile name
        existing_profiles_dict = {
            profile["apProfileName"]: profile
            for profile in existing_access_point_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested access point profiles
        for requested_profile in access_point_profiles:
            profile_name = requested_profile.get("access_point_profile_name")
            self.log(
                "Checking deletion requirement for profile: {0}".format(profile_name),
                "DEBUG",
            )

            # Check if the access point profile exists in the existing access point profiles
            if profile_name in existing_profiles_dict:
                # Add the requested access point profile with the ID from the existing profile
                existing_profile = existing_profiles_dict[profile_name]
                profile_to_delete = requested_profile.copy()
                profile_to_delete["id"] = existing_profile.get("id")
                delete_access_point_profiles_list.append(profile_to_delete)
                self.log(
                    "Access Point Profile '{0}' scheduled for deletion.".format(
                        profile_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Deletion not required for access point profile '{0}'. It does not exist.".format(
                        profile_name
                    ),
                    "INFO",
                )

        self.log(
            "Access Point Profiles scheduled for deletion: {0} - {1}".format(
                len(delete_access_point_profiles_list),
                delete_access_point_profiles_list,
            ),
            "DEBUG",
        )

        # Return the list of profiles that need to be deleted
        return delete_access_point_profiles_list

    def create_access_point_profile(self, create_access_point_profile_params):
        """
        Initiates the creation of an access point profile using the provided parameters.
        Args:
            create_access_point_profile_params (dict): A dictionary containing parameters required for creating an access point profile.
        Returns:
            dict: The response containing the task ID for the create operation.
        """
        self.log(
            "Initiating addition of Access Point profiles with parameters: {0}".format(
                create_access_point_profile_params
            ),
            "INFO",
        )

        # Execute the API call to create the access point profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "create_ap_profile", create_access_point_profile_params
        )

    def update_access_point_profile(self, update_access_point_profile_params):
        """
        Initiates the update of an access point profile using the provided parameters.
        Args:
            update_access_point_profile_params (dict): A dictionary containing parameters required for updating an access point profile.
        Returns:
            dict: The response containing the task ID for the update operation.
        """
        self.log(
            "Initiating update Access Point profiles with parameters: {0}".format(
                update_access_point_profile_params
            ),
            "INFO",
        )

        # Execute the API call to update the access point profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_ap_profile_by_id", update_access_point_profile_params
        )

    def delete_access_point_profile(self, delete_access_point_profile_params):
        """
        Initiates the deletion of an access point profile using the provided parameters.
        Args:
            delete_access_point_profile_params (dict): A dictionary containing parameters required for deleting an access point profile.
        Returns:
            dict: The response containing the task ID for the delete operation.
        """
        self.log(
            "Initiating deletion of Access Point profiles with parameters: {0}".format(
                delete_access_point_profile_params
            ),
            "INFO",
        )

        # Execute the API call to delete the access point profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "delete_ap_profile_by_id", delete_access_point_profile_params
        )

    def process_access_point_profiles_common(
        self,
        access_point_profiles_params,
        create_or_update_or_delete_access_point_profiles,
        task_name,
    ):
        """
        Processes the access point profiles for the specified operation (create, update, delete).
        Args:
            access_point_profiles_params (list): A list of dictionaries containing parameters for each access point profile operation.
            create_or_update_or_delete_access_point_profiles (function): The function to execute for each access point profile operation.
            task_name (str): The name of the task being performed, For example, "Create Access Point Profile(s) Task".
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Initialize lists to track successful and failed profile operations
        failed_profiles = []
        success_profiles = []
        msg = {}

        # Iterate over each profile parameter set for processing
        for profile in access_point_profiles_params:
            profile_name = profile.get("apProfileName")
            self.log(
                "Processing access point profile: {0}".format(profile_name), "DEBUG"
            )

            # Prepare parameters for the operation
            if (
                create_or_update_or_delete_access_point_profiles
                == self.delete_access_point_profile
            ):
                # For delete operations, only the ID is needed
                operation_params = {"id": profile.get("id")}
            else:
                # For create or update operations, use the entire profile
                operation_params = profile

            # Execute the operation and retrieve the task ID
            task_id = create_or_update_or_delete_access_point_profiles(operation_params)
            self.log(
                "Task ID for access point profile '{0}': {1}".format(
                    profile_name, task_id
                ),
                "DEBUG",
            )

            # Construct operation message
            operation_msg = "{0} operation has completed successfully for access point profile: {1}.".format(
                task_name, profile_name
            )

            # Check the status of the operation using the task ID
            self.get_task_status_from_tasks_by_id(
                task_id, task_name, operation_msg
            ).check_return_status()

            # Determine if the operation was successful and categorize accordingly
            if self.status == "success":
                success_profiles.append(profile_name)
                self.log(
                    "Access Point Profile '{0}' processed successfully.".format(
                        profile_name
                    ),
                    "INFO",
                )
            else:
                failed_profiles.append(profile_name)
                self.log(
                    "Access Point Profile '{0}' failed to process.".format(
                        profile_name
                    ),
                    "ERROR",
                )

        if success_profiles:
            self.log(
                "{0} succeeded for the following access point profile(s): {1}".format(
                    task_name, ", ".join(success_profiles)
                ),
                "INFO",
            )
            msg[
                "{0} succeeded for the following access point profile(s)".format(
                    task_name
                )
            ] = {
                "success_count": len(success_profiles),
                "successful_access_point_profiles": success_profiles,
            }

        if failed_profiles:
            self.log(
                "{0} failed for the following access point profile(s): {1}".format(
                    task_name, ", ".join(failed_profiles)
                ),
                "ERROR",
            )
            msg[
                "{0} failed for the following access point profile(s)".format(task_name)
            ] = {
                "failed_count": len(failed_profiles),
                "failed_access_point_profiles": failed_profiles,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_profiles and failed_profiles:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_profiles:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_profiles:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def process_add_access_point_profiles(self, add_access_point_profiles_params):
        """
        Initiates the process to add access point profiles.
        This function sets up the task name for creating access point profiles and invokes the common processing function
        to handle the actual operation of adding profiles.
        Args:
            add_access_point_profiles_params (list): A list of dictionaries containing parameters for each access point profile to be added.
        Returns:
            The result of the process_access_point_profiles_common function call.
        """
        # Define the task name for logging and operation tracking
        task_name_create = "Create Access Point Profile(s) Task"

        self.log(
            "Starting the {0} with parameters: {1}".format(
                task_name_create, add_access_point_profiles_params
            ),
            "INFO",
        )

        # Call the common processing function with the create operation function
        return self.process_access_point_profiles_common(
            add_access_point_profiles_params,
            self.create_access_point_profile,
            task_name_create,
        )

    def process_update_access_point_profiles(self, update_access_point_profiles_params):
        """
        Initiates the process to update access point profiles.
        This function sets up the task name for updating access point profiles and invokes the common processing function
        to handle the actual operation of updating profiles.
        Args:
            update_access_point_profiles_params (list): A list of dictionaries containing parameters for each access point profile to be updated.
        Returns:
            The result of the process_access_point_profiles_common function call.
        """
        # Define the task name for logging and operation tracking
        task_name_update = "Update Access Point Profile(s) Task"

        self.log(
            "Starting the {0} with parameters: {1}".format(
                task_name_update, update_access_point_profiles_params
            ),
            "INFO",
        )

        # Call the common processing function with the update operation function
        return self.process_access_point_profiles_common(
            update_access_point_profiles_params,
            self.update_access_point_profile,
            task_name_update,
        )

    def process_delete_access_point_profiles(self, delete_access_point_profiles_params):
        """
        Initiates the process to delete access point profiles.
        This function sets up the task name for deleting access point profiles and invokes the common processing function
        to handle the actual operation of deleting profiles.
        Args:
            delete_access_point_profiles_params (list): A list of dictionaries containing parameters for each access point profile to be deleted.
        Returns:
            The result of the process_access_point_profiles_common function call.
        """
        # Define the task name for logging and operation tracking
        task_name_delete = "Delete Access Point Profile(s) Task"

        self.log(
            "Starting the {0} with parameters: {1}".format(
                task_name_delete, delete_access_point_profiles_params
            ),
            "INFO",
        )

        # Call the common processing function with the delete operation function
        return self.process_access_point_profiles_common(
            delete_access_point_profiles_params,
            self.delete_access_point_profile,
            task_name_delete,
        )

    def verify_add_access_point_profiles_operation(
        self, add_access_point_profiles_params
    ):
        """
        Verifies whether the access point profiles specified in add_access_point_profiles_params have been successfully created.
        Args:
            add_access_point_profiles_params (list): A list of dictionaries containing the requested access point profile parameters to be added.
        Returns:
            tuple: Two lists containing successfully created access point profiles and failed profiles.
        """
        # Retrieve all existing access point profiles to verify against
        existing_access_point_profiles = self.get_access_point_profiles(
            get_access_point_profiles_params={}
        )
        self.log("Retrieved existing access point profiles.", "DEBUG")

        self.log(
            "Existing Access Point Profiles: {0}".format(
                existing_access_point_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Requested Access Point Profiles to Add: {0}".format(
                add_access_point_profiles_params
            ),
            "DEBUG",
        )

        # Initialize lists to track successful and failed profile additions
        successful_profiles = []
        failed_profiles = []

        # Convert existing access point profiles to a set for quick lookup by profile name
        existing_profiles_set = {
            profile["apProfileName"] for profile in existing_access_point_profiles
        }
        self.log("Converted existing profiles to a set for quick lookup.", "DEBUG")

        # Iterate over the requested access point profiles to verify their creation
        for requested_profile in add_access_point_profiles_params:
            profile_name = requested_profile["apProfileName"]
            self.log(
                "Verifying creation for profile: {0}".format(profile_name), "DEBUG"
            )

            # Check if the profile now exists in the existing profiles
            if profile_name in existing_profiles_set:
                # Profile exists, add to successful list
                successful_profiles.append(profile_name)
                self.log(
                    "Access Point Profile '{0}' has been successfully created.".format(
                        profile_name
                    ),
                    "INFO",
                )
            else:
                # Profile does not exist, add to failed list
                failed_profiles.append(profile_name)
                self.log(
                    "Access Point Profile '{0}' failed to create.".format(profile_name),
                    "ERROR",
                )

        if failed_profiles:
            self.log(
                "The ADD Access Point Profile(s) operation may not have been successful since some profiles were not successfully created: {0}".format(
                    failed_profiles
                ),
                "WARNING",
            )
        else:
            self.log(
                "Verified the success of ADD Access Point Profile(s) operation for parameters: {0}".format(
                    add_access_point_profiles_params
                ),
                "INFO",
            )

    def verify_update_access_point_profiles_operation(
        self, update_access_point_profiles_params
    ):
        """
        Verifies whether the access point profiles specified in update_access_point_profiles_params have been successfully updated.
        Args:
            update_access_point_profiles_params (list): A list of dictionaries containing the requested access point profile parameters to be updated.
        Returns:
            tuple: Two lists containing successfully updated access point profiles and failed profiles.
        """
        # Retrieve all existing access point profiles
        existing_access_point_profiles = self.get_access_point_profiles(
            get_access_point_profiles_params={}
        )
        self.log("Retrieved existing access point profiles.", "DEBUG")

        self.log(
            "Existing Access Point Profiles: {0}".format(
                existing_access_point_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Requested Access Point Profiles to Update: {0}".format(
                update_access_point_profiles_params
            ),
            "DEBUG",
        )

        # Initialize lists to track successful and failed profile updates
        successful_updates = []
        failed_updates = []

        # Convert existing access point profiles to a dictionary for quick lookup by profile name
        existing_profiles_dict = {
            profile["apProfileName"]: profile
            for profile in existing_access_point_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested access point profiles to verify updates
        for requested_profile in update_access_point_profiles_params:
            profile_name = requested_profile["apProfileName"]
            self.log("Verifying update for profile: {0}".format(profile_name), "DEBUG")

            # Check if the profile exists in the existing profiles
            if profile_name in existing_profiles_dict:
                existing_profile = existing_profiles_dict[profile_name]

                # Flag to determine if the update was successful
                update_successful = True

                # Iterate over each requested parameter to verify if the update was applied
                for key, requested_value in requested_profile.items():
                    if key in existing_profile:
                        existing_value = existing_profile[key]

                        # Special handling for management_settings
                        if key == "managementSetting":
                            # Skip verification for sensitive keys within management_settings
                            sensitive_keys = [
                                "dot1xPassword",
                                "managementPassword",
                                "managementEnablePassword",
                            ]
                            for sub_key, sub_value in requested_value.items():
                                if sub_key in sensitive_keys:
                                    continue
                                if not self.compare_values(
                                    sub_value, existing_value.get(sub_key)
                                ):
                                    update_successful = False
                                    self.log(
                                        "Mismatch in management setting '{0}' for profile '{1}'. Requested value: {2}, Existing value: {3}".format(
                                            sub_key,
                                            profile_name,
                                            sub_value,
                                            existing_value.get(sub_key),
                                        ),
                                        "ERROR",
                                    )
                                    break

                        # Use the compare_values method to compare the requested and existing values for other keys
                        elif not self.compare_values(requested_value, existing_value):
                            update_successful = False
                            self.log(
                                "Mismatch in parameter '{0}' for profile '{1}'. Requested value: {2}, Existing value: {3}".format(
                                    key, profile_name, requested_value, existing_value
                                ),
                                "ERROR",
                            )
                            break

                if update_successful:
                    successful_updates.append(profile_name)
                    self.log(
                        "Access Point Profile '{0}' has been successfully updated.".format(
                            profile_name
                        ),
                        "INFO",
                    )
                else:
                    failed_updates.append(profile_name)
                    self.log(
                        "Access Point Profile '{0}' failed to update.".format(
                            profile_name
                        ),
                        "ERROR",
                    )
            else:
                failed_updates.append(profile_name)
                self.log(
                    "Access Point Profile '{0}' does not exist and cannot be updated.".format(
                        profile_name
                    ),
                    "ERROR",
                )

        if failed_updates:
            self.log(
                "The UPDATE Access Point Profiles operation may not have been successful. The following access point profiles failed verification: {0}.".format(
                    failed_updates
                ),
                "ERROR",
            )
        else:
            self.log(
                "Successfully verified the UPDATE Access Point Profiles operation for the following profiles: {0}.".format(
                    successful_updates
                ),
                "INFO",
            )

    def verify_delete_access_point_profiles_operation(
        self, delete_access_point_profiles_params
    ):
        """
        Verifies whether the access point profiles specified in delete_access_point_profiles_params have been successfully deleted.
        Args:
            delete_access_point_profiles_params (list): A list of dictionaries containing the requested access point profile names to be deleted.
        Returns:
            bool: True if all requested access point profiles were successfully deleted, False otherwise.
        """
        # Retrieve all existing access point profiles
        existing_access_point_profiles = self.get_access_point_profiles(
            get_access_point_profiles_params={}
        )
        existing_profiles_set = {
            profile["apProfileName"] for profile in existing_access_point_profiles
        }
        self.log("Retrieved existing access point profiles.", "DEBUG")

        self.log(
            "Current Access Point Profiles after DELETE operation: {0}".format(
                existing_profiles_set
            ),
            "INFO",
        )
        self.log(
            "Requested Access Point Profiles to Delete: {0}".format(
                delete_access_point_profiles_params
            ),
            "INFO",
        )

        # Initialize a list to track profiles that failed deletion
        failed_deletions = []

        # Iterate over the requested access point profiles to verify deletion
        for requested_profile in delete_access_point_profiles_params:
            profile_name = requested_profile["apProfileName"]
            self.log(
                "Verifying deletion for profile: {0}".format(profile_name), "DEBUG"
            )

            # Check if the profile still exists in the existing profiles
            if profile_name in existing_profiles_set:
                # If it exists, the deletion failed
                failed_deletions.append(profile_name)
                self.log(
                    "Delete operation failed for Access Point Profile '{0}'. It still exists.".format(
                        profile_name
                    ),
                    "ERROR",
                )

        if failed_deletions:
            self.log(
                "The DELETE Access Point Profile(s) operation may not have been successful since some Access Point Profiles still exist: {0}.".format(
                    failed_deletions
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of DELETE Access Point Profile(s) operation for the following parameters: {0}.".format(
                    delete_access_point_profiles_params
                ),
                "INFO",
            )

    def get_radio_frequency_profiles_params(self, radio_frequency_profile_name=None):
        """
        Constructs and returns a dictionary of parameters for retrieving radio frequency profiles.
        Args:
            radio_frequency_profile_name (str, optional): The name of the radio frequency profile to filter the retrieval.
        Returns:
            dict: A dictionary containing parameters to be used for API calls to retrieve radio frequency profiles.
        """
        # Initialize an empty dictionary to hold the parameters for the API call
        get_radio_frequency_profiles_params = {}

        # Map the user-provided radio frequency profile name to the expected API parameter
        if radio_frequency_profile_name:
            get_radio_frequency_profiles_params["rf_profile_name"] = (
                radio_frequency_profile_name
            )
            self.log(
                "Added 'rf_profile_name' to parameters: {0}".format(
                    radio_frequency_profile_name
                ),
                "DEBUG",
            )
        else:
            self.log("No specific radio frequency profile name provided.", "DEBUG")

        # Return the constructed parameters dictionary
        self.log(
            "Constructed get_radio_frequency_profiles_params: {0}".format(
                get_radio_frequency_profiles_params
            ),
            "DEBUG",
        )
        return get_radio_frequency_profiles_params

    def get_radio_frequency_profiles(self, get_radio_frequency_profiles_params):
        """
        Retrieves radio frequency profile details using pagination.
        Args:
            get_radio_frequency_profiles_params (dict): Parameters for filtering the radio frequency profiles.
        Returns:
            list: A list of dictionaries containing details of radio frequency profiles based on the filtering parameters.
        """
        # Execute the paginated API call to retrieve radio frequency profiles
        self.log(
            "Executing paginated API call to retrieve radio frequency profiles.",
            "DEBUG",
        )
        return self.execute_get_with_pagination(
            "wireless", "get_rf_profiles", get_radio_frequency_profiles_params
        )

    def unset_existing_default_rf_profile(self, existing_rf_profiles):
        """
        Unsets the existing default RF profile if one is found in the configuration.
        Args:
            existing_rf_profiles (list): A list of dictionaries containing details of existing RF profiles.
                Each dictionary should include the "defaultRfProfile" key to indicate if the profile is set as default.
        Raises:
            Exception: If the operation to unset the default RF profile fails, an exception is raised with a descriptive message.
        """
        self.log("Checking for an existing default RF profile to unset.", "INFO")

        # Find the existing default RF profile
        existing_default_profile = next(
            (profile for profile in existing_rf_profiles if profile.get("defaultRfProfile", False)), None
        )

        if not existing_default_profile:
            self.log("No existing default RF profile found. No action required.", "INFO")
            return

        profile_name = existing_default_profile.get("rfProfileName", "Unknown")
        self.log(
            "Found an existing default RF profile: {0}. Proceeding to unset it.".format(profile_name),
            "INFO"
        )

        # Unset the default RF profile
        existing_default_profile["defaultRfProfile"] = False
        task_id = self.update_radio_frequency_profile(existing_default_profile)

        # Verify the update operation
        task_name = "Unset Default RF Profile"
        operation_msg = "Successfully unset the default RF profile: {0}".format(profile_name)
        self.log(
            "Initiated task '{0}' to unset the default RF profile. Task ID: {1}".format(task_name, task_id),
            "DEBUG"
        )

        # Check the status of the task using the task ID
        self.get_task_status_from_tasks_by_id(task_id, task_name, operation_msg).check_return_status()

        if self.status != "success":
            self.fail_and_exit("Failed to unset the default RF profile: {0}".format(profile_name))

        self.log(
            "Successfully unset the default RF profile: {0}".format(profile_name),
            "INFO"
        )

    def verify_create_update_radio_frequency_profiles_requirement(
        self, radio_frequency_profiles
    ):
        """
        Determines whether radio frequency profiles need to be created, updated, or require no updates.
        Args:
            radio_frequency_profiles (list): A list of dictionaries containing the requested radio frequency profile parameters.
        Returns:
            tuple: Three lists containing radio frequency profiles to be created, updated, and not updated.
        """
        # Update requested profiles with API-compatible values
        updated_radio_frequency_profiles = self.map_radio_frequency_profiles_params(
            radio_frequency_profiles
        )
        self.log(
            "Updated radio frequency profiles: {0}".format(
                updated_radio_frequency_profiles
            ),
            "DEBUG",
        )

        # Retrieve all existing radio frequency profiles from the system
        existing_rf_profiles = self.get_radio_frequency_profiles(
            get_radio_frequency_profiles_params={}
        )

        self.log(
            "Existing Radio Frequency Profiles: {0}".format(existing_rf_profiles),
            "DEBUG",
        )
        self.log(
            "Requested Radio Frequency Profiles: {0}".format(
                updated_radio_frequency_profiles
            ),
            "DEBUG",
        )

        # Check if a default RF profile is marked in the configuration
        if getattr(self, "is_default_rf_profile_in_config", False):
            self.log("A default RF profile is marked in the configuration. Checking existing RF profiles.", "INFO")
            self.unset_existing_default_rf_profile(existing_rf_profiles)
        else:
            self.log("The 'is_default_rf_profile_in_config' is set to False. Skipping default RF profile operations.", "INFO")

        # Initialize lists to store profiles that need to be created, updated, or not changed
        create_profiles = []
        update_profiles = []
        no_update_profiles = []

        # Create a dictionary of existing profiles for quick lookup using the profile name
        existing_profiles_dict = {
            profile["rfProfileName"]: profile for profile in existing_rf_profiles
        }

        # Iterate over the updated requested radio frequency profiles
        self.log(
            "Starting to iterate over updated requested radio frequency profiles.",
            "DEBUG",
        )
        for requested_profile in updated_radio_frequency_profiles:
            profile_name = requested_profile["rfProfileName"]
            self.log("Processing profile: {0}".format(profile_name), "DEBUG")
            update_needed = False

            # Check if the profile already exists
            if profile_name in existing_profiles_dict:
                self.log(
                    "Profile '{0}' exists in the existing profiles.".format(
                        profile_name
                    ),
                    "DEBUG",
                )
                existing_profile = existing_profiles_dict[profile_name]

                # Iterate over each top-level parameter in the requested profile
                for key, requested_value in requested_profile.items():
                    self.log(
                        "Checking parameter '{0}' for profile '{1}'.".format(
                            key, profile_name
                        ),
                        "DEBUG",
                    )
                    if key in existing_profile:
                        existing_value = existing_profile[key]
                        self.log(
                            "Found existing value for parameter '{0}' in profile '{1}': {2}".format(
                                key, profile_name, existing_value
                            ),
                            "DEBUG",
                        )

                        # Check if the value is a dictionary containing specific properties
                        if isinstance(requested_value, dict) and isinstance(
                            existing_value, dict
                        ):
                            self.log(
                                "Parameter '{0}' is a dictionary. Checking all sub-keys.".format(
                                    key
                                ),
                                "DEBUG",
                            )
                            for sub_key, sub_requested_value in requested_value.items():
                                if sub_key in existing_value:
                                    sub_existing_value = existing_value[sub_key]
                                    self.log(
                                        "Checking sub-key '{0}' in parameter '{1}'.".format(
                                            sub_key, key
                                        ),
                                        "DEBUG",
                                    )

                                    # Special handling for comma-separated string of numbers
                                    if sub_key in [
                                        "radioChannels",
                                        "dataRates",
                                        "mandatoryDataRates",
                                    ]:
                                        requested_sorted = sorted(
                                            map(float, sub_requested_value.split(","))
                                        )
                                        existing_sorted = sorted(
                                            map(float, sub_existing_value.split(","))
                                        )
                                        self.log(
                                            "Sorted requested values for '{0}.{1}': {2}".format(
                                                key, sub_key, requested_sorted
                                            ),
                                            "DEBUG",
                                        )
                                        self.log(
                                            "Sorted existing values for '{0}.{1}': {2}".format(
                                                key, sub_key, existing_sorted
                                            ),
                                            "DEBUG",
                                        )
                                        if requested_sorted != existing_sorted:
                                            update_needed = True
                                            self.log(
                                                "Mismatch found in parameter '{0}.{1}' for profile '{2}'. "
                                                "Requested value: {3}, Existing value: {4}".format(
                                                    key,
                                                    sub_key,
                                                    profile_name,
                                                    requested_sorted,
                                                    existing_sorted,
                                                ),
                                                "DEBUG",
                                            )
                                            break
                                    else:
                                        # Standard comparison for other sub-keys
                                        if not self.compare_values(
                                            sub_requested_value, sub_existing_value
                                        ):
                                            update_needed = True
                                            self.log(
                                                "Mismatch found in parameter '{0}.{1}' for profile '{2}'. "
                                                "Requested value: {3}, Existing value: {4}".format(
                                                    key,
                                                    sub_key,
                                                    profile_name,
                                                    sub_requested_value,
                                                    sub_existing_value,
                                                ),
                                                "DEBUG",
                                            )
                                            break
                        else:
                            # Compare requested and existing values using compare_values
                            if not self.compare_values(requested_value, existing_value):
                                update_needed = True
                                self.log(
                                    "Mismatch found in parameter '{0}' for profile '{1}'. "
                                    "Requested value: {2}, Existing value: {3}".format(
                                        key,
                                        profile_name,
                                        requested_value,
                                        existing_value,
                                    ),
                                    "DEBUG",
                                )
                                break

                if update_needed:
                    # Copy the existing profile and update it with the requested values
                    updated_profile = existing_profile.copy()
                    self.recursive_update(updated_profile, requested_profile)
                    update_profiles.append(updated_profile)
                    self.log(
                        "Profile '{0}' marked for update.".format(profile_name), "DEBUG"
                    )
                else:
                    # No changes needed for this profile
                    no_update_profiles.append(existing_profile)
                    self.log(
                        "Profile '{0}' requires no updates.".format(profile_name),
                        "DEBUG",
                    )
            else:
                # The profile does not exist and needs to be created
                create_profiles.append(requested_profile)
                self.log(
                    "Profile '{0}' marked for creation.".format(profile_name), "DEBUG"
                )

        self.log(
            "Radio Frequency Profiles that need to be CREATED: {0} - {1}".format(
                len(create_profiles), create_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Radio Frequency Profiles that need to be UPDATED: {0} - {1}".format(
                len(update_profiles), update_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Radio Frequency Profiles that DON'T NEED UPDATES: {0} - {1}".format(
                len(no_update_profiles), no_update_profiles
            ),
            "DEBUG",
        )

        # Validate that the total number of processed profiles matches the number of requested profiles
        total_profiles_processed = (
            len(create_profiles) + len(update_profiles) + len(no_update_profiles)
        )
        if total_profiles_processed == len(updated_radio_frequency_profiles):
            self.log(
                "Match in total counts: Processed={0}, Requested={1}.".format(
                    total_profiles_processed, len(updated_radio_frequency_profiles)
                ),
                "DEBUG",
            )
        else:
            self.log(
                "Mismatch in total counts: Processed={0}, Requested={1}.".format(
                    total_profiles_processed, len(updated_radio_frequency_profiles)
                ),
                "ERROR",
            )

        # Return the categorized profiles
        return create_profiles, update_profiles, no_update_profiles

    def verify_delete_radio_frequency_profiles_requirement(self, access_point_profiles):
        """
        Determines which radio frequency profiles need to be deleted based on the requested parameters.
        Args:
            access_point_profiles (list): A list of dictionaries containing the requested radio frequency profile parameters for deletion.
        Returns:
            list: A list of radio frequency profiles that need to be deleted, including their IDs.
        """
        # Initialize an empty list to store the profiles that need to be deleted
        delete_rf_profiles_list = []

        self.log(
            "Starting verification of radio frequency profiles for deletion.", "INFO"
        )

        # Retrieve all existing radio frequency profiles
        existing_rf_profiles = self.get_radio_frequency_profiles(
            get_radio_frequency_profiles_params={}
        )
        self.log("Retrieved existing radio frequency profiles.", "DEBUG")
        self.log(
            "Existing Radio Frequency Profiles: {0}".format(existing_rf_profiles),
            "DEBUG",
        )

        # Convert existing radio frequency profiles to a dictionary for quick lookup by profile name
        existing_profiles_dict = {
            profile["rfProfileName"]: profile for profile in existing_rf_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested radio frequency profiles
        self.log(
            "Iterating over requested radio frequency profiles for deletion.", "DEBUG"
        )
        for requested_profile in access_point_profiles:
            profile_name = requested_profile.get("radio_frequency_profile_name")
            self.log("Processing requested profile: {0}".format(profile_name), "DEBUG")

            # Check if the radio frequency profile exists in the existing radio frequency profiles
            if profile_name in existing_profiles_dict:
                self.log(
                    "Profile '{0}' found in existing profiles, scheduling for deletion.".format(
                        profile_name
                    ),
                    "DEBUG",
                )
                # Add the requested radio frequency profile with the ID from the existing profile
                existing_profile = existing_profiles_dict[profile_name]
                profile_to_delete = requested_profile.copy()
                profile_to_delete["id"] = existing_profile.get("id")
                delete_rf_profiles_list.append(profile_to_delete)
                self.log(
                    "Radio Frequency Profile '{0}' scheduled for deletion.".format(
                        profile_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Deletion not required for radio frequency profile '{0}'. It does not exist.".format(
                        profile_name
                    ),
                    "INFO",
                )

        self.log(
            "Radio Frequency Profiles scheduled for deletion: {0} - {1}".format(
                len(delete_rf_profiles_list), delete_rf_profiles_list
            ),
            "DEBUG",
        )

        self.log(
            "Completed verification of radio frequency profiles for deletion.", "INFO"
        )

        # Return the list of profiles that need to be deleted
        return delete_rf_profiles_list

    def create_radio_frequency_profile(self, create_radio_frequency_profile_params):
        """
        Initiates the creation of a new Radio Frequency profile using the provided parameters.
        Args:
            create_radio_frequency_profile_params (dict): A dictionary containing parameters required
                for creating a new Radio Frequency profile.
        Returns:
            dict: Response from the API call, including task ID for the create operation.
        """
        self.log(
            "Initiating addition of Radio Frequency profile with parameters: {0}".format(
                create_radio_frequency_profile_params
            ),
            "INFO",
        )

        # Call the API to create an RF profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "create_rf_profile", create_radio_frequency_profile_params
        )

    def update_radio_frequency_profile(self, update_radio_frequency_profile_params):
        """
        Initiates the update of an existing Radio Frequency profile using the provided parameters.
        Args:
            update_radio_frequency_profile_params (dict): A dictionary containing parameters required
                for updating an existing Radio Frequency profile.
        Returns:
            dict: Response from the API call, including task ID for the update operation.
        """
        self.log(
            "Initiating update Radio Frequency profile with parameters: {0}".format(
                update_radio_frequency_profile_params
            ),
            "INFO",
        )

        # Call the API to update the RF profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_rf_profile", update_radio_frequency_profile_params
        )

    def delete_radio_frequency_profile(self, delete_radio_frequency_profile_params):
        """
        Initiates the deletion of a Radio Frequency profile using the provided parameters.
        Args:
            delete_radio_frequency_profile_params (dict): A dictionary containing parameters required
                for deleting a Radio Frequency profile.
        Returns:
            dict: Response from the API call, including task ID for the delete operation.
        """
        self.log(
            "Initiating deletion of Radio Frequency profile with parameters: {0}".format(
                delete_radio_frequency_profile_params
            ),
            "INFO",
        )

        # Call the API to delete the RF profile and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "delete_rf_profile", delete_radio_frequency_profile_params
        )

    def map_radio_frequency_profiles_params(self, radio_frequency_profiles):
        """
        Maps radio frequency profile parameters from the input list of profiles to a new format.
        Args:
            radio_frequency_profiles (list): A list of dictionaries, where each dictionary contains
                radio frequency profile settings including radio bands and other configurations.
        Returns:
            list: A list of mapped radio frequency profiles with translated parameter names and structures.
        """
        # Initialize an empty list to store the mapped profiles
        mapped_profiles = []

        # Iterate over each radio frequency profile in the input list
        for profile in radio_frequency_profiles:
            # Extract radio bands from the current profile
            radio_bands = profile.get("radio_bands", [])

            # Create a new dictionary with mapped profile parameters
            mapped_profile = {
                "rfProfileName": profile.get("radio_frequency_profile_name"),
                "defaultRfProfile": profile.get("default_rf_profile"),
                "enableRadioTypeA": 5 in radio_bands,
                "enableRadioTypeB": 2.4 in radio_bands,
                "enableRadioType6GHz": 6 in radio_bands,
            }

            def map_band_settings(band_settings):
                """Maps individual band settings to their corresponding new format."""
                # If band settings are not provided, return None
                mapped = {}

                if not band_settings:
                    return mapped

                # Define the mapping from band settings keys to target keys
                band_mapping = {
                    "parent_profile": "parentProfile",
                    "dca_channels_list": "radioChannels",
                    "supported_data_rates_list": "dataRates",
                    "mandatory_data_rates_list": "mandatoryDataRates",
                    "minimum_power_level": "minPowerLevel",
                    "maximum_power_level": "maxPowerLevel",
                    "rx_sop_threshold": "rxSopThreshold",
                    "custom_rx_sop_threshold": "customRxSopThreshold",
                    "tpc_power_threshold": "powerThresholdV1",
                    "client_limit": "maxRadioClients",
                    "channel_width": "channelWidth",
                    "minimum_dbs_channel_width": "minDbsWidth",
                    "maximum_dbs_channel_width": "maxDbsWidth",
                    "preamble_puncturing": "preamblePuncture",
                    "psc_enforcing_enabled": "pscEnforcingEnabled",
                    "zero_wait_dfs": "zeroWaitDfsEnable",
                    "discovery_frames_6ghz": "discoveryFrames6GHz",
                    "standard_power_service": "enableStandardPowerService",
                    "broadcast_probe_response_interval": "broadcastProbeResponseInterval",
                }

                # Initialize the mapped dictionary
                self.log("Initializing the mapped dictionary.", "DEBUG")

                # Iterate over each band setting and map them if present
                self.log("Starting to map band settings.", "DEBUG")
                for key, target_key in band_mapping.items():
                    if key in band_settings:
                        # Special handling for lists that need to be joined into strings
                        if key in [
                            "dca_channels_list",
                            "supported_data_rates_list",
                            "mandatory_data_rates_list",
                        ]:
                            mapped[target_key] = ",".join(map(str, band_settings[key]))
                            self.log(
                                "Joined list for '{0}' and mapped to '{1}' with value: {2}.".format(
                                    key, target_key, mapped[target_key]
                                ),
                                "DEBUG",
                            )
                        else:
                            mapped[target_key] = band_settings[key]
                            self.log(
                                "Mapped '{0}' to '{1}' with value: {2}.".format(
                                    key, target_key, mapped[target_key]
                                ),
                                "DEBUG",
                            )
                    else:
                        self.log(
                            "Key '{0}' not found in band_settings.".format(key),
                            "WARNING",
                        )

                # Define mappings for nested structures
                self.log("Defining mappings for nested structures.", "DEBUG")

                spatial_reuse_mapping = {
                    "non_srg_obss_pd": "dot11axNonSrgObssPacketDetect",
                    "non_srg_obss_pd_max_threshold": "dot11axNonSrgObssPacketDetectMaxThreshold",
                    "srg_obss_pd": "dot11axSrgObssPacketDetect",
                    "srg_obss_pd_min_threshold": "dot11axSrgObssPacketDetectMinThreshold",
                    "srg_obss_pd_max_threshold": "dot11axSrgObssPacketDetectMaxThreshold",
                }
                self.log("Spatial reuse mapping defined.", "DEBUG")

                coverage_hole_detection_mapping = {
                    "minimum_client_level": "chdClientLevel",
                    "data_rssi_threshold": "chdDataRssiThreshold",
                    "voice_rssi_threshold": "chdVoiceRssiThreshold",
                    "exception_level": "chdExceptionLevel",
                }
                self.log("Coverage hole detection mapping defined.", "DEBUG")

                dot_11ax_parameters_mapping = {
                    "mu_mimo_downlink": "muMimoDownLink",
                    "mu_mimo_uplink": "muMimoUpLink",
                    "ofdma_downlink": "ofdmaDownLink",
                    "ofdma_uplink": "ofdmaUpLink",
                }
                self.log("Dot 11ax parameters mapping defined.", "DEBUG")

                dot_11be_parameters_mapping = {
                    "mu_mimo_downlink": "muMimoDownLink",
                    "mu_mimo_uplink": "muMimoUpLink",
                    "ofdma_downlink": "ofdmaDownLink",
                    "ofdma_uplink": "ofdmaUpLink",
                    "ofdma_multi_ru": "ofdmaMultiRu",
                }
                self.log("Dot 11be parameters mapping defined.", "DEBUG")

                # Process spatial reuse settings
                if "spatial_reuse" in band_settings:
                    self.log("Processing spatial reuse settings.", "DEBUG")
                    mapped["spatialReuseProperties"] = {}
                    for key, target_key in spatial_reuse_mapping.items():
                        if key in band_settings["spatial_reuse"]:
                            mapped["spatialReuseProperties"][target_key] = (
                                band_settings["spatial_reuse"][key]
                            )
                            self.log(
                                "Mapped spatial reuse '{0}' to '{1}' with value: {2}.".format(
                                    key,
                                    target_key,
                                    mapped["spatialReuseProperties"][target_key],
                                ),
                                "DEBUG",
                            )

                # Process coverage hole detection settings
                if "coverage_hole_detection" in band_settings:
                    self.log("Processing coverage hole detection settings.", "DEBUG")
                    mapped["coverageHoleDetectionProperties"] = {}
                    for key, target_key in coverage_hole_detection_mapping.items():
                        if key in band_settings["coverage_hole_detection"]:
                            mapped["coverageHoleDetectionProperties"][target_key] = (
                                band_settings["coverage_hole_detection"][key]
                            )
                            self.log(
                                "Mapped coverage hole detection '{0}' to '{1}' with value: {2}.".format(
                                    key,
                                    target_key,
                                    mapped["coverageHoleDetectionProperties"][
                                        target_key
                                    ],
                                ),
                                "DEBUG",
                            )

                # Process multi-bssid settings
                if "multi_bssid" in band_settings:
                    self.log("Processing multi-bssid settings.", "DEBUG")
                    mapped["multiBssidProperties"] = {}

                    if "dot_11ax_parameters" in band_settings["multi_bssid"]:
                        self.log("Processing dot 11ax parameters.", "DEBUG")
                        mapped["multiBssidProperties"]["dot11axParameters"] = {}
                        for key, target_key in dot_11ax_parameters_mapping.items():
                            if (
                                key
                                in band_settings["multi_bssid"]["dot_11ax_parameters"]
                            ):
                                mapped["multiBssidProperties"]["dot11axParameters"][
                                    target_key
                                ] = band_settings["multi_bssid"]["dot_11ax_parameters"][
                                    key
                                ]
                                self.log(
                                    "Mapped dot_11ax '{0}' to '{1}' with value: {2}.".format(
                                        key,
                                        target_key,
                                        mapped["multiBssidProperties"][
                                            "dot11axParameters"
                                        ][target_key],
                                    ),
                                    "DEBUG",
                                )

                    if "dot_11be_parameters" in band_settings["multi_bssid"]:
                        self.log("Processing dot 11be parameters.", "DEBUG")
                        mapped["multiBssidProperties"]["dot11beParameters"] = {}
                        for key, target_key in dot_11be_parameters_mapping.items():
                            if (
                                key
                                in band_settings["multi_bssid"]["dot_11be_parameters"]
                            ):
                                mapped["multiBssidProperties"]["dot11beParameters"][
                                    target_key
                                ] = band_settings["multi_bssid"]["dot_11be_parameters"][
                                    key
                                ]
                                self.log(
                                    "Mapped dot_11be '{0}' to '{1}' with value: {2}.".format(
                                        key,
                                        target_key,
                                        mapped["multiBssidProperties"][
                                            "dot11beParameters"
                                        ][target_key],
                                    ),
                                    "DEBUG",
                                )

                    # Additional mappings directly under multi_bssid
                    self.log("Processing additional multi-bssid settings.", "DEBUG")
                    additional_keys = ["twt_broadcast_support", "target_waketime"]
                    for key in additional_keys:
                        if key in band_settings["multi_bssid"]:
                            target_key = key.replace(
                                "twt_broadcast_support", "twtBroadcastSupport"
                            ).replace("target_waketime", "targetWakeTime")
                            mapped["multiBssidProperties"][target_key] = band_settings[
                                "multi_bssid"
                            ][key]
                            self.log(
                                "Mapped multi_bssid '{0}' to '{1}' with value: {2}.".format(
                                    key,
                                    target_key,
                                    mapped["multiBssidProperties"][target_key],
                                ),
                                "DEBUG",
                            )

                self.log("Completed mapping of band settings.", "DEBUG")
                return mapped

            def map_fra_settings(fra_settings, band_key, property_key, mapping):
                for fra_key, mapped_key in mapping.items():
                    if fra_key in fra_settings:
                        mapped_profile[band_key][property_key][mapped_key] = (
                            fra_settings[fra_key]
                        )

            fra_mapping_5ghz = {
                "client_aware": "clientAware",
                "client_select": "clientSelect",
                "client_reset": "clientReset",
            }

            fra_mapping_6ghz = {
                "client_reset_count": "clientResetCount",
                "client_utilization_threshold": "clientUtilizationThreshold",
            }

            # Map settings for 5GHz band if present
            if profile.get("radio_bands_5ghz_settings"):
                mapped_profile["radioTypeAProperties"] = map_band_settings(
                    profile.get("radio_bands_5ghz_settings")
                )

                # Check and map flexible radio assignment settings for 5GHz band
                if "flexible_radio_assignment" in profile.get(
                    "radio_bands_5ghz_settings", {}
                ):
                    fraA = profile["radio_bands_5ghz_settings"][
                        "flexible_radio_assignment"
                    ]
                    mapped_profile["radioTypeAProperties"]["fraPropertiesA"] = {}
                    map_fra_settings(
                        fraA, "radioTypeAProperties", "fraPropertiesA", fra_mapping_5ghz
                    )

            # Map settings for 2.4GHz band if present
            if profile.get("radio_bands_2_4ghz_settings"):
                mapped_profile["radioTypeBProperties"] = map_band_settings(
                    profile.get("radio_bands_2_4ghz_settings")
                )

            # Map settings for 6GHz band if present
            if profile.get("radio_bands_6ghz_settings"):
                mapped_profile["radioType6GHzProperties"] = map_band_settings(
                    profile.get("radio_bands_6ghz_settings")
                )

                # Check and map flexible radio assignment settings for 6GHz band
                if "flexible_radio_assignment" in profile.get(
                    "radio_bands_6ghz_settings", {}
                ):
                    fraC = profile["radio_bands_6ghz_settings"][
                        "flexible_radio_assignment"
                    ]
                    mapped_profile["radioType6GHzProperties"]["fraPropertiesC"] = {}
                    map_fra_settings(
                        fraC,
                        "radioType6GHzProperties",
                        "fraPropertiesC",
                        fra_mapping_6ghz,
                    )

            # Append the mapped profile to the list of mapped profiles
            mapped_profiles.append(mapped_profile)

        # Return the final list of mapped profiles
        return mapped_profiles

    def process_radio_frequency_profiles_common(
        self,
        radio_frequency_profiles_params,
        create_or_update_or_delete_radio_frequency_profiles,
        task_name,
    ):
        """
        Processes the radio frequency profiles for the specified operation (create, update, delete).
        Args:
            radio_frequency_profiles_params (list): A list of dictionaries containing parameters for each radio frequency profile operation.
            create_or_update_or_delete_radio_frequency_profiles (function): The function to execute for each radio frequency profile operation.
            task_name (str): The name of the task being performed, For example, "Create Radio Frequency Profile(s) Task".
        Returns:
            self: The current instance to allow for method chaining or further processing.
        """
        # Initialize lists to track successful and failed profile operations
        failed_profiles = []
        success_profiles = []
        msg = {}

        # Iterate over each profile parameter set for processing
        for index, profile in enumerate(radio_frequency_profiles_params, start=1):
            # Determine the profile name based on the operation type
            profile_name = (
                profile.get("radio_frequency_profile_name")
                if (
                    create_or_update_or_delete_radio_frequency_profiles
                    == self.delete_radio_frequency_profile
                )
                else profile.get("rfProfileName")
            )
            self.log(
                "Processing radio frequency profile {0}: {1}".format(
                    index, profile_name
                ),
                "DEBUG",
            )

            # Prepare parameters for the operation
            if (
                create_or_update_or_delete_radio_frequency_profiles
                == self.delete_radio_frequency_profile
            ):
                # For delete operations, only the ID is needed
                operation_params = {"id": profile.get("id")}
            else:
                # For create or update operations, use the entire profile
                operation_params = profile

            # Execute the operation and retrieve the task ID
            task_id = create_or_update_or_delete_radio_frequency_profiles(
                operation_params
            )
            self.log(
                "Task ID for radio frequency profile '{0}': {1}".format(
                    profile_name, task_id
                ),
                "DEBUG",
            )

            # Construct operation message
            operation_msg = "{0} operation has completed successfully for radio frequency profile: {1}.".format(
                task_name, profile_name
            )

            # Check the status of the operation using the task ID
            self.get_task_status_from_tasks_by_id(
                task_id, task_name, operation_msg
            ).check_return_status()

            # Determine if the operation was successful and categorize accordingly
            if self.status == "success":
                success_profiles.append(profile_name)
                self.log(
                    "Radio Frequency Profile '{0}' processed successfully.".format(
                        profile_name
                    ),
                    "INFO",
                )
            else:
                failed_profiles.append(profile_name)
                self.log(
                    "Radio Frequency Profile '{0}' failed to process.".format(
                        profile_name
                    ),
                    "ERROR",
                )

        if success_profiles:
            self.log(
                "{0} succeeded for the following radio frequency profile(s): {1}".format(
                    task_name, ", ".join(success_profiles)
                ),
                "INFO",
            )
            msg[
                "{0} succeeded for the following radio frequency profile(s)".format(
                    task_name
                )
            ] = {
                "success_count": len(success_profiles),
                "successful_radio_frequency_profiles": success_profiles,
            }

        if failed_profiles:
            self.log(
                "{0} failed for the following radio frequency profile(s): {1}".format(
                    task_name, ", ".join(failed_profiles)
                ),
                "ERROR",
            )
            msg[
                "{0} failed for the following radio frequency profile(s)".format(
                    task_name
                )
            ] = {
                "failed_count": len(failed_profiles),
                "failed_radio_frequency_profiles": failed_profiles,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_profiles and failed_profiles:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_profiles:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_profiles:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def process_add_radio_frequency_profiles(self, add_radio_frequency_profiles_params):
        """
        Initiates the creation of radio frequency profiles.
        Args:
            add_radio_frequency_profiles_params (list): A list of dictionaries containing the parameters for the radio frequency profiles to be added.
        Returns:
            tuple: A tuple containing the results of the add operation, typically indicating success or failure for each profile.
        """
        # Define the task name for creating radio frequency profiles
        task_name_create = "Create Radio Frequency Profile(s) Task"
        self.log(
            "Starting '{0}' with parameters: {1}".format(
                task_name_create, add_radio_frequency_profiles_params
            ),
            "INFO",
        )

        # Call the common processing function with the appropriate creation function and task name
        result = self.process_radio_frequency_profiles_common(
            add_radio_frequency_profiles_params,
            self.create_radio_frequency_profile,
            task_name_create,
        )

        self.log("Completed '{0}'.".format(task_name_create), "INFO")
        return result

    def process_update_radio_frequency_profiles(
        self, update_radio_frequency_profiles_params
    ):
        """
        Initiates the update of radio frequency profiles.
        Args:
            update_radio_frequency_profiles_params (list): A list of dictionaries containing the parameters for the radio frequency profiles to be updated.
        Returns:
            tuple: A tuple containing the results of the update operation, typically indicating success or failure for each profile.
        """
        # Define the task name for updating radio frequency profiles
        task_name_update = "Update Radio Frequency Profile(s) Task"
        self.log(
            "Starting '{0}' with parameters: {1}".format(
                task_name_update, update_radio_frequency_profiles_params
            ),
            "INFO",
        )

        # Call the common processing function with the appropriate update function and task name
        result = self.process_radio_frequency_profiles_common(
            update_radio_frequency_profiles_params,
            self.update_radio_frequency_profile,
            task_name_update,
        )

        self.log("Completed '{0}'.".format(task_name_update), "INFO")
        return result

    def process_delete_radio_frequency_profiles(
        self, delete_radio_frequency_profiles_params
    ):
        """
        Initiates the deletion of radio frequency profiles.
        Args:
            delete_radio_frequency_profiles_params (list): A list of dictionaries containing the parameters for the radio frequency profiles to be deleted.
        Returns:
            tuple: A tuple containing the results of the delete operation, typically indicating success or failure for each profile.
        """
        # Define the task name for deleting radio frequency profiles
        task_name_delete = "Delete Radio Frequency Profile(s) Task"
        self.log(
            "Starting '{0}' with parameters: {1}".format(
                task_name_delete, delete_radio_frequency_profiles_params
            ),
            "INFO",
        )

        # Call the common processing function with the appropriate deletion function and task name
        result = self.process_radio_frequency_profiles_common(
            delete_radio_frequency_profiles_params,
            self.delete_radio_frequency_profile,
            task_name_delete,
        )

        self.log("Completed '{0}'.".format(task_name_delete), "INFO")
        return result

    def verify_add_radio_frequency_profiles_operation(
        self, add_radio_frequency_profiles_params
    ):
        """
        Verifies whether the radio frequency profiles specified in add_radio_frequency_profiles_params have been successfully created.
        Args:
            add_radio_frequency_profiles_params (list): A list of dictionaries containing the requested radio frequency profile parameters to be added.
        Returns:
            tuple: Two lists containing successfully created radio frequency profiles and failed profiles.
        """
        self.log("Starting verification of radio frequency profile creation.", "INFO")

        # Retrieve all existing radio frequency profiles to verify against
        existing_radio_frequency_profiles = self.get_radio_frequency_profiles(
            get_radio_frequency_profiles_params={}
        )
        self.log(
            "Existing Radio Frequency Profiles: {0}".format(
                existing_radio_frequency_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Requested Radio Frequency Profiles to Add: {0}".format(
                add_radio_frequency_profiles_params
            ),
            "DEBUG",
        )

        # Initialize lists to track successful and failed profile additions
        successful_profiles = []
        failed_profiles = []

        # Convert existing radio frequency profiles to a set for quick lookup by profile name
        existing_profiles_set = {
            profile["rfProfileName"] for profile in existing_radio_frequency_profiles
        }
        self.log("Converted existing profiles to a set for quick lookup.", "DEBUG")

        # Iterate over the requested radio frequency profiles to verify their creation
        for index, requested_profile in enumerate(
            add_radio_frequency_profiles_params, start=1
        ):
            profile_name = requested_profile["rfProfileName"]
            self.log(
                "Iteration {0}: Verifying creation for Radio Frequency Profile '{1}'.".format(
                    index, profile_name
                ),
                "DEBUG",
            )

            # Check if the profile now exists in the existing profiles
            if profile_name in existing_profiles_set:
                # Profile exists, add to successful list
                successful_profiles.append(profile_name)
                self.log(
                    "Iteration {0}: Radio Frequency Profile '{1}' has been successfully created.".format(
                        index, profile_name
                    ),
                    "INFO",
                )
            else:
                # Profile does not exist, add to failed list
                failed_profiles.append(profile_name)
                self.log(
                    "Iteration {0}: Radio Frequency Profile '{1}' failed to create.".format(
                        index, profile_name
                    ),
                    "ERROR",
                )

        if failed_profiles:
            self.log(
                "The ADD Radio Frequency Profile(s) operation may not have been successful since some profiles were not successfully created: {0}".format(
                    failed_profiles
                ),
                "WARNING",
            )
        else:
            self.log(
                "Successfully verified the ADD Radio Frequency Profile(s) operation for parameters: {0}".format(
                    add_radio_frequency_profiles_params
                ),
                "INFO",
            )

    def verify_update_radio_frequency_profiles_operation(
        self, update_radio_frequency_profiles_params
    ):
        """
        Verifies whether the radio frequency profiles specified in update_radio_frequency_profiles_params have been successfully updated.
        Args:
            update_radio_frequency_profiles_params (list): A list of dictionaries containing the requested radio frequency profile parameters to be updated.
        Returns:
            tuple: Two lists containing successfully updated radio frequency profiles and failed profiles.
        """
        self.log("Starting verification of radio frequency profile updates.", "INFO")

        # Retrieve all existing radio frequency profiles from the system
        existing_radio_frequency_profiles = self.get_radio_frequency_profiles(
            get_radio_frequency_profiles_params={}
        )
        self.log(
            "Existing Radio Frequency Profiles: {0}".format(
                existing_radio_frequency_profiles
            ),
            "DEBUG",
        )
        self.log(
            "Requested Radio Frequency Profiles to Update: {0}".format(
                update_radio_frequency_profiles_params
            ),
            "DEBUG",
        )

        # Initialize lists to track successful and failed profile updates
        successful_updates = []
        failed_updates = []

        # Convert existing profiles to a dictionary for quick lookup by profile name
        existing_profiles_dict = {
            profile["rfProfileName"]: profile
            for profile in existing_radio_frequency_profiles
        }
        self.log(
            "Converted existing profiles to a dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested radio frequency profiles to verify updates
        for index, requested_profile in enumerate(
            update_radio_frequency_profiles_params, start=1
        ):
            profile_name = requested_profile["rfProfileName"]
            self.log(
                "Iteration {0}: Verifying update for Radio Frequency Profile '{1}'.".format(
                    index, profile_name
                ),
                "DEBUG",
            )

            # Check if the profile exists in the existing profiles
            if profile_name in existing_profiles_dict:
                existing_profile = existing_profiles_dict[profile_name]

                # Flag to determine if the update was successful
                update_successful = True

                # Iterate over each requested parameter to verify if the update was applied
                for key, requested_value in requested_profile.items():
                    if key in existing_profile:
                        existing_value = existing_profile[key]

                        # Compare the requested and existing values
                        if not self.compare_values(requested_value, existing_value):
                            update_successful = False
                            self.log(
                                "Mismatch in parameter '{0}' for profile '{1}'. Requested value: {2}, Existing value: {3}".format(
                                    key, profile_name, requested_value, existing_value
                                ),
                                "ERROR",
                            )
                            break

                if update_successful:
                    successful_updates.append(profile_name)
                    self.log(
                        "Iteration {0}: Radio Frequency Profile '{1}' has been successfully updated.".format(
                            index, profile_name
                        ),
                        "INFO",
                    )
                else:
                    failed_updates.append(profile_name)
                    self.log(
                        "Iteration {0}: Radio Frequency Profile '{1}' failed to update.".format(
                            index, profile_name
                        ),
                        "ERROR",
                    )
            else:
                failed_updates.append(profile_name)
                self.log(
                    "Iteration {0}: Radio Frequency Profile '{1}' does not exist and cannot be updated.".format(
                        index, profile_name
                    ),
                    "ERROR",
                )

        if failed_updates:
            self.log(
                "The UPDATE Radio Frequency Profiles operation may not have been successful. "
                "The following radio frequency profiles failed verification: {0}.".format(
                    failed_updates
                ),
                "ERROR",
            )
        else:
            self.log(
                "Successfully verified the UPDATE Radio Frequency Profiles operation for the following profiles: {0}.".format(
                    successful_updates
                ),
                "INFO",
            )

    def verify_delete_radio_frequency_profiles_operation(
        self, delete_radio_frequency_profiles_params
    ):
        """
        Verifies whether the radio frequency profiles specified in delete_radio_frequency_profiles_params have been successfully deleted.
        Args:
            delete_radio_frequency_profiles_params (list): A list of dictionaries containing the requested radio frequency profile names to be deleted.
        Returns:
            bool: True if all requested radio frequency profiles were successfully deleted, False otherwise.
        """
        self.log(
            "Starting verification of radio frequency profiles for deletion.", "INFO"
        )

        # Retrieve all existing radio frequency profiles from the system
        existing_radio_frequency_profiles = self.get_radio_frequency_profiles(
            get_radio_frequency_profiles_params={}
        )
        # Create a set of existing profile names for quick lookup
        existing_profiles_set = {
            profile["rfProfileName"] for profile in existing_radio_frequency_profiles
        }
        self.log(
            "Current Radio Frequency Profiles after DELETE operation: {0}".format(
                existing_profiles_set
            ),
            "DEBUG",
        )

        self.log(
            "Requested Radio Frequency Profiles to Delete: {0}".format(
                delete_radio_frequency_profiles_params
            ),
            "DEBUG",
        )

        # Initialize a list to track profiles that failed deletion
        failed_deletions = []

        # Iterate over the requested radio frequency profiles to verify their deletion
        for index, requested_profile in enumerate(
            delete_radio_frequency_profiles_params, start=1
        ):
            profile_name = requested_profile["radio_frequency_profile_name"]
            self.log(
                "Iteration {0}: Verifying deletion for Radio Frequency Profile '{1}'.".format(
                    index, profile_name
                ),
                "DEBUG",
            )

            # Check if the profile still exists in the existing profiles
            if profile_name in existing_profiles_set:
                # If it exists, the deletion failed
                failed_deletions.append(profile_name)
                self.log(
                    "Iteration {0}: Delete operation failed for Radio Frequency Profile '{1}'. It still exists.".format(
                        index, profile_name
                    ),
                    "ERROR",
                )

        if failed_deletions:
            self.log(
                "The DELETE Radio Frequency Profile(s) operation may not have been successful since some Radio Frequency Profiles still exist: {0}.".format(
                    failed_deletions
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of DELETE Radio Frequency Profile(s) operation for the following parameters: {0}.".format(
                    delete_radio_frequency_profiles_params
                ),
                "INFO",
            )

    def get_anchor_groups(self, get_anchor_groups_params):
        """
        Retrieves the anchor groups using the specified parameters and handles the API response.
        Args:
            get_anchor_groups_params (dict): Optional parameters for the GET request.
        Returns:
            list: A list of anchor group dictionaries if the request is successful.
        """
        self.log(
            "Initiating GET request for anchor groups with parameters: {0}".format(
                get_anchor_groups_params
            ),
            "INFO",
        )

        if self.compare_dnac_versions(self.get_ccc_version(), "2.3.7.9") <= 0:
            self.log("Using 'execute_get_request' for version <= 2.3.7.9", "DEBUG")
            # Execute the GET request to retrieve anchor groups
            api_response = self.execute_get_request(
                "wireless", "get_anchor_groups", get_anchor_groups_params
            )
            # Handle the case where api_response is None
            if api_response is None:
                self.log("API response is None. Resetting anchor_groups to an empty list.", "ERROR")
                anchor_groups = []
            else:
                anchor_groups = api_response.get("response", [])
        else:
            self.log("Using 'execute_get_with_pagination' for version > 2.3.7.9", "DEBUG")
            anchor_groups = self.execute_get_with_pagination(
                "wireless", "get_anchor_groups", get_anchor_groups_params
            )

        self.log("API response received: {0}".format(anchor_groups), "DEBUG")

        # Extract the 'response' part of the API response
        if not anchor_groups:
            self.log(
                "No response received from API call 'get_anchor_groups'. Returning an empty list.",
                "ERROR",
            )
            return []

        self.log(
            "Anchor groups extracted from response: {0}".format(anchor_groups), "DEBUG"
        )

        # Return the list of anchor groups if successfully retrieved
        self.log(
            "Successfully retrieved anchor groups: {0}".format(anchor_groups), "INFO"
        )
        return anchor_groups

    def verify_create_update_anchor_groups_requirement(self, anchor_groups):
        """
        Determines whether anchor groups need to be created, updated, or require no updates.
        Args:
            anchor_groups (list): A list of dictionaries containing the requested anchor group parameters.
        Returns:
            tuple: Three lists containing anchor groups to be created, updated, and not updated.
        """
        self.log(
            "Starting verification for creating/updating anchor groups with requested parameters: {0}".format(
                anchor_groups
            ),
            "INFO",
        )

        # Map the parameters to the API-supported format
        updated_anchor_groups = self.map_anchor_groups_params(anchor_groups)
        self.log(
            "Mapped Requested Anchor Groups: {0}".format(updated_anchor_groups), "DEBUG"
        )

        # Retrieve all existing anchor groups
        existing_anchor_groups = self.get_anchor_groups(get_anchor_groups_params={})
        self.log("Existing Anchor Groups: {0}".format(existing_anchor_groups), "DEBUG")

        # Initialize lists to track anchor groups for creation, update, and no update needed
        create_groups = []
        update_groups = []
        no_update_groups = []

        # Convert existing anchor groups to a dictionary for quick lookup by group name
        existing_groups_dict = {
            group["anchorGroupName"]: group for group in existing_anchor_groups
        }
        self.log(
            "Converted existing anchor groups to a dictionary for quick lookup.",
            "DEBUG",
        )

        # Iterate over the mapped requested anchor groups
        for index, requested_group in enumerate(updated_anchor_groups, start=1):
            group_name = requested_group["anchorGroupName"]
            requested_mobility_anchors = requested_group.get("mobilityAnchors", [])
            self.log(
                "Iteration {0}: Evaluating anchor group '{1}'.".format(
                    index, group_name
                ),
                "DEBUG",
            )

            # Check if the group exists in the existing groups
            if group_name in existing_groups_dict:
                existing_group = existing_groups_dict[group_name]
                existing_mobility_anchors = existing_group.get("mobilityAnchors", [])
                self.log(
                    "Iteration {0}: Anchor Group '{1}' exists. Checking for updates.".format(
                        index, group_name
                    ),
                    "DEBUG",
                )

                # Function to normalize and sort anchors for comparison
                def normalize_anchors(anchors):
                    return sorted(
                        [
                            (
                                anchor.get("deviceName"),
                                anchor.get("ipAddress"),
                                anchor.get("macAddress"),
                                anchor.get("peerDeviceType"),
                                anchor.get("anchorPriority"),
                                anchor.get("privateIp"),
                                anchor.get("mobilityGroupName"),
                                anchor.get("managedAnchorWlc"),
                            )
                            for anchor in anchors
                        ]
                    )

                # Determine if an update is needed by comparing mobility anchors
                update_needed = normalize_anchors(
                    requested_mobility_anchors
                ) != normalize_anchors(existing_mobility_anchors)

                if update_needed:
                    # Add the requested group with the ID from the existing group for update
                    updated_group = requested_group.copy()
                    updated_group["id"] = existing_group.get("id")
                    update_groups.append(updated_group)
                    self.log(
                        "Iteration {0}: Anchor Group '{1}' scheduled for update.".format(
                            index, group_name
                        ),
                        "INFO",
                    )
                else:
                    # If there's no difference, add to no_update_groups
                    no_update_groups.append(existing_group)
                    self.log(
                        "Iteration {0}: Anchor Group '{1}' requires no update.".format(
                            index, group_name
                        ),
                        "INFO",
                    )
            else:
                # If the group does not exist, mark it for creation
                create_groups.append(requested_group)
                self.log(
                    "Iteration {0}: Anchor Group '{1}' scheduled for creation.".format(
                        index, group_name
                    ),
                    "INFO",
                )

        self.log(
            "Anchor Groups that need to be CREATED: {0} - {1}".format(
                len(create_groups), create_groups
            ),
            "DEBUG",
        )
        self.log(
            "Anchor Groups that need to be UPDATED: {0} - {1}".format(
                len(update_groups), update_groups
            ),
            "DEBUG",
        )
        self.log(
            "Anchor Groups that DON'T NEED UPDATES: {0} - {1}".format(
                len(no_update_groups), no_update_groups
            ),
            "DEBUG",
        )

        # Calculate total groups processed and check against requested groups
        total_groups_processed = (
            len(create_groups) + len(update_groups) + len(no_update_groups)
        )
        if total_groups_processed == len(updated_anchor_groups):
            self.log(
                "Match in total counts: Processed={0}, Requested={1}.".format(
                    total_groups_processed, len(updated_anchor_groups)
                ),
                "DEBUG",
            )
        else:
            self.log(
                "Mismatch in total counts: Processed={0}, Requested={1}.".format(
                    total_groups_processed, len(updated_anchor_groups)
                ),
                "ERROR",
            )

        # Return the categorized groups
        return create_groups, update_groups, no_update_groups

    def verify_delete_anchor_groups_requirement(self, anchor_groups):
        """
        Determines whether anchor groups need to be deleted based on the requested parameters.
        Args:
            anchor_groups (list): A list of dictionaries containing the requested anchor group parameters for deletion.
        Returns:
            list: A list of anchor groups that need to be deleted, including their IDs.
        """
        # Initialize a list to track anchor groups that need deletion
        delete_anchor_groups_list = []

        self.log("Starting verification of anchor groups for deletion.", "INFO")

        # Retrieve all existing anchor groups
        existing_anchor_groups = self.get_anchor_groups(get_anchor_groups_params={})
        self.log("Existing Anchor Groups: {0}".format(existing_anchor_groups), "DEBUG")

        # Convert existing anchor groups to a dictionary for quick lookup by group name
        existing_groups_dict = {
            group["anchorGroupName"]: group for group in existing_anchor_groups
        }
        self.log(
            "Converted existing anchor groups to a dictionary for quick lookup.",
            "DEBUG",
        )

        # Iterate over the requested anchor groups
        for index, requested_group in enumerate(anchor_groups, start=1):
            group_name = requested_group.get("anchor_group_name")
            self.log(
                "Iteration {0}: Checking anchor group '{1}' for deletion requirement.".format(
                    index, group_name
                ),
                "DEBUG",
            )

            # Check if the anchor group exists in the existing anchor groups
            if group_name in existing_groups_dict:
                # Add the requested anchor group with the ID from the existing group to the deletion list
                existing_group = existing_groups_dict[group_name]
                group_to_delete = requested_group.copy()
                group_to_delete["id"] = existing_group.get("id")
                delete_anchor_groups_list.append(group_to_delete)
                self.log(
                    "Iteration {0}: Anchor Group '{1}' scheduled for deletion.".format(
                        index, group_name
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: Deletion not required for anchor group '{1}'. It does not exist.".format(
                        index, group_name
                    ),
                    "INFO",
                )

        self.log(
            "Anchor Groups scheduled for deletion: {0} - {1}".format(
                len(delete_anchor_groups_list), delete_anchor_groups_list
            ),
            "DEBUG",
        )

        # Return the list of groups that need to be deleted
        return delete_anchor_groups_list

    def create_anchor_group(self, create_anchor_group_params):
        """
        Initiates the creation of an anchor group.
        Args:
            create_anchor_group_params (dict): Parameters required for creating the anchor group.
        Returns:
            dict: The response from the API call containing the task ID for the operation.
        """
        self.log(
            "Initiating addition of Anchor group with parameters: {0}".format(
                create_anchor_group_params
            ),
            "INFO",
        )

        # Perform the API call to create the anchor group and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "create_anchor_group", create_anchor_group_params
        )

    def update_anchor_group(self, update_anchor_group_params):
        """
        Initiates the update of an anchor group.
        Args:
            update_anchor_group_params (dict): Parameters required for updating the anchor group.
        Returns:
            dict: The response from the API call containing the task ID for the operation.
        """
        self.log(
            "Initiating update Anchor group with parameters: {0}".format(
                update_anchor_group_params
            ),
            "INFO",
        )

        # Perform the API call to update the anchor group and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "update_anchor_group", update_anchor_group_params
        )

    def delete_anchor_group(self, delete_anchor_group_params):
        """
        Initiates the deletion of an anchor group.
        Args:
            delete_anchor_group_params (dict): Parameters required for deleting the anchor group.
        Returns:
            dict: The response from the API call containing the task ID for the operation.
        """
        self.log(
            "Initiating deletion of Anchor group with parameters: {0}".format(
                delete_anchor_group_params
            ),
            "INFO",
        )

        # Perform the API call to delete the anchor group and return the task ID
        return self.get_taskid_post_api_call(
            "wireless", "delete_anchor_group_by_id", delete_anchor_group_params
        )

    def map_anchor_groups_params(self, anchor_groups):
        """
        Maps the provided anchor group parameters to the parameters required for API calls.
        Args:
            anchor_groups (list): A list of dictionaries containing anchor group parameters.
        Returns:
            list: A list of dictionaries with mapped parameters suitable for API calls.
        """
        self.log(
            "Starting 'map_anchor_groups_params' with anchor groups: {0}".format(
                anchor_groups
            ),
            "INFO",
        )
        mapped_anchor_groups = []

        # Check if the anchor_groups list is empty
        if not anchor_groups:
            self.log(
                "No anchor groups provided for mapping. Returning an empty list.",
                "DEBUG",
            )
            return mapped_anchor_groups

        # Define priority mapping from integer to string representation
        priority_mapping = {1: "PRIMARY", 2: "SECONDARY", 3: "TERTIARY"}
        self.log("Priority mapping defined: {0}".format(priority_mapping), "DEBUG")

        # Iterate over each anchor group to map its parameters
        for index, group in enumerate(anchor_groups, start=1):
            self.log(
                "Mapping parameters for anchor group {0}: {1}".format(index, group),
                "DEBUG",
            )
            mapped_group = {}

            # Map top-level parameters for the anchor group
            if "anchor_group_name" in group:
                mapped_group["anchorGroupName"] = group["anchor_group_name"]
                self.log(
                    "Mapped 'anchor_group_name' to 'anchorGroupName' with value '{0}'.".format(
                        group["anchor_group_name"]
                    ),
                    "DEBUG",
                )

            # Initialize the list for mobility anchors in the mapped group
            if "mobility_anchors" in group:
                mapped_group["mobilityAnchors"] = []
                self.log("Initialized 'mobilityAnchors' for mapped group.", "DEBUG")

                # Iterate over each mobility anchor to map its parameters
                for anchor_index, anchor in enumerate(
                    group["mobility_anchors"], start=1
                ):
                    self.log(
                        "Mapping mobility anchor {0}: {1}".format(anchor_index, anchor),
                        "DEBUG",
                    )
                    mapped_anchor = {}

                    # Define the mapping of anchor parameters to API parameters
                    mappings = {
                        "device_name": "deviceName",
                        "device_ip_address": "ipAddress",
                        "device_mac_address": "macAddress",
                        "device_type": "peerDeviceType",
                        "device_nat_ip_address": "privateIp",
                        "mobility_group_name": "mobilityGroupName",
                        "managed_device": "managedAnchorWlc",
                    }

                    # Apply mappings for each parameter in the anchor
                    for param, api_param in mappings.items():
                        if param in anchor:
                            mapped_anchor[api_param] = anchor[param]
                            self.log(
                                "Mapped '{0}' to '{1}' with value '{2}'.".format(
                                    param, api_param, anchor[param]
                                ),
                                "DEBUG",
                            )

                    # Map device_priority to anchorPriority using the defined priority mapping
                    if "device_priority" in anchor:
                        device_priority = anchor["device_priority"]
                        anchor_priority = priority_mapping.get(device_priority, None)
                        if anchor_priority:
                            mapped_anchor["anchorPriority"] = anchor_priority
                            self.log(
                                "Mapped 'device_priority' {0} to 'anchorPriority' '{1}'.".format(
                                    device_priority, anchor_priority
                                ),
                                "DEBUG",
                            )

                    # Add the mapped anchor to the mobility anchors list
                    mapped_group["mobilityAnchors"].append(mapped_anchor)

            # Append the fully mapped group to the list of mapped anchor groups
            mapped_anchor_groups.append(mapped_group)
            self.log(
                "Mapped anchor group {0} completed: {1}".format(index, mapped_group),
                "DEBUG",
            )

        self.log(
            "Finished mapping anchor groups. Result: {0}".format(mapped_anchor_groups),
            "DEBUG",
        )
        return mapped_anchor_groups

    def process_anchor_groups_common(
        self, anchor_groups_params, create_or_update_or_delete_anchor_groups, task_name
    ):
        """
        Processes the anchor groups for the specified operation (create, update, delete).
        Args:
            anchor_groups_params (list): A list of dictionaries containing parameters for each anchor group operation.
            create_or_update_or_delete_anchor_groups (function): The function to execute for each anchor group operation.
            task_name (str): The name of the task being performed, For example, "Create Anchor Group(s) Task".
        """
        # Initialize lists to track successful and failed group operations
        failed_groups = []
        success_groups = []
        msg = {}

        # Iterate over each group parameter set for processing
        for group in anchor_groups_params:
            if create_or_update_or_delete_anchor_groups == self.delete_anchor_group:
                group_name = group.get("anchor_group_name")
            else:
                group_name = group.get("anchorGroupName")
            self.log("Processing anchor group: {0}".format(group_name), "DEBUG")

            # Prepare parameters for the operation
            if create_or_update_or_delete_anchor_groups == self.delete_anchor_group:
                # For delete operations, only the ID is needed
                operation_params = {"id": group.get("id")}
            else:
                # For create or update operations, use the entire group
                operation_params = group

            # Execute the operation and retrieve the task ID
            task_id = create_or_update_or_delete_anchor_groups(operation_params)
            self.log(
                "Task ID for anchor group '{0}': {1}".format(group_name, task_id),
                "DEBUG",
            )

            # Construct operation message
            operation_msg = "{0} operation has completed successfully for anchor group: {1}.".format(
                task_name, group_name
            )

            # Check the status of the operation using the task ID
            self.get_task_status_from_tasks_by_id(
                task_id, task_name, operation_msg
            ).check_return_status()

            # Determine if the operation was successful and categorize accordingly
            if self.status == "success":
                success_groups.append(group_name)
            else:
                failed_groups.append(group_name)

        if success_groups:
            self.log(
                "{0} succeeded for the following anchor group(s): {1}".format(
                    task_name, ", ".join(success_groups)
                ),
                "INFO",
            )
            msg["{0} succeeded for the following anchor group(s)".format(task_name)] = {
                "success_count": len(success_groups),
                "successful_anchor_groups": success_groups,
            }

        if failed_groups:
            self.log(
                "{0} failed for the following anchor group(s): {1}".format(
                    task_name, ", ".join(failed_groups)
                ),
                "ERROR",
            )
            msg["{0} failed for the following anchor group(s)".format(task_name)] = {
                "failed_count": len(failed_groups),
                "failed_anchor_groups": failed_groups,
            }

        # Store the message dictionary in the class
        self.msg = msg

        # Determine the final operation result based on success and failure lists
        if success_groups and failed_groups:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        elif success_groups:
            self.set_operation_result("success", True, self.msg, "INFO")
        elif failed_groups:
            self.set_operation_result("failed", True, self.msg, "ERROR")
        else:
            self.set_operation_result("ok", False, self.msg, "INFO")

        # Return the instance for method chaining or further processing
        return self

    def process_add_anchor_groups(self, add_anchor_groups_params):
        """
        Processes the addition of anchor groups.
        This function initiates the creation process for a list of anchor groups by calling a common
        processing function that handles the task execution and logging.
        Args:
            add_anchor_groups_params (list): A list of dictionaries containing the parameters for anchor groups to be added.
        Returns:
            tuple: A tuple containing the results of the add operation, typically indicating success or failure for each group.
        """
        task_name_create = "Create Anchor Group(s) Task"
        self.log(
            "Starting '{0}' with parameters: {1}".format(
                task_name_create, add_anchor_groups_params
            ),
            "INFO",
        )
        result = self.process_anchor_groups_common(
            add_anchor_groups_params, self.create_anchor_group, task_name_create
        )
        self.log("Completed '{0}'.".format(task_name_create), "INFO")
        return result

    def process_update_anchor_groups(self, update_anchor_groups_params):
        """
        Processes the update of anchor groups.
        This function initiates the update process for a list of anchor groups by calling a common
        processing function that handles the task execution and logging.
        Args:
            update_anchor_groups_params (list): A list of dictionaries containing the parameters for anchor groups to be updated.
        Returns:
            tuple: A tuple containing the results of the update operation, typically indicating success or failure for each group.
        """
        task_name_update = "Update Anchor Group(s) Task"
        self.log(
            "Starting '{0}' with parameters: {1}".format(
                task_name_update, update_anchor_groups_params
            ),
            "INFO",
        )
        result = self.process_anchor_groups_common(
            update_anchor_groups_params, self.update_anchor_group, task_name_update
        )
        self.log("Completed '{0}'.".format(task_name_update), "INFO")
        return result

    def process_delete_anchor_groups(self, delete_anchor_groups_params):
        """
        Processes the deletion of anchor groups.
        This function initiates the deletion process for a list of anchor groups by calling a common
        processing function that handles the task execution and logging.
        Args:
            delete_anchor_groups_params (list): A list of dictionaries containing the parameters for anchor groups to be deleted.
        Returns:
            tuple: A tuple containing the results of the delete operation, typically indicating success or failure for each group.
        """
        task_name_delete = "Delete Anchor Group(s) Task"
        self.log(
            "Starting '{0}' with parameters: {1}".format(
                task_name_delete, delete_anchor_groups_params
            ),
            "INFO",
        )
        result = self.process_anchor_groups_common(
            delete_anchor_groups_params, self.delete_anchor_group, task_name_delete
        )
        self.log("Completed '{0}'.".format(task_name_delete), "INFO")
        return result

    def verify_add_anchor_groups_operation(self, add_anchor_groups_params):
        """
        Verifies whether the anchor groups specified in add_anchor_groups_params have been successfully created.
        Args:
            add_anchor_groups_params (list): A list of dictionaries containing the requested anchor group parameters to be added.
        Returns:
            tuple: Two lists containing successfully created anchor groups and failed groups.
        """
        self.log(
            "Starting 'verify_add_anchor_groups_operation' with parameters: {0}".format(
                add_anchor_groups_params
            ),
            "INFO",
        )

        # Retrieve all existing anchor groups to verify against
        self.log("Retrieving existing anchor groups for verification.", "DEBUG")
        existing_anchor_groups = self.get_anchor_groups(get_anchor_groups_params={})
        self.log("Existing Anchor Groups: {0}".format(existing_anchor_groups), "DEBUG")
        self.log(
            "Requested Anchor Groups to Add: {0}".format(add_anchor_groups_params),
            "DEBUG",
        )

        # Initialize lists to track successful and failed group additions
        successful_groups = []
        failed_groups = []
        self.log(
            "Initialized lists to track successful and failed anchor group additions.",
            "DEBUG",
        )

        # Convert existing anchor groups to a set for quick lookup by group name
        existing_groups_set = {
            group["anchorGroupName"] for group in existing_anchor_groups
        }
        self.log("Converted existing anchor groups to a set for quick lookup.", "DEBUG")

        # Iterate over the requested anchor groups to verify their creation
        for index, requested_group in enumerate(add_anchor_groups_params, start=1):
            group_name = requested_group["anchorGroupName"]
            self.log(
                "Iteration {0}: Verifying creation for Anchor Group '{1}'.".format(
                    index, group_name
                ),
                "DEBUG",
            )

            # Check if the group now exists in the existing groups
            if group_name in existing_groups_set:
                successful_groups.append(group_name)
                self.log(
                    "Iteration {0}: Anchor Group '{1}' has been successfully created.".format(
                        index, group_name
                    ),
                    "INFO",
                )
            else:
                failed_groups.append(group_name)
                self.log(
                    "Iteration {0}: Anchor Group '{1}' failed to create.".format(
                        index, group_name
                    ),
                    "ERROR",
                )

        if failed_groups:
            self.log(
                "The ADD Anchor Group(s) operation may not have been successful since some groups were not successfully created: {0}".format(
                    failed_groups
                ),
                "WARNING",
            )
        else:
            self.log(
                "Verified the success of ADD Anchor Group(s) operation for parameters: {0}".format(
                    add_anchor_groups_params
                ),
                "INFO",
            )

    def verify_update_anchor_groups_operation(self, update_anchor_groups_params):
        """
        Verifies whether the anchor groups specified in update_anchor_groups_params have been successfully updated.
        Args:
            update_anchor_groups_params (list): A list of dictionaries containing the requested anchor group parameters to be updated.
        Returns:
            tuple: Two lists containing successfully updated anchor groups and failed updates.
        """
        self.log(
            "Starting 'verify_update_anchor_groups_operation' with parameters: {0}".format(
                update_anchor_groups_params
            ),
            "INFO",
        )

        # Retrieve all existing anchor groups
        self.log("Retrieving existing anchor groups.", "DEBUG")
        existing_anchor_groups = self.get_anchor_groups(get_anchor_groups_params={})
        self.log("Existing Anchor Groups: {0}".format(existing_anchor_groups), "DEBUG")
        self.log(
            "Requested Anchor Groups to Update: {0}".format(
                update_anchor_groups_params
            ),
            "DEBUG",
        )

        successful_updates = []
        failed_updates = []

        # Convert existing anchor groups to a dictionary for quick lookup by group name
        existing_groups_dict = {
            group["anchorGroupName"]: group for group in existing_anchor_groups
        }
        self.log(
            "Converted existing anchor groups to dictionary for quick lookup.", "DEBUG"
        )

        # Iterate over the requested anchor groups to verify updates
        for index, requested_group in enumerate(update_anchor_groups_params, start=1):
            group_name = requested_group["anchorGroupName"]
            requested_mobility_anchors = requested_group.get("mobilityAnchors", [])
            self.log(
                "Iteration {0}: Verifying update for Anchor Group '{1}'.".format(
                    index, group_name
                ),
                "DEBUG",
            )

            # Check if the group exists in the existing groups
            if group_name in existing_groups_dict:
                existing_group = existing_groups_dict[group_name]
                existing_mobility_anchors = existing_group.get("mobilityAnchors", [])
                self.log(
                    "Iteration {0}: Found existing Anchor Group '{1}'.".format(
                        index, group_name
                    ),
                    "DEBUG",
                )

                # Function to normalize and sort anchors for comparison
                def normalize_anchors(anchors):
                    return sorted(
                        [
                            (
                                anchor.get("deviceName"),
                                anchor.get("ipAddress"),
                                anchor.get("macAddress"),
                                anchor.get("peerDeviceType"),
                                anchor.get("anchorPriority"),
                                anchor.get("privateIp"),
                                anchor.get("mobilityGroupName"),
                                anchor.get("managedAnchorWlc"),
                            )
                            for anchor in anchors
                        ]
                    )

                # Compare mobility anchors, ignoring the order
                if normalize_anchors(requested_mobility_anchors) == normalize_anchors(
                    existing_mobility_anchors
                ):
                    successful_updates.append(group_name)
                    self.log(
                        "Iteration {0}: Anchor Group '{1}' has been successfully updated.".format(
                            index, group_name
                        ),
                        "INFO",
                    )
                else:
                    failed_updates.append(group_name)
                    self.log(
                        "Iteration {0}: Anchor Group '{1}' failed to update.".format(
                            index, group_name
                        ),
                        "ERROR",
                    )
            else:
                failed_updates.append(group_name)
                self.log(
                    "Iteration {0}: Anchor Group '{1}' does not exist and cannot be updated.".format(
                        index, group_name
                    ),
                    "ERROR",
                )

        if failed_updates:
            self.log(
                "The UPDATE Anchor Groups operation may not have been successful. The following anchor groups failed verification: {0}.".format(
                    failed_updates
                ),
                "ERROR",
            )
        else:
            self.log(
                "Successfully verified the UPDATE Anchor Groups operation for the following anchor groups: {0}.".format(
                    successful_updates
                ),
                "INFO",
            )

    def verify_delete_anchor_groups_operation(self, delete_anchor_groups_params):
        """
        Verifies whether the anchor groups specified in delete_anchor_groups_params have been successfully deleted.
        Args:
            delete_anchor_groups_params (list): A list of dictionaries containing the requested anchor group names to be deleted.
        Returns:
            bool: True if all requested anchor groups were successfully deleted, False otherwise.
        """
        self.log(
            "Starting 'verify_delete_anchor_groups_operation' with parameters: {0}".format(
                delete_anchor_groups_params
            ),
            "INFO",
        )

        # Retrieve all existing anchor groups
        self.log("Retrieving existing anchor groups.", "DEBUG")
        existing_anchor_groups = self.get_anchor_groups(get_anchor_groups_params={})
        existing_groups_set = {
            group["anchorGroupName"] for group in existing_anchor_groups
        }
        self.log(
            "Current Anchor Groups after DELETE operation: {0}".format(
                existing_groups_set
            ),
            "INFO",
        )

        self.log(
            "Requested Anchor Groups to Delete: {0}".format(
                delete_anchor_groups_params
            ),
            "INFO",
        )

        # Initialize a list to track groups that failed deletion
        failed_deletions = []
        self.log("Initialized list to track failed deletions.", "DEBUG")

        # Iterate over the requested anchor groups to verify deletion
        for index, requested_group in enumerate(delete_anchor_groups_params, start=1):
            group_name = requested_group["anchor_group_name"]
            self.log(
                "Iteration {0}: Verifying deletion for Anchor Group '{1}'.".format(
                    index, group_name
                ),
                "DEBUG",
            )

            # Check if the group still exists in the existing groups
            if group_name in existing_groups_set:
                # If it exists, the deletion failed
                failed_deletions.append(group_name)
                self.log(
                    "Iteration {0}: Delete operation failed for Anchor Group '{1}'. It still exists.".format(
                        index, group_name
                    ),
                    "ERROR",
                )

        if failed_deletions:
            self.log(
                "The DELETE Anchor Group(s) operation may not have been successful since some Anchor Groups still exist: {0}.".format(
                    failed_deletions
                ),
                "ERROR",
            )
        else:
            self.log(
                "Verified the success of DELETE Anchor Group(s) operation for the following parameters: {0}.".format(
                    delete_anchor_groups_params
                ),
                "INFO",
            )

    def process_final_result(self, final_status_list):
        """
        Processes a list of final statuses and returns a tuple indicating the result and a boolean flag.
        Args:
            final_status_list (list): List of status strings to process.
        Returns:
            tuple: A tuple containing a status string ("ok" or "success") and a boolean flag
                   (False if all statuses are "ok", True otherwise).
        """
        self.log(
            "Starting 'process_final_result' with final_status_list: {0}".format(
                final_status_list
            ),
            "INFO",
        )

        # Convert the list of statuses to a set to identify unique statuses
        status_set = set(final_status_list)
        self.log("Unique statuses identified: {0}".format(status_set), "DEBUG")

        # Determine the final status and change flag based on the unique statuses
        if status_set == {"ok"}:
            self.log("All statuses are 'ok'. Returning ('ok', False).", "INFO")
            return "ok", False
        else:
            self.log(
                "Statuses include non-'ok' values. Returning ('success', True).", "INFO"
            )
            return "success", True

    def verify_create_update_flex_connect_requirement(self, flex_connect_list):
        """
        Compares desired Flex Connect configuration against existing Catalyst Center
        configuration and determines which entries need to be updated or left unchanged.

        IMPORTANT:
        - Flex Connect configuration CANNOT be created.
        - Payloads MUST use siteId resolved via get_site_id().
        """

        add_configs = []   # Always empty (creation not supported)
        update_configs = []
        no_update_configs = []

        # ---------------------------------------------------------
        # Iterate requested Flex Connect configurations
        # ---------------------------------------------------------
        for req in flex_connect_list:
            site_hierarchy = req.get("site_name_hierarchy")
            vlan_id = req.get("vlan_id")

            self.log(
                "Evaluating Flex Connect config for site hierarchy: {0}".format(site_hierarchy),
                "DEBUG"
            )

            # Basic validation
            if not site_hierarchy or vlan_id is None:
                self.msg = (
                    "Invalid Flex Connect configuration. "
                    "Both 'site_name_hierarchy' and 'vlan_id' are required."
                )
                self.set_operation_result(
                    "failed", False, self.msg, "ERROR"
                ).check_return_status()

            # ---------------------------------------------------------
            # Resolve siteId FIRST
            # ---------------------------------------------------------
            site_exists, site_id = self.get_site_id(site_hierarchy)

            if not site_exists or not site_id:
                self.msg = (
                    "Site '{0}' does not exist or siteId could not be resolved."
                    .format(site_hierarchy)
                )
                self.set_operation_result(
                    "failed", False, self.msg, "ERROR"
                ).check_return_status()

            self.log(
                "Resolved siteId '{0}' for site hierarchy '{1}'.".format(site_id, site_hierarchy),
                "DEBUG"
            )

            # ---------------------------------------------------------
            # Fetch existing Flex Connect config for THIS siteId
            # ---------------------------------------------------------
            existing_response = self.get_flex_connect_configuration(site_id)
            self.log(
                "Existing Flex Connect config for siteId '{0}': {1}".format(
                    site_id, existing_response
                ),
                "DEBUG"
            )

            # **FIX: Handle the API response structure correctly**
            # The API returns: {'nativeVlanId': 7, 'inheritedSiteUUID': '...', 'inheritedSiteNameHierarchy': '...'}
            # NOT a list!
            existing = None

            if existing_response:
                # If it's a dict with nativeVlanId, use it directly
                if isinstance(existing_response, dict) and 'nativeVlanId' in existing_response:
                    existing = existing_response
                # If it's a list (some DNAC versions), take first entry
                elif isinstance(existing_response, list) and len(existing_response) > 0:
                    existing = existing_response[0]

            # ---------------------------------------------------------
            # Case 1: Flex Connect config exists → compare & update
            # ---------------------------------------------------------
            if existing and 'nativeVlanId' in existing:
                self.log(
                    "Flex Connect configuration exists for siteId '{0}'.".format(site_id),
                    "DEBUG"
                )

                existing_vlan = existing.get("nativeVlanId")

                self.log(
                    "Comparing VLANs for siteId '{0}': existing={1}, requested={2}".format(
                        site_id, existing_vlan, vlan_id
                    ),
                    "DEBUG"
                )

                if existing_vlan != vlan_id:
                    payload = {
                        "siteId": site_id,
                        "vlanId": vlan_id
                    }

                    update_configs.append(payload)

                    self.log(
                        "Flex Connect config for siteId '{0}' marked for update "
                        "(existing VLAN: {1}, requested VLAN: {2}).".format(
                            site_id, existing_vlan, vlan_id
                        ),
                        "INFO"
                    )
                else:
                    no_update_configs.append(existing)
                    self.log(
                        "Flex Connect config for siteId '{0}' requires no update "
                        "(VLAN already set to {1}).".format(site_id, vlan_id),
                        "INFO"
                    )

            # ---------------------------------------------------------
            # Case 2: Flex Connect config does NOT exist → NO CREATE
            # ---------------------------------------------------------
            else:
                self.log(
                    "Flex Connect configuration does not exist for siteId '{0}'. "
                    "Creation is not supported — skipping.".format(site_id),
                    "WARNING"
                )

                no_update_configs.append(
                    {
                        "siteId": site_id,
                        "vlanId": vlan_id,
                        "reason": "Flex Connect configuration does not exist; creation not supported"
                    }
                )

        # ---------------------------------------------------------
        # Final logs
        # ---------------------------------------------------------
        self.log(
            "Flex Connect configs to ADD (always empty): {0}".format(add_configs),
            "DEBUG"
        )
        self.log(
            "Flex Connect configs to UPDATE: {0}".format(update_configs),
            "DEBUG"
        )
        self.log(
            "Flex Connect configs with NO UPDATE: {0}".format(no_update_configs),
            "DEBUG"
        )

        return add_configs, update_configs, no_update_configs

    def get_flex_connect_configuration(self, site_id=None):
        """
        Retrieve existing Flex Connect (Native VLAN) configuration from
        Cisco Catalyst Center.

        Args:
            site_id (str, optional): Specific siteId to filter the native VLAN settings.

        Returns:
            list: A list of existing Flex Connect configuration dicts
                (the API 'response' list), or [] on failure.
        """
        self.log("Fetching existing Flex Connect (Native VLAN) configuration from DNAC.", "DEBUG")

        try:
            params = {}
            if site_id:
                params["site_id"] = site_id

            response = self.execute_get_request(
                "wireless",
                "get_native_vlan_settings_by_site",
                params
            )

            self.log("Received API response: {0}".format(response), "DEBUG")

            existing_configs = response.get("response", [])

            self.log(
                "Retrieved {0} Flex Connect configuration entries.".format(
                    len(existing_configs)
                ),
                "DEBUG",
            )

            return existing_configs

        except Exception as e:
            self.log(
                "Failed to fetch Flex Connect (Native VLAN) configuration: {0}".format(
                    str(e)
                ),
                "ERROR",
            )
            return []

    def verify_delete_flex_connect_requirement(self, flex_connect_list):
        """
        Determines which Flex Connect configurations need to be deleted/reset
        based on the requested parameters.

        IMPORTANT:
        - Flex Connect configurations are reset via Native VLAN delete/reset API.
        - Creation is not supported; delete means removing overrides.

        Args:
            flex_connect_list (list): List of Flex Connect delete requests.
                Each dictionary must contain:
                    - site_name_hierarchy (str)
                    - remove_override_in_hierarchy (bool, optional)

        Returns:
            tuple:
                - delete_configs (list)
                - no_delete_configs (list)
        """

        delete_configs = []
        no_delete_configs = []

        self.log(
            "Starting verification of Flex Connect configurations for deletion.",
            "INFO"
        )

        for req in flex_connect_list or []:
            site_hierarchy = req.get("site_name_hierarchy")
            remove_override = req.get("remove_override_in_hierarchy", True)

            if not site_hierarchy:
                self.msg = (
                    "Invalid Flex Connect deletion request. "
                    "'site_name_hierarchy' is required."
                )
                self.set_operation_result(
                    "failed", False, self.msg, "ERROR"
                ).check_return_status()

            self.log(
                "Evaluating Flex Connect deletion for site hierarchy: {0}".format(
                    site_hierarchy
                ),
                "DEBUG"
            )

            # -------------------------------------------------
            # Resolve siteId
            # -------------------------------------------------
            site_exists, site_id = self.get_site_id(site_hierarchy)

            if not site_exists or not site_id:
                self.log(
                    "Site '{0}' does not exist. Skipping deletion.".format(
                        site_hierarchy
                    ),
                    "WARNING"
                )
                no_delete_configs.append(
                    {
                        "site_name_hierarchy": site_hierarchy,
                        "reason": "Site does not exist"
                    }
                )
                continue

            self.log(
                "Resolved siteId '{0}' for site hierarchy '{1}'.".format(
                    site_id, site_hierarchy
                ),
                "DEBUG"
            )

            # -------------------------------------------------
            # Fetch existing Flex Connect config for this site
            # -------------------------------------------------
            existing_configs = self.get_flex_connect_configuration(site_id)

            self.log(
                "Existing Flex Connect configs for siteId '{0}': {1}".format(
                    site_id, existing_configs
                ),
                "DEBUG"
            )

            # -------------------------------------------------
            # Determine delete vs no-delete
            # -------------------------------------------------
            if existing_configs:
                delete_configs.append(
                    {
                        "siteId": site_id,
                        "removeOverrideInHierarchy": remove_override
                    }
                )

                self.log(
                    "Flex Connect config for site '{0}' scheduled for deletion/reset. "
                    "Remove hierarchy overrides: {1}".format(
                        site_hierarchy, remove_override
                    ),
                    "INFO"
                )
            else:
                self.log(
                    "No Flex Connect configuration exists for site '{0}'. "
                    "Nothing to delete.".format(site_hierarchy),
                    "INFO"
                )
                no_delete_configs.append(
                    {
                        "site_name_hierarchy": site_hierarchy,
                        "siteId": site_id,
                        "reason": "Flex Connect configuration does not exist"
                    }
                )

        self.log(
            "Flex Connect configs to DELETE: {0}".format(delete_configs),
            "DEBUG"
        )
        self.log(
            "Flex Connect configs with NO DELETE required: {0}".format(no_delete_configs),
            "DEBUG"
        )

        return delete_configs, no_delete_configs

    def get_have(self, config, state):
        """
        Constructs the 'have' dictionary representing the current state of network configurations.
        This function validates the given configuration and determines the current state for SSIDs, interfaces,
        power profiles, access point profiles, radio frequency profiles, and anchor groups based on the specified
        state ('merged' or 'deleted').
        Args:
            config (dict): Configuration data for network elements.
            state (str): Desired state of the network elements ('merged' or 'deleted').
        """
        self.log("Starting 'get_have' operation with state: {0}".format(state), "INFO")
        self.validate_params(config, state)
        have = {}

        ssids = config.get("ssids")
        if ssids:
            global_site_name = "Global"
            self.log("Processing SSIDs for state: {0}".format(state), "DEBUG")
            global_site_exists, global_site_id = self.get_site_id(global_site_name)
            self.validate_site_name_hierarchy(
                global_site_exists, global_site_id, global_site_name
            )
            have["global_site_details"] = {
                "site_name": global_site_name,
                "site_id": global_site_id,
            }

            if state == "merged":
                add_ssids, update_ssids, no_update_ssids = (
                    self.verify_create_update_ssids_requirement(
                        ssids, have["global_site_details"]
                    )
                )
                have.update(
                    {
                        "add_ssids": add_ssids,
                        "update_ssids": update_ssids,
                        "no_update_ssids": no_update_ssids,
                    }
                )
            elif state == "deleted":
                have["delete_ssids"] = self.verify_delete_ssids_requirement(
                    ssids, have["global_site_details"]
                )

        element_mappings = [
            (
                "interfaces",
                "interface",
                self.verify_create_update_interfaces_requirement,
                self.verify_delete_interfaces_requirement,
            ),
            (
                "power_profiles",
                "power profile",
                self.verify_create_update_power_profiles_requirement,
                self.verify_delete_power_profiles_requirement,
            ),
            (
                "access_point_profiles",
                "access point profile",
                self.verify_create_update_access_point_profiles_requirement,
                self.verify_delete_access_point_profiles_requirement,
            ),
            (
                "radio_frequency_profiles",
                "radio frequency profile",
                self.verify_create_update_radio_frequency_profiles_requirement,
                self.verify_delete_radio_frequency_profiles_requirement,
            ),
            (
                "anchor_groups",
                "anchor group",
                self.verify_create_update_anchor_groups_requirement,
                self.verify_delete_anchor_groups_requirement,
            ),
        ]

        for config_key, log_name, merged_func, deleted_func in element_mappings:
            elements = config.get(config_key)
            if elements:
                self.log(
                    "Processing {0}s for state: {1}".format(
                        log_name.capitalize(), state
                    ),
                    "DEBUG",
                )
                if state == "merged":
                    add, update, no_update = merged_func(elements)
                    have.update(
                        {
                            "add_{0}".format(config_key): add,
                            "update_{0}".format(config_key): update,
                            "no_update_{0}".format(config_key): no_update,
                        }
                    )
                elif state == "deleted":
                    have["delete_{0}".format(config_key)] = deleted_func(elements)

        # --- New logic for Flex Connect configuration ---
        if config.get("flex_connect_configuration", []):
            flex_connect_list = []

            # Normalize config
            raw_config = config
            if isinstance(raw_config, dict):
                config_list = [raw_config]
            elif isinstance(raw_config, list):
                config_list = raw_config
            else:
                self.msg = "Invalid config format for flex_connect_configuration"
                self.set_operation_result(
                    "failed", False, self.msg, "ERROR"
                ).check_return_status()

            for item in config_list:
                if not isinstance(item, dict):
                    continue

                if "flex_connect_configuration" in item:
                    flex_connect_list.extend(
                        item.get("flex_connect_configuration", [])
                    )

            if flex_connect_list:
                self.log(
                    "Processing Flex Connect configuration for state: {0}".format(state),
                    "DEBUG"
                )

                if state == "merged":
                    (
                        add_flex_configs,
                        update_flex_configs,
                        no_update_flex_configs,
                    ) = self.verify_create_update_flex_connect_requirement(
                        flex_connect_list
                    )

                    have.update(
                        {
                            "add_flex_connect_configuration": add_flex_configs,
                            "update_flex_connect_configuration": update_flex_configs,
                            "no_update_flex_connect_configuration": no_update_flex_configs,
                        }
                    )

                elif state == "deleted":
                    (
                        delete_flex_configs,
                        no_delete_flex_configs,
                    ) = self.verify_delete_flex_connect_requirement(
                        flex_connect_list
                    )

                    have.update(
                        {
                            "delete_flex_connect_configuration": delete_flex_configs,
                            "no_delete_flex_connect_configuration": no_delete_flex_configs,
                        }
                    )

        # --- New logic for 802.11be profiles ---
        if config.get("802_11_be_profiles", []):
            be_profiles_list = []

            # Normalize config
            raw_config = config
            if isinstance(raw_config, dict):
                config_list = [raw_config]
            elif isinstance(raw_config, list):
                config_list = raw_config
            else:
                self.msg = "Invalid config format for 802.11be profiles"
                self.set_operation_result("failed", False, self.msg, "ERROR").check_return_status()

            for item in config_list:
                if not isinstance(item, dict):
                    continue

                if "802_11_be_profiles" in item:
                    be_profiles_list.extend(item.get("802_11_be_profiles", []))

            if be_profiles_list:
                self.log(
                    "Processing 802.11be profiles configuration for state: {0}".format(state),
                    "DEBUG"
                )

                if state == "merged":
                    add_be_profiles, update_be_profiles, no_update_be_profiles = (
                        self.verify_create_update_80211be_profiles_requirement(be_profiles_list)
                    )

                    have.update(
                        {
                            "add_80211be_profiles": add_be_profiles,
                            "update_80211be_profiles": update_be_profiles,
                            "no_update_80211be_profiles": no_update_be_profiles,
                        }
                    )

                elif state == "deleted":
                    delete_be_profiles, no_delete_be_profiles = (
                        self.verify_delete_80211be_profiles_requirement(be_profiles_list)
                    )

                    have.update(
                        {
                            "delete_80211be_profiles": delete_be_profiles,
                            "no_delete_80211be_profiles": no_delete_be_profiles,
                        }
                    )

        # --- New logic for AAA Radius Attributes ---
        if config.get("feature_template_config", []):
            aaa_attr_list = []
            for item in config.get("feature_template_config", []):
                if "aaa_radius_attribute" in item:
                    aaa_attr_list.extend(item.get("aaa_radius_attribute", []))

            if aaa_attr_list:
                self.log("Processing AAA Radius Attributes for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_attrs, update_attrs, no_update_attrs = (
                        self.verify_create_update_aaa_radius_attributes_requirement(aaa_attr_list)
                    )
                    have.update(
                        {
                            "add_aaa_radius_attribute": add_attrs,
                            "update_aaa_radius_attribute": update_attrs,
                            "no_update_aaa_radius_attribute": no_update_attrs,
                        }
                    )
                elif state == "deleted":
                    have["delete_aaa_radius_attribute"] = self.verify_delete_aaa_radius_attributes_requirement(
                        aaa_attr_list
                    )

            # --- New logic for Advanced SSID ---
            adv_ssid_list = []
            for item in config.get("feature_template_config", []):
                if "advanced_ssid" in item:
                    adv_ssid_list.extend(item.get("advanced_ssid", []))

            if adv_ssid_list:
                self.log("Processing Advanced SSID for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_adv, update_adv, no_update_adv = (
                        self.verify_create_update_advanced_ssid_requirement(adv_ssid_list)
                    )
                    have.update(
                        {
                            "add_advanced_ssid": add_adv,
                            "update_advanced_ssid": update_adv,
                            "no_update_advanced_ssid": no_update_adv,
                        }
                    )
                elif state == "deleted":
                    have["delete_advanced_ssid"] = self.verify_delete_advanced_ssid_requirement(
                        adv_ssid_list
                    )

            # --- New logic for CleanAir profiles ---
            clean_air_list = []
            for item in config.get("feature_template_config", []):
                if "clean_air_configuration" in item:
                    clean_air_list.extend(item.get("clean_air_configuration", []))

            if clean_air_list:
                self.log("Processing CleanAir configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_clean_air, update_clean_air, no_update_clean_air = (
                        self.verify_create_update_clean_air_requirement(clean_air_list)
                    )
                    have.update(
                        {
                            "add_clean_air_configuration": add_clean_air,
                            "update_clean_air_configuration": update_clean_air,
                            "no_update_clean_air_configuration": no_update_clean_air,
                        }
                    )
                elif state == "deleted":
                    have["delete_clean_air_configuration"] = self.verify_delete_clean_air_requirement(
                        clean_air_list
                    )

            # --- New logic for 802.11ax profiles ---
            dot11ax_list = []
            for item in config.get("feature_template_config", []):
                if "dot11ax_configuration" in item:
                    dot11ax_list.extend(item.get("dot11ax_configuration", []))

            if dot11ax_list:
                self.log("Processing 802.11ax configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_dot11ax, update_dot11ax, no_update_dot11ax = (
                        self.verify_create_update_dot11axs_requirement(dot11ax_list)
                    )
                    have.update(
                        {
                            "add_dot11ax_configuration": add_dot11ax,
                            "update_dot11ax_configuration": update_dot11ax,
                            "no_update_dot11ax_configuration": no_update_dot11ax,
                        }
                    )
                elif state == "deleted":
                    have["delete_dot11ax_configuration"] = self.verify_delete_dot11axs_requirement(
                        dot11ax_list
                    )

            # --- New logic for 802.11be profiles ---
            dot11be_list = []
            for item in config.get("feature_template_config", []):
                self.log("Processing 802.11be configuration for state: {0}".format(item), "Warning")
                if "dot11be_configuration" in item:
                    dot11be_list.extend(item.get("dot11be_configuration", []))

            if dot11be_list:
                self.log("Processing 802.11be configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_dot11be, update_dot11be, no_update_dot11be = (
                        self.verify_create_update_dot11be_requirement(dot11be_list)
                    )
                    have.update(
                        {
                            "add_dot11be_configuration": add_dot11be,
                            "update_dot11be_configuration": update_dot11be,
                            "no_update_dot11be_configuration": no_update_dot11be,
                        }
                    )
                elif state == "deleted":
                    self.log("Deleting 802.11be configuration for state: {0}".format(state), "WARNING")
                    self.log("Dot11be List: {0}".format(dot11be_list), "WARNING")
                    have["delete_dot11be_configuration"] = self.verify_delete_dot11be_requirement(
                        dot11be_list
                    )

            # --- New logic for Event Driven RRM profiles ---
            event_rrm_list = []
            for item in config.get("feature_template_config", []):
                if "event_driven_rrm_configuration" in item:
                    event_rrm_list.extend(item.get("event_driven_rrm_configuration", []))

            if event_rrm_list:
                self.log("Processing Event Driven RRM configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_event_rrm, update_event_rrm, no_update_event_rrm = (
                        self.verify_create_update_event_rrm_requirement(event_rrm_list)
                    )
                    have.update(
                        {
                            "add_event_driven_rrm_configuration": add_event_rrm,
                            "update_event_driven_rrm_configuration": update_event_rrm,
                            "no_update_event_driven_rrm_configuration": no_update_event_rrm,
                        }
                    )
                elif state == "deleted":
                    have["delete_event_driven_rrm_configuration"] = self.verify_delete_event_rrm_requirement(
                        event_rrm_list
                    )

            # --- New logic for FlexConnect profiles ---
            flexconnect_list = []
            for item in config.get("feature_template_config", []):
                if "flexconnect_configuration" in item:
                    flexconnect_list.extend(item.get("flexconnect_configuration", []))

            if flexconnect_list:
                self.log("Processing FlexConnect configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_flexconnect, update_flexconnect, no_update_flexconnect = (
                        self.verify_create_update_flexconnect_requirement(flexconnect_list)
                    )
                    have.update(
                        {
                            "add_flexconnect_configuration": add_flexconnect,
                            "update_flexconnect_configuration": update_flexconnect,
                            "no_update_flexconnect_configuration": no_update_flexconnect,
                        }
                    )
                elif state == "deleted":
                    have["delete_flexconnect_configuration"] = self.verify_delete_flexconnect_requirement(
                        flexconnect_list
                    )

            # --- New logic for Multicast profiles ---
            multicast_list = []
            for item in config.get("feature_template_config", []):
                if "multicast_configuration" in item:
                    multicast_list.extend(item.get("multicast_configuration", []))

            if multicast_list:
                self.log("Processing Multicast configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_multicast, update_multicast, no_update_multicast = (
                        self.verify_create_update_multicast_requirement(multicast_list)
                    )
                    have.update(
                        {
                            "add_multicast_configuration": add_multicast,
                            "update_multicast_configuration": update_multicast,
                            "no_update_multicast_configuration": no_update_multicast,
                        }
                    )
                elif state == "deleted":
                    have["delete_multicast_configuration"] = self.verify_delete_multicast_requirement(
                        multicast_list
                    )

            # --- New logic for RRM-FRA profiles ---
            rrm_fra_list = []
            for item in config.get("feature_template_config", []):
                if "rrm_fra_configuration" in item:
                    rrm_fra_list.extend(item.get("rrm_fra_configuration", []))

            if rrm_fra_list:
                self.log("Processing RRM-FRA configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_rrm_fra, update_rrm_fra, no_update_rrm_fra = (
                        self.verify_create_update_rrm_fra_requirement(rrm_fra_list)
                    )
                    have.update(
                        {
                            "add_rrm_fra_configuration": add_rrm_fra,
                            "update_rrm_fra_configuration": update_rrm_fra,
                            "no_update_rrm_fra_configuration": no_update_rrm_fra,
                        }
                    )
                elif state == "deleted":
                    have["delete_rrm_fra_configuration"] = self.verify_delete_rrm_fra_requirement(
                        rrm_fra_list
                    )

            # --- New logic for RRM General profiles ---
            rrm_general_list = []
            for item in config.get("feature_template_config", []):
                if "rrm_general_configuration" in item:
                    rrm_general_list.extend(item.get("rrm_general_configuration", []))

            if rrm_general_list:
                self.log("Processing RRM General configuration for state: {0}".format(state), "DEBUG")

                if state == "merged":
                    add_rrm_general, update_rrm_general, no_update_rrm_general = (
                        self.verify_create_update_rrm_general_requirement(rrm_general_list)
                    )
                    have.update(
                        {
                            "add_rrm_general_configuration": add_rrm_general,
                            "update_rrm_general_configuration": update_rrm_general,
                            "no_update_rrm_general_configuration": no_update_rrm_general,
                        }
                    )
                elif state == "deleted":
                    have["delete_rrm_general_configuration"] = self.verify_delete_rrm_general_requirement(
                        rrm_general_list
                    )

        self.have = have
        self.log("Current State (have): {0}".format(str(self.have)), "INFO")
        return self

    def get_want(self, config, state):
        """
        Creates parameters for API calls based on the specified state.
        This method prepares the parameters required for adding, updating, or deleting
        network configurations such as SSIDs and interfaces in the Cisco Catalyst Center
        based on the desired state. It logs detailed information for each operation.
        Args:
            config (dict): The configuration data for the network elements.
            state (str): The desired state of the network elements ('merged' or 'deleted').
        """
        self.log(
            "Creating Parameters for API Calls with state: {0}".format(state), "INFO"
        )

        want = {}

        # Define operations for each state
        self.log("Defining operations for each state: 'merged' and 'deleted'.", "DEBUG")
        operations = {
            "merged": [
                ("add_ssids", "add_ssids_params", self.have.get("add_ssids")),
                ("update_ssids", "update_ssids_params", self.have.get("update_ssids")),
                (
                    "add_interfaces",
                    "add_interfaces_params",
                    self.map_interface_params(self.have.get("add_interfaces")),
                ),
                (
                    "update_interfaces",
                    "update_interfaces_params",
                    self.map_interface_params(self.have.get("update_interfaces")),
                ),
                (
                    "add_power_profiles",
                    "add_power_profiles_params",
                    self.map_power_profiles_params(self.have.get("add_power_profiles")),
                ),
                (
                    "update_power_profiles",
                    "update_power_profiles_params",
                    self.map_power_profiles_params(
                        self.have.get("update_power_profiles")
                    ),
                ),
                (
                    "add_access_point_profiles",
                    "add_access_point_profiles_params",
                    self.have.get("add_access_point_profiles"),
                ),
                (
                    "update_access_point_profiles",
                    "update_access_point_profiles_params",
                    self.have.get("update_access_point_profiles"),
                ),
                (
                    "add_radio_frequency_profiles",
                    "add_radio_frequency_profiles_params",
                    self.have.get("add_radio_frequency_profiles"),
                ),
                (
                    "update_radio_frequency_profiles",
                    "update_radio_frequency_profiles_params",
                    self.have.get("update_radio_frequency_profiles"),
                ),
                (
                    "add_anchor_groups",
                    "add_anchor_groups_params",
                    self.have.get("add_anchor_groups"),
                ),
                (
                    "update_anchor_groups",
                    "update_anchor_groups_params",
                    self.have.get("update_anchor_groups"),
                ),
                # --- New AAA Radius Attributes ---
                ("add_aaa_radius_attribute", "add_aaa_radius_attribute_params", self.have.get("add_aaa_radius_attribute")),
                ("update_aaa_radius_attribute", "update_aaa_radius_attribute_params", self.have.get("update_aaa_radius_attribute")),

                # --- New Advanced SSID ---
                ("add_advanced_ssid", "add_advanced_ssid_params", self.have.get("add_advanced_ssid")),
                ("update_advanced_ssid", "update_advanced_ssid_params", self.have.get("update_advanced_ssid")),

                # --- New CleanAir ---
                (
                    "add_clean_air_configuration",
                    "add_clean_air_configuration_params",
                    self.have.get("add_clean_air_configuration"),
                ),
                (
                    "update_clean_air_configuration",
                    "update_clean_air_configuration_params",
                    self.have.get("update_clean_air_configuration"),
                ),
                # --- New dot11ax Configuration ---
                (
                    "add_dot11ax_configuration",
                    "add_dot11ax_configuration_params",
                    self.have.get("add_dot11ax_configuration"),
                ),
                (
                    "update_dot11ax_configuration",
                    "update_dot11ax_configuration_params",
                    self.have.get("update_dot11ax_configuration"),
                ),
                # --- New dot11be Configuration ---
                (
                    "add_dot11be_configuration",
                    "add_dot11be_configuration_params",
                    self.have.get("add_dot11be_configuration"),
                ),
                (
                    "update_dot11be_configuration",
                    "update_dot11be_configuration_params",
                    self.have.get("update_dot11be_configuration"),
                ),
                # --- New Event Driven RRM Configuration ---
                (
                    "add_event_driven_rrm_configuration",
                    "add_event_driven_rrm_configuration_params",
                    self.have.get("add_event_driven_rrm_configuration"),
                ),
                (
                    "update_event_driven_rrm_configuration",
                    "update_event_driven_rrm_configuration_params",
                    self.have.get("update_event_driven_rrm_configuration"),
                ),
                # --- New FlexConnect Configuration ---
                (
                    "add_flexconnect_configuration",
                    "add_flexconnect_configuration_params",
                    self.have.get("add_flexconnect_configuration"),
                ),
                (
                    "update_flexconnect_configuration",
                    "update_flexconnect_configuration_params",
                    self.have.get("update_flexconnect_configuration"),
                ),
                # --- New Multicast Configuration ---
                (
                    "add_multicast_configuration",
                    "add_multicast_configuration_params",
                    self.have.get("add_multicast_configuration"),
                ),
                (
                    "update_multicast_configuration",
                    "update_multicast_configuration_params",
                    self.have.get("update_multicast_configuration"),
                ),
                # --- New RRM-FRA Configuration ---
                (
                    "add_rrm_fra_configuration",
                    "add_rrm_fra_configuration_params",
                    self.have.get("add_rrm_fra_configuration"),
                ),
                (
                    "update_rrm_fra_configuration",
                    "update_rrm_fra_configuration_params",
                    self.have.get("update_rrm_fra_configuration"),
                ),
                # --- New RRM General Configuration ---
                (
                    "add_rrm_general_configuration",
                    "add_rrm_general_configuration_params",
                    self.have.get("add_rrm_general_configuration"),
                ),
                (
                    "update_rrm_general_configuration",
                    "update_rrm_general_configuration_params",
                    self.have.get("update_rrm_general_configuration"),
                ),
                # -- 802_11_be_profiles --
                (
                    "add_80211be_profiles",
                    "add_80211be_profiles_params",
                    self.have.get("add_80211be_profiles"),
                ),
                (
                    "update_80211be_profiles",
                    "update_80211be_profiles_params",
                    self.have.get("update_80211be_profiles"),
                ),
                # -- flex_connect
                (
                    "update_flex_connect_configuration",
                    "update_flex_connect_configuration_params",
                    self.have.get("update_flex_connect_configuration"),
                ),
            ],
            "deleted": [
                ("delete_ssids", "delete_ssids_params", self.have.get("delete_ssids")),
                (
                    "delete_interfaces",
                    "delete_interfaces_params",
                    self.map_interface_params(self.have.get("delete_interfaces")),
                ),
                (
                    "delete_power_profiles",
                    "delete_power_profiles_params",
                    self.map_power_profiles_params(
                        self.have.get("delete_power_profiles")
                    ),
                ),
                (
                    "delete_access_point_profiles",
                    "delete_access_point_profiles_params",
                    self.map_access_point_profiles_params(
                        self.have.get("delete_access_point_profiles")
                    ),
                ),
                (
                    "delete_radio_frequency_profiles",
                    "delete_radio_frequency_profiles_params",
                    self.have.get("delete_radio_frequency_profiles"),
                ),
                (
                    "delete_anchor_groups",
                    "delete_anchor_groups_params",
                    self.have.get("delete_anchor_groups"),
                ),
                # --- New AAA Radius Attributes ---
                (
                    "delete_aaa_radius_attribute",
                    "delete_aaa_radius_attribute_params",
                    self.have.get("delete_aaa_radius_attribute"),
                ),
                # --- New Advanced SSIDs ---
                (
                    "delete_advanced_ssid",
                    "delete_advanced_ssid_params",
                    self.have.get("delete_advanced_ssid"),
                ),
                # --- New CleanAir ---
                (
                    "delete_clean_air_configuration",
                    "delete_clean_air_configuration_params",
                    self.have.get("delete_clean_air_configuration"),
                ),
                # --- New dot11ax Configuration ---
                (
                    "delete_dot11ax_configuration",
                    "delete_dot11ax_configuration_params",
                    self.have.get("delete_dot11ax_configuration"),
                ),
                # --- New dot11be Configuration ---
                (
                    "delete_dot11be_configuration",
                    "delete_dot11be_configuration_params",
                    self.have.get("delete_dot11be_configuration"),
                ),
                # --- New Event Driven RRM Configuration ---
                (
                    "delete_event_driven_rrm_configuration",
                    "delete_event_driven_rrm_configuration_params",
                    self.have.get("delete_event_driven_rrm_configuration"),
                ),
                # --- New FlexConnect Configuration ---
                (
                    "delete_flexconnect_configuration",
                    "delete_flexconnect_configuration_params",
                    self.have.get("delete_flexconnect_configuration"),
                ),
                # --- New Multicast Configuration ---
                (
                    "delete_multicast_configuration",
                    "delete_multicast_configuration_params",
                    self.have.get("delete_multicast_configuration"),
                ),
                # --- New RRM-FRA Configuration ---
                (
                    "delete_rrm_fra_configuration",
                    "delete_rrm_fra_configuration_params",
                    self.have.get("delete_rrm_fra_configuration"),
                ),
                # --- New RRM General Configuration ---
                (
                    "delete_rrm_general_configuration",
                    "delete_rrm_general_configuration_params",
                    self.have.get("delete_rrm_general_configuration"),
                ),
                # -- 802_11_be_profiles --
                (
                    "delete_80211be_profiles",
                    "delete_80211be_profiles_params",
                    self.have.get("delete_80211be_profiles"),
                ),
                # -- Flex Connect (Native VLAN) --
                (
                    "delete_flex_connect_configuration",
                    "delete_flex_connect_configuration_params",
                    self.have.get("delete_flex_connect_configuration"),
                ),
            ],
        }

        # Process operations based on the state
        if state in operations:
            self.log("Processing operations for state: {0}".format(state), "DEBUG")
            for index, (op_name, param_key, value) in enumerate(
                operations[state], start=1
            ):
                self.log(
                    "Iteration {0}: State '{1}', Operation '{2}', Parameter Key '{3}', Value '{4}'.".format(
                        index, state, op_name, param_key, value
                    ),
                    "DEBUG",
                )
                if value:
                    want[param_key] = value
                    self.log(
                        "Iteration {0}: State is '{1}' and '{2}' need to be processed in the Cisco Catalyst Center, "
                        "therefore setting '{3}' - {4}.".format(
                            index, state, op_name, param_key, want.get(param_key)
                        ),
                        "DEBUG",
                    )

        self.want = want
        self.log("Desired State (want): {0}".format(str(self.want)), "INFO")
        self.msg = "Successfully collected all parameters from the playbook for Wireless Design operations."
        self.status = "success"
        return self

    def verify_delete_80211be_profiles_requirement(self, be_profiles):
        """
        Determines which 802.11be profiles should be deleted based on the requested configuration.
        Delete is decided ONLY by profile name presence.
        """

        delete_profiles, no_delete_profiles = [], []

        # ------------------------------------------------------------------
        # Fetch existing profiles
        # ------------------------------------------------------------------
        existing_blocks = self.get_80211be_profiles()
        self.log(
            "Existing 802.11be Profiles RAW: {0}".format(existing_blocks),
            "DEBUG"
        )

        # ------------------------------------------------------------------
        # Normalize existing profiles into dict { profileName: profile }
        # ------------------------------------------------------------------
        existing_dict = {}

        for block in existing_blocks or []:
            # Flat response (Wi-Fi 7)
            if isinstance(block, dict) and block.get("profileName"):
                existing_dict[block["profileName"]] = block

            # Legacy instances[] response
            elif isinstance(block, dict):
                for inst in block.get("instances", []):
                    if inst.get("profileName"):
                        existing_dict[inst["profileName"]] = inst

        self.log(
            "Detected existing 802.11be profiles for delete: {0}"
            .format(list(existing_dict.keys())),
            "DEBUG"
        )

        # ------------------------------------------------------------------
        # Process delete requests
        # ------------------------------------------------------------------
        for profile in be_profiles or []:
            profile_name = profile.get("profile_name")

            if not profile_name:
                self.msg = "Missing required field 'profile_name' for 802.11be delete."
                self.set_operation_result(
                    "failed", False, self.msg, "ERROR"
                ).check_return_status()

            self.log(
                "Evaluating delete request for 802.11be profile: {0}"
                .format(profile_name),
                "DEBUG"
            )

            existing = existing_dict.get(profile_name)

            # =========================
            # Case 1: Profile exists → DELETE
            # =========================
            if existing:
                delete_profiles.append(
                    {
                        "id": existing["id"],
                        "profileName": profile_name,
                    }
                )

                self.log(
                    "802.11be profile '{0}' scheduled for deletion."
                    .format(profile_name),
                    "DEBUG"
                )

            # =========================
            # Case 2: Profile does not exist
            # =========================
            else:
                no_delete_profiles.append(profile_name)
                self.log(
                    "802.11be profile '{0}' does not exist. Nothing to delete."
                    .format(profile_name),
                    "INFO"
                )

        # ------------------------------------------------------------------
        # Final logs
        # ------------------------------------------------------------------
        self.log(
            "802.11be Profiles to Delete: {0}".format(delete_profiles),
            "DEBUG"
        )
        self.log(
            "802.11be Profiles not found for deletion: {0}"
            .format(no_delete_profiles),
            "DEBUG"
        )

        return delete_profiles, no_delete_profiles

    def verify_create_update_80211be_profiles_requirement(self, be_profiles):
        """
        Compares desired 802.11be profiles against existing ones and determines
        which need to be created, updated, or left unchanged.
        """

        add_profiles, update_profiles, no_update_profiles = [], [], []

        # ------------------------------------------------------------------
        # Fetch existing profiles
        # ------------------------------------------------------------------
        existing_blocks = self.get_80211be_profiles()
        self.log("Existing 802.11be Profiles RAW: {0}".format(existing_blocks), "DEBUG")

        # ------------------------------------------------------------------
        # Normalize existing profiles into dict { profileName: profile }
        # Handles BOTH:
        #   1) Flat API response (Wi-Fi 7)
        #   2) Wrapped instances[] (older patterns)
        # ------------------------------------------------------------------
        existing_dict = {}

        for block in existing_blocks or []:
            # Case 1: Flat profile object
            if isinstance(block, dict) and block.get("profileName"):
                existing_dict[block["profileName"]] = block

            # Case 2: Wrapped instances[]
            elif isinstance(block, dict):
                for inst in block.get("instances", []):
                    if inst.get("profileName"):
                        existing_dict[inst["profileName"]] = inst

        self.log(
            "Detected existing 802.11be profiles: {0}".format(list(existing_dict.keys())),
            "DEBUG"
        )

        # ------------------------------------------------------------------
        # Process desired profiles
        # ------------------------------------------------------------------
        for profile in be_profiles or []:
            profile_name = profile.get("profile_name")
            new_profile_name = profile.get("new_profile_name")

            if not profile_name:
                self.msg = "Missing required field 'profile_name' for 802.11be profile."
                self.set_operation_result(
                    "failed", False, self.msg, "ERROR"
                ).check_return_status()

            self.log("Evaluating 802.11be profile: {0}".format(profile_name), "DEBUG")

            # Desired config (FLAT, API-READY)
            desired_config = {
                "ofdmaDownLink": profile.get("ofdma_down_link", True),
                "ofdmaUpLink": profile.get("ofdma_up_link", True),
                "muMimoDownLink": profile.get("mu_mimo_down_link", False),
                "muMimoUpLink": profile.get("mu_mimo_up_link", False),
                "ofdmaMultiRu": profile.get("ofdma_multi_ru", False),
            }

            existing = existing_dict.get(profile_name)

            # ==============================================================
            # CASE 1: Profile EXISTS → UPDATE / RENAME / NO-CHANGE
            # ==============================================================
            if existing:
                self.log(
                    "802.11be profile '{0}' already exists.".format(profile_name),
                    "DEBUG"
                )

                details = self.get_80211be_profile_details(existing.get("id")) or {}

                existing_config = {
                    k: details.get(k)
                    for k in desired_config.keys()
                }

                config_changed = existing_config != desired_config

                # ------------------ Rename (with optional config update)
                if new_profile_name:
                    if (
                        new_profile_name in existing_dict
                        and new_profile_name != profile_name
                    ):
                        self.msg = (
                            "Cannot rename 802.11be profile '{0}' to '{1}' "
                            "- target name already exists."
                            .format(profile_name, new_profile_name)
                        )
                        self.set_operation_result(
                            "failed", False, self.msg, "ERROR"
                        ).check_return_status()

                    update_profiles.append(
                        {
                            "id": existing["id"],
                            "profileName": new_profile_name,
                            **desired_config,
                        }
                    )

                    self.log(
                        "802.11be profile '{0}' scheduled for rename/update."
                        .format(profile_name),
                        "DEBUG"
                    )

                # ------------------ Config update only
                elif config_changed:
                    update_profiles.append(
                        {
                            "id": existing["id"],
                            "profileName": profile_name,
                            **desired_config,
                        }
                    )

                    self.log(
                        "802.11be profile '{0}' marked for config update."
                        .format(profile_name),
                        "DEBUG"
                    )

                # ------------------ No change
                else:
                    no_update_profiles.append(details)
                    self.log(
                        "802.11be profile '{0}' requires no update."
                        .format(profile_name),
                        "DEBUG"
                    )

            # ==============================================================
            # CASE 2: Profile DOES NOT EXIST → ADD
            # ==============================================================
            else:
                add_profiles.append(
                    {
                        "profileName": profile_name,
                        **desired_config,
                    }
                )

                self.log(
                    "802.11be profile '{0}' scheduled for creation."
                    .format(profile_name),
                    "DEBUG"
                )

        # ------------------------------------------------------------------
        # Final logs
        # ------------------------------------------------------------------
        self.log("802.11be Profiles to Add: {0}".format(add_profiles), "DEBUG")
        self.log("802.11be Profiles to Update: {0}".format(update_profiles), "DEBUG")
        self.log(
            "802.11be Profiles with No Changes: {0}".format(no_update_profiles),
            "DEBUG",
        )

        return add_profiles, update_profiles, no_update_profiles

    def get_80211be_profile_details(self, profile_id):
        """
        Retrieves details of a specific 802.11be (Wi-Fi 7) profile by ID.

        Args:
            profile_id (str): UUID of the 802.11be profile.

        Returns:
            dict: Flat profile details.
                Example:
                {
                    "id": "<uuid>",
                    "profileName": "sample_design",
                    "ofdmaDownLink": True,
                    "ofdmaUpLink": True,
                    "muMimoDownLink": True,
                    "muMimoUpLink": True,
                    "ofdmaMultiRu": False
                }
        """

        if not profile_id:
            return {}

        self.log(
            "Fetching 802.11be profile details for id: {0}".format(profile_id),
            "DEBUG"
        )

        try:
            response = self.dnac._exec(
                family="wireless",
                function="get80211be_profile_by_id",
                params={"id": profile_id},
            )
            self.log(f"received API response for {response}", "DEBUG")
        except Exception as exc:
            self.msg = (
                "Failed to fetch 802.11be profile details for id '{0}': {1}".format(
                    profile_id, str(exc)
                )
            )
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()

        self.log(
            "Raw get80211beProfileById API response: {0}".format(response),
            "DEBUG"
        )

        data = response.get("response")

        if not data:
            return {}

        # Some DNAC versions return a list
        if isinstance(data, list):
            data = data[0] if data else {}

        # Return FLAT structure (important for diff comparison)
        return {
            "id": data.get("id"),
            "profileName": data.get("profileName"),
            "ofdmaDownLink": data.get("ofdmaDownLink"),
            "ofdmaUpLink": data.get("ofdmaUpLink"),
            "muMimoDownLink": data.get("muMimoDownLink"),
            "muMimoUpLink": data.get("muMimoUpLink"),
            "ofdmaMultiRu": data.get("ofdmaMultiRu"),
        }

    def get_80211be_profiles(self, profile_name=None, limit=500, offset=0):
        """
        Fetch existing 802.11be (Wi-Fi 7) profiles from Cisco Catalyst Center.

        Args:
            profile_name (str, optional): Profile name to filter results.
            limit (int, optional): Number of records to fetch.
            offset (int, optional): Pagination offset.

        Returns:
            list: List of profile blocks with instances.
                Example:
                [
                    {
                        "instances": [
                            {
                                "id": "<uuid>",
                                "profileName": "<name>",
                                ...
                            }
                        ]
                    }
                ]
        """

        self.log("Fetching 802.11be profiles from Cisco Catalyst Center", "DEBUG")

        # Build params (ONLY profileName + pagination)
        params = {}

        if profile_name:
            params["profileName"] = profile_name

        self.log(
            "get80211beProfiles request params: {0}".format(params),
            "DEBUG"
        )

        try:
            response = self.dnac._exec(
                family="wireless",
                function="get80211be_profiles",
                params=params
            )
            self.log(f"Received API response for {response}", "DEBUG")

        except Exception as e:
            self.msg = "Failed to fetch 802.11be profiles: {0}".format(str(e))
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()

        self.log(
            "Raw get80211beProfiles API response: {0}".format(response),
            "DEBUG"
        )

        # Normalize response safely
        profiles = response.get("response", [])

        if profiles is None:
            return []

        if not isinstance(profiles, list):
            profiles = [profiles]

        return profiles

    def process_update_flex_connect_configuration(self, params):
        """
        Handles the update of Flex Connect (Native VLAN) configuration
        in Cisco Catalyst Center.

        Args:
            params (list): A list of Flex Connect payloads to update.
                        Each payload must include:
                            - siteId (str)
                            - vlanId (int)

        Returns:
            self (with self.msg and self.status set)
        """

        self.log("Processing UPDATE for Flex Connect Configuration.", "INFO")
        self.log("Params for UPDATE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                site_id = payload.get("siteId")
                vlan_id = payload.get("vlanId")
                site_key = site_id or "<unknown-site>"

                self.log(
                    "Updating Flex Connect config: siteId='{0}', vlanId='{1}'".format(
                        site_id, vlan_id
                    ),
                    "DEBUG",
                )

                # -------------------------------------------------
                # Validate payload
                # -------------------------------------------------
                if not site_id or vlan_id is None:
                    results[site_key] = (
                        "Skipped update: missing 'siteId' or 'vlanId' in payload."
                    )
                    self.log(results[site_key], "ERROR")
                    continue

                # -------------------------------------------------
                # Build API payload (DNAC expects nativeVlanId)
                # -------------------------------------------------
                api_payload = {
                    "site_id": site_id,
                    "nativeVlanId": vlan_id
                }
                self.log(
                    "Constructed API payload for Flex Connect update: {0}"
                    .format(api_payload),
                    "DEBUG"
                )

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="update_native_vlan_settings_by_site",
                        op_modifies=True,
                        params=api_payload,
                    )

                    self.log("Received API response: {0}".format(response), "DEBUG")

                    # -------------------------------------------------
                    # Validate async task
                    # -------------------------------------------------
                    self.check_tasks_response_status(
                        response,
                        "updateNativeVlanSettingsBySite"
                    )

                    if self.status not in ["failed", "exited"]:
                        results[site_key] = (
                            "Successfully updated Flex Connect configuration."
                        )
                    else:
                        fail_reason = self.msg
                        results[site_key] = (
                            "Failed to update Flex Connect configuration: {0}"
                            .format(fail_reason)
                        )
                        self.log(results[site_key], "ERROR")

                except Exception as exc:
                    results[site_key] = (
                        "Exception while updating Flex Connect configuration: {0}"
                        .format(str(exc))
                    )
                    self.log(results[site_key], "ERROR")

            # -----------------------------------------------------
            # Final aggregated result
            # -----------------------------------------------------
            self.msg = {"flex_connect_update": results}
            self.status = (
                "failed"
                if all(
                    ("Failed" in v or "Exception" in v or "Skipped" in v)
                    for v in results.values()
                )
                else "success"
            )

            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {
                "flex_connect_update": "Exception during update: {0}".format(str(exc))
            }
            self.status = "failed"
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()
            return self

    def get_diff_merged(self):
        """
        Executes the merge operations for various network configurations in the Cisco Catalyst Center.
        This method processes additions and updates for SSIDs, interfaces, power profiles, access point profiles,
        radio frequency profiles, and anchor groups. It logs detailed information about each operation,
        updates the result status, and returns a consolidated result.
        """
        self.log("Starting 'get_diff_merged' operation.", "INFO")

        final_status_list = []
        result_details = {}

        # Define a list of operations for adding and updating configurations
        self.log("Defining operations for addition and update.", "DEBUG")
        operations = [
            ("add_ssids_params", "ADD SSIDs", self.process_add_ssids),
            ("update_ssids_params", "UPDATE SSIDs", self.process_update_ssids),
            ("add_interfaces_params", "ADD Interfaces", self.process_add_interfaces),
            (
                "update_interfaces_params",
                "UPDATE Interfaces",
                self.process_update_interfaces,
            ),
            (
                "add_power_profiles_params",
                "ADD Power Profiles",
                self.process_add_power_profiles,
            ),
            (
                "update_power_profiles_params",
                "UPDATE Power Profiles",
                self.process_update_power_profiles,
            ),
            (
                "add_access_point_profiles_params",
                "ADD Access Point Profiles",
                self.process_add_access_point_profiles,
            ),
            (
                "update_access_point_profiles_params",
                "UPDATE Access Point Profiles",
                self.process_update_access_point_profiles,
            ),
            (
                "add_radio_frequency_profiles_params",
                "ADD Radio Frequency Profiles",
                self.process_add_radio_frequency_profiles,
            ),
            (
                "update_radio_frequency_profiles_params",
                "UPDATE Radio Frequency Profiles",
                self.process_update_radio_frequency_profiles,
            ),
            (
                "add_anchor_groups_params",
                "ADD Anchor Groups",
                self.process_add_anchor_groups,
            ),
            (
                "update_anchor_groups_params",
                "UPDATE Anchor Groups",
                self.process_update_anchor_groups,
            ),
            # new enhancement for AAA Radius Attributes
            (
                "add_aaa_radius_attribute_params",
                "ADD AAA Radius Attributes",
                self.process_add_aaa_radius_attributes
            ),
            (
                "update_aaa_radius_attribute_params",
                "UPDATE AAA Radius Attributes",
                self.process_update_aaa_radius_attributes
            ),
            # --- New Advanced SSID ---
            (
                "add_advanced_ssid_params",
                "ADD Advanced SSIDs",
                self.process_add_advanced_ssids
            ),
            (
                "update_advanced_ssid_params",
                "UPDATE Advanced SSIDs",
                self.process_update_advanced_ssids
            ),
            # --- New CleanAir ---
            (
                "add_clean_air_configuration_params",
                "ADD CleanAir Profiles",
                self.process_add_clean_air,
            ),
            (
                "update_clean_air_configuration_params",
                "UPDATE CleanAir Profiles",
                self.process_update_clean_air,
            ),
            # --- New dot11ax Configuration ---
            (
                "add_dot11ax_configuration_params",
                "ADD dot11ax Configurations",
                self.process_add_dot11ax,
            ),
            (
                "update_dot11ax_configuration_params",
                "UPDATE dot11ax Configurations",
                self.process_update_dot11ax,
            ),
            # --- New dot11be Configuration ---
            (
                "add_dot11be_configuration_params",
                "ADD dot11be Configurations",
                self.process_add_dot11be,
            ),
            (
                "update_dot11be_configuration_params",
                "UPDATE dot11be Configurations",
                self.process_update_dot11be,
            ),
            # --- New Event-Driven RRM Configuration ---
            (
                "add_event_driven_rrm_configuration_params",
                "ADD Event-Driven RRM Configurations",
                self.process_add_event_driven_rrm,
            ),
            (
                "update_event_driven_rrm_configuration_params",
                "UPDATE Event-Driven RRM Configurations",
                self.process_update_event_driven_rrm,
            ),
            # --- New FlexConnect Configuration ---
            (
                "add_flexconnect_configuration_params",
                "ADD FlexConnect Configurations",
                self.process_add_flexconnect,
            ),
            (
                "update_flexconnect_configuration_params",
                "UPDATE FlexConnect Configurations",
                self.process_update_flexconnect,
            ),
            # --- New Multicast Configuration ---
            (
                "add_multicast_configuration_params",
                "ADD Multicast Configurations",
                self.process_add_multicast,
            ),
            (
                "update_multicast_configuration_params",
                "UPDATE Multicast Configurations",
                self.process_update_multicast,
            ),
            # --- New RRM-FRA Configuration ---
            (
                "add_rrm_fra_configuration_params",
                "ADD RRM-FRA Configurations",
                self.process_add_rrm_fra,
            ),
            (
                "update_rrm_fra_configuration_params",
                "UPDATE RRM-FRA Configurations",
                self.process_update_rrm_fra,
            ),
            # --- New RRM General Configuration ---
            (
                "add_rrm_general_configuration_params",
                "ADD RRM General Configurations",
                self.process_add_rrm_general,
            ),
            (
                "update_rrm_general_configuration_params",
                "UPDATE RRM General Configurations",
                self.process_update_rrm_general,
            ),
            # -- 802_11_be_profiles --
            (
                "add_80211be_profiles_params",
                "ADD 802.11be Profiles",
                self.process_add_802_11_be_profile,
            ),
            (
                "update_80211be_profiles_params",
                "UPDATE 802.11be Profiles",
                self.process_update_802_11_be_profile,
            ),
            # -- Flex Connect (Native VLAN) --
            (
                "update_flex_connect_configuration_params",
                "UPDATE Flex Connect Configuration",
                self.process_update_flex_connect_configuration,
            ),
        ]

        # Iterate over operations and process them
        self.log("Beginning iteration over defined operations for processing.", "DEBUG")
        for index, (param_key, operation_name, operation_func) in enumerate(
            operations, start=1
        ):
            self.log(
                "Iteration {0}: Checking parameters for {1} operation with param_key '{2}'.".format(
                    index, operation_name, param_key
                ),
                "DEBUG",
            )
            params = self.want.get(param_key)
            if params:
                self.log(
                    "Iteration {0}: Parameters found for {1}. Starting processing.".format(
                        index, operation_name
                    ),
                    "INFO",
                )
                operation_func(params).check_return_status()
                self.log(
                    "Iteration {0}: Completed processing of {1}.".format(
                        index, operation_name
                    ),
                    "INFO",
                )
                result = self.msg
                result_details.update(result)
                final_status_list.append(self.status)
            else:
                self.log(
                    "Iteration {0}: No parameters found for {1}. Skipping operation.".format(
                        index, operation_name
                    ),
                    "WARNING",
                )

        self.log("Final Statuses = {0}".format(final_status_list), "DEBUG")

        # Handle the case where no operations are required
        if not final_status_list:
            self.msg = "No Wireless Design operations were required for the provided parameters in the Cisco Catalyst Center."
            self.set_operation_result("ok", False, self.msg, "INFO")
            self.log("No operations were performed.", "DEBUG")
            return self

        # Process the final result
        final_status, is_changed = self.process_final_result(final_status_list)
        self.msg = result_details
        self.log(
            "Completed 'get_diff_merged' operation with final status: {0}, is_changed: {1}".format(
                final_status, is_changed
            ),
            "INFO",
        )
        self.set_operation_result(final_status, is_changed, self.msg, "INFO")
        return self

    def get_diff_deleted(self):
        """
        Executes the deletion operations for various network configurations in the Cisco Catalyst Center.
        This method processes deletions for SSIDs, interfaces, power profiles, access point profiles,
        radio frequency profiles, and anchor groups. It logs detailed information about each operation,
        updates the result status, and returns a consolidated result.
        """
        self.log("Starting 'get_diff_deleted' operation.", "INFO")

        final_status_list = []
        result_details = {}

        # Define a list of operations to delete
        self.log("Defining operations for deletion.", "DEBUG")
        operations = [
            ("delete_ssids_params", "DELETE SSIDs", self.process_delete_ssids),
            (
                "delete_interfaces_params",
                "DELETE Interfaces",
                self.process_delete_interfaces,
            ),
            (
                "delete_power_profiles_params",
                "DELETE Power Profiles",
                self.process_delete_power_profiles,
            ),
            (
                "delete_access_point_profiles_params",
                "DELETE Access Point Profiles",
                self.process_delete_access_point_profiles,
            ),
            (
                "delete_radio_frequency_profiles_params",
                "DELETE Radio Frequency Profiles",
                self.process_delete_radio_frequency_profiles,
            ),
            (
                "delete_anchor_groups_params",
                "DELETE Anchor Groups",
                self.process_delete_anchor_groups,
            ),
            (
                "delete_aaa_radius_attribute_params",
                "DELETE AAA Radius Attributes",
                self.process_delete_aaa_radius_attributes
            ),
            # --- New Advanced SSIDs ---
            ("delete_advanced_ssid_params", "DELETE Advanced SSIDs", self.process_delete_advanced_ssids),
            # --- New CleanAir ---
            (
                "delete_clean_air_configuration_params",
                "DELETE CleanAir Profiles",
                self.process_delete_clean_air,
            ),
            # --- New dot11ax Configuration ---
            (
                "delete_dot11ax_configuration_params",
                "DELETE dot11ax Configurations",
                self.process_delete_dot11ax,
            ),
            # --- New dot11be Configuration ---
            (
                "delete_dot11be_configuration_params",
                "DELETE dot11be Configurations",
                self.process_delete_dot11be,
            ),
            # --- New dot11be Configuration ---
            (
                "delete_event_driven_rrm_configuration_params",
                "DELETE Event-Driven RRM Configurations",
                self.process_delete_event_driven_rrm,
            ),
            # --- New FlexConnect Configuration ---
            (
                "delete_flexconnect_configuration_params",
                "DELETE FlexConnect Configurations",
                self.process_delete_flexconnect,
            ),
            # --- New Multicast Configuration ---
            (
                "delete_multicast_configuration_params",
                "DELETE Multicast Configurations",
                self.process_delete_multicast,
            ),
            # --- New RRM-FRA Configuration ---
            (
                "delete_rrm_fra_configuration_params",
                "DELETE RRM-FRA Configurations",
                self.process_delete_rrm_fra,
            ),
            # --- New RRM-General Configuration ---
            (
                "delete_rrm_general_configuration_params",
                "DELETE RRM-General Configurations",
                self.process_delete_rrm_general,
            ),
            # --- 802.11be Profiles ---
            (
                "delete_80211be_profiles_params",
                "DELETE 802.11be Profiles",
                self.process_delete_802_11_be_profile,
            ),
            # --- Flex Connect (Native VLAN) ---
            (
                "delete_flex_connect_configuration_params",
                "DELETE Flex Connect Configuration",
                self.process_delete_flex_connect_configuration,
            ),

        ]

        # Iterate over operations and process deletions
        self.log("Beginning iteration over defined operations for deletion.", "DEBUG")
        for index, (param_key, operation_name, operation_func) in enumerate(
            operations, start=1
        ):
            self.log(
                "Iteration {0}: Checking parameters for {1} operation with param_key '{2}'.".format(
                    index, operation_name, param_key
                ),
                "DEBUG",
            )
            params = self.want.get(param_key)
            if params:
                self.log(
                    "Iteration {0}: Parameters found for {1}. Starting processing.".format(
                        index, operation_name
                    ),
                    "INFO",
                )
                operation_func(params).check_return_status()
                self.log(
                    "Iteration {0}: Completed processing of {1}.".format(
                        index, operation_name
                    ),
                    "INFO",
                )
                result = self.msg
                result_details.update(result)
                final_status_list.append(self.status)
            else:
                self.log(
                    "Iteration {0}: No parameters found for {1}. Skipping operation.".format(
                        index, operation_name
                    ),
                    "WARNING",
                )

        self.log("Final Statuses = {0}".format(final_status_list), "DEBUG")

        # Handle the case where no deletions are required
        if not final_status_list:
            self.msg = "No deletions were required for the provided parameters in the Cisco Catalyst Center."
            self.set_operation_result("ok", False, self.msg, "INFO")
            self.log("No deletion operations were performed.", "DEBUG")
            return self

        # Process the final result
        final_status, is_changed = self.process_final_result(final_status_list)
        self.msg = result_details
        self.log(
            "Completed 'get_diff_deleted' operation with final status: {0}, is_changed: {1}".format(
                final_status, is_changed
            ),
            "INFO",
        )
        self.set_operation_result(final_status, is_changed, self.msg, "INFO")
        return self

    def process_delete_flex_connect_configuration(self, params):
        """
        Handles deletion/reset of Flex Connect (Native VLAN) configuration
        in Cisco Catalyst Center.

        Args:
            params (list): A list of Flex Connect delete/reset payloads.
                        Each payload must include:
                            - siteId (str)
                            - removeOverrideInHierarchy (bool)

        Returns:
            self (with self.msg and self.status set)
        """

        self.log("Processing DELETE for Flex Connect Configuration.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                site_id = payload.get("siteId")
                remove_override = payload.get("removeOverrideInHierarchy", True)
                site_key = site_id or "<unknown-site>"

                self.log(
                    "Deleting Flex Connect config: siteId='{0}', removeOverrideInHierarchy='{1}'"
                    .format(site_id, remove_override),
                    "DEBUG",
                )

                # -------------------------------------------------
                # Validate payload
                # -------------------------------------------------
                if not site_id:
                    results[site_key] = (
                        "Skipped delete: missing 'siteId' in payload."
                    )
                    self.log(results[site_key], "ERROR")
                    continue

                # -------------------------------------------------
                # Build API payload
                # -------------------------------------------------
                api_payload = {
                    "site_id": site_id,
                    "removeOverrideInHierarchy": remove_override
                }
                self.log(
                    "Constructed API payload for Flex Connect delete: {0}"
                    .format(api_payload),
                    "DEBUG"
                )

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_native_vlan_settings_by_site",
                        op_modifies=True,
                        params=api_payload,
                    )

                    self.log("Received API response: {0}".format(response), "DEBUG")

                    # -------------------------------------------------
                    # Validate async task
                    # -------------------------------------------------
                    self.check_tasks_response_status(
                        response,
                        "deleteNativeVlanSettingsBySite"
                    )

                    if self.status not in ["failed", "exited"]:
                        results[site_key] = (
                            "Successfully deleted/reset Flex Connect configuration."
                        )
                    else:
                        fail_reason = self.msg
                        results[site_key] = (
                            "Failed to delete/reset Flex Connect configuration: {0}"
                            .format(fail_reason)
                        )
                        self.log(results[site_key], "ERROR")

                except Exception as exc:
                    results[site_key] = (
                        "Exception while deleting Flex Connect configuration: {0}"
                        .format(str(exc))
                    )
                    self.log(results[site_key], "ERROR")

            # -----------------------------------------------------
            # Final aggregated result
            # -----------------------------------------------------
            self.msg = {"flex_connect_delete": results}
            self.status = (
                "failed"
                if all(
                    ("Failed" in v or "Exception" in v or "Skipped" in v)
                    for v in results.values()
                )
                else "success"
            )

            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {
                "flex_connect_delete": "Exception during delete: {0}".format(str(exc))
            }
            self.status = "failed"
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()
            return self

    def process_delete_802_11_be_profile(self, params):
        """
        Handles deletion of 802.11be (Wi-Fi 7) profiles in Cisco Catalyst Center.

        Args:
            params (list): List of payloads returned by
                        verify_delete_80211be_profiles_requirement().
                        Each payload must include:
                            - id (str)
                            - profileName (str)

        Returns:
            self (with self.msg and self.status set)
        """

        self.log("Processing DELETE for 802.11be Profiles.", "INFO")
        self.log("Params for DELETE: {0}".format(params), "DEBUG")

        results = {}

        try:
            for payload in params or []:
                profile_name = payload.get("profileName") or "<unknown>"
                profile_id = payload.get("id")

                self.log(
                    "Deleting 802.11be profile: profile='{0}', id='{1}'"
                    .format(profile_name, profile_id),
                    "DEBUG"
                )

                # Safety check
                if not profile_id:
                    results[profile_name] = "Skipped delete: missing 'id' in payload."
                    self.log(results[profile_name], "ERROR")
                    continue

                try:
                    response = self.dnac._exec(
                        family="wireless",
                        function="delete_a80211be_profile",
                        op_modifies=True,
                        params={"id": profile_id},
                    )

                    self.log(
                        "Received API response for delete: {0}".format(response),
                        "DEBUG"
                    )

                    # Validate async task (DNAC standard)
                    self.check_tasks_response_status(
                        response, "delete_a80211be_profile"
                    )

                    if self.status not in ["failed", "exited"]:
                        results[profile_name] = "Successfully deleted 802.11be profile."
                    else:
                        fail_reason = self.msg
                        results[profile_name] = (
                            "Failed to delete 802.11be profile: {0}".format(fail_reason)
                        )
                        self.log(results[profile_name], "ERROR")

                except Exception as exc:
                    results[profile_name] = (
                        "Exception while deleting 802.11be profile: {0}".format(str(exc))
                    )
                    self.log(results[profile_name], "ERROR")

            # --------------------------------------------------
            # Final aggregated result - count successes and failures
            # --------------------------------------------------
            success_count = sum(
                1 for v in results.values()
                if "Successfully" in v
            )
            failure_count = sum(
                1 for v in results.values()
                if ("Failed" in v or "Exception" in v or "Skipped" in v)
            )

            # Mark as failed if ANY profile deletion failed (Ansible binary outcome)
            if failure_count > 0:
                self.status = "failed"
                msg = (
                    "802.11be Profile deletion completed with {0} success(es) and {1} failure(s). "
                    "Details: {2}".format(success_count, failure_count, results)
                )
            else:
                self.status = "success"
                msg = (
                    "Successfully deleted {0} 802.11be profile(s). "
                    "Details: {1}".format(success_count, results)
                )

            self.msg = {"80211be_delete": msg}
            self.set_operation_result(self.status, True, self.msg, "INFO")
            return self

        except Exception as exc:
            self.msg = {
                "80211be_delete": "Exception during delete: {0}".format(str(exc))
            }
            self.status = "failed"
            self.set_operation_result(
                "failed", False, self.msg, "ERROR"
            ).check_return_status()
            return self

    def verify_diff_merged(self):
        """
        Verifies the merge operations for various network configurations.
        This method ensures that the add and update operations for SSIDs, interfaces, power profiles,
        access point profiles, radio frequency profiles, and anchor groups are verified. It logs detailed
        information about each operation, including the parameter key being processed, and confirms that
        each merge operation is performed as expected.
        """
        self.log("Starting 'verify_diff_merged' operation.", "INFO")

        # Define a list of operations with their parameter keys, descriptive names, and corresponding functions
        self.log(
            "Defining operations and their corresponding verification functions.",
            "DEBUG",
        )
        operations = [
            ("add_ssids_params", "ADD SSIDs", self.verify_add_ssids_operation),
            ("update_ssids_params", "UPDATE SSIDs", self.verify_update_ssids_operation),
            (
                "add_interfaces_params",
                "ADD Interfaces",
                self.verify_add_interfaces_operation,
            ),
            (
                "update_interfaces_params",
                "UPDATE Interfaces",
                self.verify_update_interfaces_operation,
            ),
            (
                "add_power_profiles_params",
                "ADD Power Profiles",
                self.verify_add_power_profiles_operation,
            ),
            (
                "update_power_profiles_params",
                "UPDATE Power Profiles",
                self.verify_update_power_profiles_operation,
            ),
            (
                "add_access_point_profiles_params",
                "ADD Access Point Profiles",
                self.verify_add_access_point_profiles_operation,
            ),
            (
                "update_access_point_profiles_params",
                "UPDATE Access Point Profiles",
                self.verify_update_access_point_profiles_operation,
            ),
            (
                "add_radio_frequency_profiles_params",
                "ADD Radio Frequency Profiles",
                self.verify_add_radio_frequency_profiles_operation,
            ),
            (
                "update_radio_frequency_profiles_params",
                "UPDATE Radio Frequency Profiles",
                self.verify_update_radio_frequency_profiles_operation,
            ),
            (
                "add_anchor_groups_params",
                "ADD Anchor Groups",
                self.verify_add_anchor_groups_operation,
            ),
            (
                "update_anchor_groups_params",
                "UPDATE Anchor Groups",
                self.verify_update_anchor_groups_operation,
            ),
        ]

        # Iterate over operations and perform verification
        self.log(
            "Beginning iteration over defined operations for verification.", "DEBUG"
        )
        for index, (param_key, operation_name, operation_func) in enumerate(
            operations, start=1
        ):
            # Retrieve the parameters for the current operation
            self.log(
                "Checking parameters for operation {0}: '{1}' with param_key '{2}'.".format(
                    index, operation_name, param_key
                ),
                "DEBUG",
            )
            params = self.want.get(param_key)
            if params:
                self.log(
                    "Iteration {0}: Parameters found for {1} operation. Starting verification.".format(
                        index, operation_name
                    ),
                    "INFO",
                )
                operation_func(params)
                self.log(
                    "Iteration {0}: Successfully completed verification of {1} operation with param_key '{2}'.".format(
                        index, operation_name, param_key
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: No parameters found for {1} operation. Skipping verification.".format(
                        index, operation_name
                    ),
                    "WARNING",
                )

        self.log("Completed 'verify_diff_merged' operation.", "INFO")
        return self

    def verify_diff_deleted(self):
        """
        Verifies the deletion operations for various network configurations.
        This method checks the deletion parameters for SSIDs, interfaces, power profiles,
        access point profiles, radio frequency profiles, and anchor groups, ensuring that
        each specified delete operation is completed as expected. It logs the start and
        completion of each verification process.
        """
        self.log("Starting 'verify_diff_deleted' operation.", "INFO")

        # Define a list of operations to verify
        self.log(
            "Defining operations and their corresponding verification functions.",
            "DEBUG",
        )
        operations = [
            (
                "delete_ssids_params",
                "DELETE Port SSIDs",
                self.verify_delete_ssids_operation,
            ),
            (
                "delete_interfaces_params",
                "DELETE Interfaces",
                self.verify_delete_interfaces_operation,
            ),
            (
                "delete_power_profiles_params",
                "DELETE Power Profiles",
                self.verify_delete_power_profiles_operation,
            ),
            (
                "delete_access_point_profiles_params",
                "DELETE Access Point Profiles",
                self.verify_delete_access_point_profiles_operation,
            ),
            (
                "delete_radio_frequency_profiles_params",
                "DELETE Radio Frequency Profiles",
                self.verify_delete_radio_frequency_profiles_operation,
            ),
            (
                "delete_anchor_groups_params",
                "DELETE Anchor Groups",
                self.verify_delete_anchor_groups_operation,
            ),
        ]

        # Iterate over operations and perform verification
        self.log(
            "Beginning iteration over defined operations for verification.", "DEBUG"
        )
        for index, (param_key, operation_name, operation_func) in enumerate(
            operations, start=1
        ):
            self.log(
                "Checking parameters for operation {0}: '{1}' with param_key '{2}'.".format(
                    index, operation_name, param_key
                ),
                "DEBUG",
            )
            params = self.want.get(param_key)
            if params:
                self.log(
                    "Iteration {0}: Found parameters for {1} operation. Starting verification.".format(
                        index, operation_name
                    ),
                    "INFO",
                )
                operation_func(params)
                self.log(
                    "Iteration {0}: Successfully completed verification of {1} operation with param_key '{2}'.".format(
                        index, operation_name, param_key
                    ),
                    "INFO",
                )
            else:
                self.log(
                    "Iteration {0}: No parameters found for {1} operation. Skipping verification.".format(
                        index, operation_name
                    ),
                    "WARNING",
                )

        self.log("Completed 'verify_diff_deleted' operation.", "INFO")
        return self


def main():
    """main entry point for module execution"""
    # Define the specification for the module"s arguments
    element_spec = {
        "dnac_host": {"required": True, "type": "str"},
        "dnac_port": {"type": "str", "default": "443"},
        "dnac_username": {"type": "str", "default": "admin", "aliases": ["user"]},
        "dnac_password": {"type": "str", "no_log": True},
        "dnac_verify": {"type": "bool", "default": "True"},
        "dnac_version": {"type": "str", "default": "2.2.3.3"},
        "dnac_debug": {"type": "bool", "default": False},
        "dnac_log_level": {"type": "str", "default": "WARNING"},
        "dnac_log_file_path": {"type": "str", "default": "dnac.log"},
        "dnac_log_append": {"type": "bool", "default": True},
        "dnac_log": {"type": "bool", "default": False},
        "validate_response_schema": {"type": "bool", "default": True},
        "config_verify": {"type": "bool", "default": False},
        "dnac_api_task_timeout": {"type": "int", "default": 1200},
        "dnac_task_poll_interval": {"type": "int", "default": 2},
        "config": {"required": True, "type": "list", "elements": "dict"},
        "state": {"default": "merged", "choices": ["merged", "deleted"]},
    }

    # Initialize the Ansible module with the provided argument specifications
    module = AnsibleModule(argument_spec=element_spec, supports_check_mode=False)

    # Initialize the NetworkCompliance object with the module
    ccc_wireless_design = WirelessDesign(module)
    if (
        ccc_wireless_design.compare_dnac_versions(
            ccc_wireless_design.get_ccc_version(), "2.3.7.9"
        )
        < 0
    ):
        ccc_wireless_design.msg = (
            "The specified version '{0}' does not support the Wireless Design Operations. Supported versions start "
            "  from '2.3.7.9' onwards. Version '2.3.7.9' introduces APIs for creating, updating and deleting the "
            "SSID(s), Interface(s), Power Profile(s), Access Point Profile(s), Radio Frequency Profile(s), Anchor Group(s)".format(
                ccc_wireless_design.get_ccc_version()
            )
        )
        ccc_wireless_design.set_operation_result(
            "failed", False, ccc_wireless_design.msg, "ERROR"
        ).check_return_status()

    # Get the state parameter from the provided parameters
    state = ccc_wireless_design.params.get("state")

    # Check if the state is valid
    if state not in ccc_wireless_design.supported_states:
        ccc_wireless_design.status = "invalid"
        ccc_wireless_design.msg = "State {0} is invalid".format(state)
        ccc_wireless_design.check_return_status()

    # Validate the input parameters and check the return status
    ccc_wireless_design.validate_input().check_return_status()

    # Get the config_verify parameter from the provided parameters
    config_verify = ccc_wireless_design.params.get("config_verify")

    # Iterate over the validated configuration parameters
    for config in ccc_wireless_design.validated_config:
        ccc_wireless_design.reset_values()
        ccc_wireless_design.get_have(config, state).check_return_status()
        ccc_wireless_design.get_want(config, state).check_return_status()
        ccc_wireless_design.get_diff_state_apply[state]().check_return_status()

        if config_verify:
            ccc_wireless_design.verify_diff_state_apply[state]().check_return_status()

    module.exit_json(**ccc_wireless_design.result)


if __name__ == "__main__":
    main()
