Safebreach

For enterprises using SafeBreach and XSOAR, integrating this package streamlines operations by allowing you to operate SafeBreach through XSOAR, making SafeBreach an integral part of the enterprise workflows. This integration includes commands for managing tests, insight indicators, simulators and deployments, users, API keys, integration issues, and more.

Deception & Breach Simulation · SafeBreach - Breach and Attack Simulation platform

Details

IDSafebreach
ProviderSafeBreach
CategoryDeception & Breach Simulation
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

For enterprises using SafeBreach and XSOAR, integrating this package streamlines operations by allowing you to operate SafeBreach through XSOAR, making SafeBreach an integral part of the enterprise workflows. This integration includes commands for managing tests, insight indicators, simulators and deployments, users, API keys, integration issues, and more.
This integration was integrated and tested with version 2024Q1.4 of Safebreach.

Configure Safebreach in Cortex

Parameter Description Required
Server URL This is base URL for your instance. True
API Key This is API key for your instance, this can be created in Safe Breach User Administration -> API keys, it must be saved as there is no way to view it again. True
Account ID This is account ID of account with which we want to get data from safebreach True
Verify SSL Certificate This Field is useful for checking if the certificate of SSL for HTTPS is valid or not False
Use system proxy settings This Field is useful for asking integration to use default system proxy settings. False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

safebreach-approve-simulator


This command approves the simulator with the specified simulator_id.

Base Command

safebreach-approve-simulator

Input

Argument Name Description Required
simulator_id ID of simulator to approve, in case unsure then please call safebreach-get-all-simulators and search for simulator name. Required

Context Output

Path Type Description
SafeBreach.Simulator.is_enabled String Whether the simulator is enabled or not.
SafeBreach.Simulator.simulator_id String The Id of given simulator.
SafeBreach.Simulator.name String name for given simulator.
SafeBreach.Simulator.account_id String Account Id of account Hosting given simulator.
SafeBreach.Simulator.is_critical String Whether the simulator is critical.
SafeBreach.Simulator.is_exfiltration String If Simulator is exfiltration target.
SafeBreach.Simulator.is_infiltration String If simulator is infiltration target.
SafeBreach.Simulator.is_mail_target String If simulator is mail target.
SafeBreach.Simulator.is_mail_attacker String If simulator is mail attacker.
SafeBreach.Simulator.is_pre_executor String Whether the simulator is pre executor.
SafeBreach.Simulator.is_aws_attacker String if the given simulator is aws attacker.
SafeBreach.Simulator.is_azure_attacker String If the given simulator is azure attacker.
SafeBreach.Simulator.external_ip String external ip of given simulator.
SafeBreach.Simulator.internal_ip String internal ip of given simulator.
SafeBreach.Simulator.is_web_application_attacker String Whether the simulator is Web application attacker.
SafeBreach.Simulator.preferred_interface String Preferred simulator interface.
SafeBreach.Simulator.preferred_ip String Preferred Ip of simulator.
SafeBreach.Simulator.hostname String Hostname of given simulator.
SafeBreach.Simulator.connection_type String connection_type of given simulator.
SafeBreach.Simulator.simulator_status String status of the simulator.
SafeBreach.Simulator.connection_status String connection status of simulator.
SafeBreach.Simulator.simulator_framework_version String Framework version of simulator.
SafeBreach.Simulator.operating_system_type String operating system type of given simulator.
SafeBreach.Simulator.operating_system String Operating system of given simulator.
SafeBreach.Simulator.execution_hostname String Execution Hostname of the given simulator.
SafeBreach.Simulator.deployments String deployments simulator is part of.
SafeBreach.Simulator.created_at String Creation datetime of simulator.
SafeBreach.Simulator.updated_at String Update datetime of given simulator.
SafeBreach.Simulator.deleted_at String deletion datetime of given simulator.
SafeBreach.Simulator.assets String Assets of given simulator.
SafeBreach.Simulator.simulation_users String simulator users list.
SafeBreach.Simulator.proxies String Proxies of simulator.
SafeBreach.Simulator.advanced_actions String Advanced simulator details.

safebreach-generate-api-key


This command creates an API key with the name and optionally the description provided. The API key created will be shown on the Settings > API Keys page of SafeBreach Management. Important: The API key generated can be seen only once, so it is recommended to store/save it in a safe place for further use.

Base Command

safebreach-generate-api-key

Input

Argument Name Description Required
name
Name of the API Key to create. This will be the name shown in UI for API key under API keys section
.
Required
description
Description of the API Key to create. This is not a required field but it is recommended to store a
description for easier identification if your use case requires using multiple API keys for multiple tasks.
.
Optional

Context Output

Path Type Description
SafeBreach.API.name String The Name of API Key generated through this command, This will match the input name of the command.
SafeBreach.API.description String The Description of API Key created. this will be same as input description given for the command.
SafeBreach.API.created_by String The id of user who generated this API key.
SafeBreach.API.created_bt String The creation date and time of API key.
SafeBreach.API.key String The value of API key generated. store this for further use as this will only be shown once

safebreach-create-deployment


This command creates a deployment, grouping the list of simulators provided with a name and optionally a description.

Base Command

safebreach-create-deployment

Input

Argument Name Description Required
name Name of the deployment to create. this will be shown as name in deployments page of safebreach. Required
description Description of the deployment to create. This will show as description of the deployment in your safebreach instance. It is generally preferable to give description while creating a deployment for easier identification. Optional
simulators Deployment manages multiple simulators as single group. This parameter receives a comma separated list of IDs of all simulators that should be part of this deployment Simulator ID can be retrieved from safebreach-get-all-simulator-details . Optional

Context Output

Path Type Description
SafeBreach.Deployment.id Number The ID of deployment created. this Id can be used to update ,delete deployment as deployment_id field of the deployment.
SafeBreach.Deployment.account_id String This field shows account ID of user who has created the account.
SafeBreach.Deployment.name String The name of deployment created. this will be name which will be shown on deployments page of safebreach and name that is given as input to the command.
SafeBreach.Deployment.created_at String The creation date and time of deployment , this will be closer to command execution time if the deployment creation is successful.
SafeBreach.Deployment.description String The description of the deployment created will be shown in description part of the table in safebreach.
SafeBreach.Deployment.simulators String The simulators that are part of deployment.

safebreach-create-user


This command creates a user, including credentials and permissions.

Base Command

safebreach-create-user

Input

Argument Name Description Required
name Name of the user to create. Required
email Email of the user to Create. Required
is_active If the user will be activated upon creation. Setting this parameter to ‘true’ active as soon as this command succeeds. Setting to ‘false’, will require to activate the user by an administrator. Possible values are: true, false. Default is true. Possible values are: true, false. Default is true. Optional
email_post_creation Whether to send an email with login information to a newly crated user. Possible values are: true, false. Default is false. Possible values are: true, false. Default is true. Optional
password Enforce password change on user creation. Possible values are: true, false. Default is false. Required
change_password_on_create Should user change password on creation. when this is set to true then user will have to reset password on the next login, this can be used if we want user to reset password as soon as they login. Possible values are: true, false. Default is false. Optional
user_role Role of the user being created. Possible values are: viewer, administrator, contentDeveloper, operator. Default is viewer. Possible values are: viewer, administrator, contentDeveloper, operator. Default is viewer. Optional
deployments Comma separated ID of all deployments the user should be part of. The deployment IDs can be retrieved from ‘list-deployments’ command or from UI directly but care should be noted that only deployment ids of deployments which haven’t been deleted will be shown here and after creation of user. for example if 1,2,3 are deployment ids given while creation but if 2 is deleted then when user is created , he will only have 1,3. Optional

Context Output

Path Type Description
SafeBreach.User.id Number The ID of User created.
SafeBreach.User.name String The name of User created.
SafeBreach.User.email String The email of User created.
SafeBreach.User.createdAt String The creation time of User.
SafeBreach.User.roles String The roles and permissions of User created.
SafeBreach.User.description String The description of User if any is given at creation time, it will be populated here.
SafeBreach.User.role String The role assigned to user during creation.
SafeBreach.User.deployments String The deployments user is part of.

safebreach-delete-api-key


This command deletes the API key with the name as specified in SafeBreach Management. It is not case sensitive.

Base Command

safebreach-delete-api-key

Input

Argument Name Description Required
key_name Name of the API Key to Delete. This will be used for searching key with given name and then once it matches, that API key will be deleted. Required

Context Output

Path Type Description
SafeBreach.API.name Number The Name of API Key deleted.
SafeBreach.API.description String Description of API Key deleted.
SafeBreach.API.created_by String The id of user who generated this API key.
SafeBreach.API.created_at String The creation time and date of API key.
SafeBreach.API.deleted_at String The deletion time and date of API key. The deletion date and time are generally close to the command execution time and date.

safebreach-delete-deployment


This command deletes a deployment with the deployment_id (retrieved using the get-all-deployments command).

Base Command

safebreach-delete-deployment

Input

Argument Name Description Required
deployment_id ID of the deployment to delete. The ID his can be searched with list-deployments command. Required

Context Output

Path Type Description
SafeBreach.Deployment.id Number The ID of deployment which has been deleted.
SafeBreach.Deployment.account_id String The account Id of user who deleted the deployment.
SafeBreach.Deployment.name String The name of deployment before the deployment was deleted.
SafeBreach.Deployment.created_at String The creation date and time of deployment which has been deleted.
SafeBreach.Deployment.description String The description of deployment before it was deleted.
SafeBreach.Deployment.simulators String The simulators that are part of deployment before it was deleted.

safebreach-clear-integration-issues


This command deletes connector-related errors and warnings for the specified connector_id (retrieved using the get-all-integration-issues command).

Base Command

safebreach-clear-integration-issues

Input

Argument Name Description Required
integration_id The ID of Integration to have its errors/warnings deleted. Both errors and warnings will be deleted. Required

Context Output

Path Type Description
SafeBreach.Integration.error Number Error count after deletion of errors for the given Integration.
SafeBreach.Integration.result String error deletion status whether true or false.

safebreach-delete-scheduled-scenarios


This command deletes the scheduled scenario with the specified schedule_id.

Base Command

safebreach-delete-scheduled-scenarios

Input

Argument Name Description Required
schedule_id schedule ID of scheduled scenario to delete. Required

Context Output

Path Type Description
SafeBreach.Scenario.id String the Id of the scheduled scenario.
SafeBreach.Scenario.name String the name of the scheduled scenario.
SafeBreach.Scenario.accountId String the account ID of the scheduled scenario.
SafeBreach.Scenario.description String the description of the scheduled scenario.
SafeBreach.Scenario.successCriteria String the success criteria of the scheduled scenario.
SafeBreach.Scenario.originalScenarioId String the original test ID of the scheduled scenario.
SafeBreach.Scenario.systemFilter String the systemFilter of the scheduled scenario.
SafeBreach.Scenario.tags String the tags of the scheduled scenario.
SafeBreach.Scenario.createdAt String the creation datetime of the scheduled scenario.
SafeBreach.Scenario.updatedAt String the updated datetime of the scheduled scenario.

safebreach-delete-simulator


The provided command facilitates the deletion of a simulator identified by its unique ID.To obtain the respective simulator ID, execute the “safebreach-get-all-simulators” command.

Base Command

safebreach-delete-simulator

Input

Argument Name Description Required
simulator_id Id of the simulator we want to delete. Required

Context Output

Path Type Description
SafeBreach.Simulator.is_enabled String Whether the simulator is enabled or not.
SafeBreach.Simulator.simulator_id String The Id of given simulator.
SafeBreach.Simulator.name String name for given simulator.
SafeBreach.Simulator.account_id String Account Id of account Hosting given simulator.
SafeBreach.Simulator.is_critical String Whether the simulator is critical.
SafeBreach.Simulator.is_exfiltration String If Simulator is exfiltration target.
SafeBreach.Simulator.is_infiltration String If simulator is infiltration target.
SafeBreach.Simulator.is_mail_target String If simulator is mail target.
SafeBreach.Simulator.is_mail_attacker String If simulator is mail attacker.
SafeBreach.Simulator.is_pre_executor String Whether the simulator is pre executor.
SafeBreach.Simulator.is_aws_attacker String if the given simulator is aws attacker.
SafeBreach.Simulator.is_azure_attacker String If the given simulator is azure attacker.
SafeBreach.Simulator.external_ip String external ip of given simulator.
SafeBreach.Simulator.internal_ip String internal ip of given simulator.
SafeBreach.Simulator.is_web_application_attacker String Whether the simulator is Web application attacker.
SafeBreach.Simulator.preferred_interface String Preferred simulator interface.
SafeBreach.Simulator.preferred_ip String Preferred Ip of simulator.
SafeBreach.Simulator.hostname String Hostname of given simulator.
SafeBreach.Simulator.connection_type String connection_type of given simulator.
SafeBreach.Simulator.simulator_status String status of the simulator.
SafeBreach.Simulator.connection_status String connection status of simulator.
SafeBreach.Simulator.simulator_framework_version String Framework version of simulator.
SafeBreach.Simulator.operating_system_type String operating system type of given simulator.
SafeBreach.Simulator.operating_system String Operating system of given simulator.
SafeBreach.Simulator.execution_hostname String Execution Hostname of the given simulator.
SafeBreach.Simulator.deployments String deployments simulator is part of.
SafeBreach.Simulator.created_at String Creation datetime of simulator.
SafeBreach.Simulator.updated_at String Update datetime of given simulator.
SafeBreach.Simulator.deleted_at String deletion datetime of given simulator.
SafeBreach.Simulator.assets String Assets of given simulator.
SafeBreach.Simulator.simulation_users String simulator users list.
SafeBreach.Simulator.proxies String Proxies of simulator.
SafeBreach.Simulator.advanced_actions String Advanced simulator details.

safebreach-delete-test-with-id


This command deletes tests with given test ID.

Base Command

safebreach-delete-test-with-id

Input

Argument Name Description Required
test_id Id of test to be deleted. Required

Context Output

Path Type Description
SafeBreach.Test.scenario_id String scenario ID of the test.
SafeBreach.Test.simulation_name String Name of the simulation.
SafeBreach.Test.security_action_per_control String Security Actions of the simulation.
SafeBreach.Test.test_id String Test id of the test.
SafeBreach.Test.status String status of the test.
SafeBreach.Test.planned_simulations_amount String Planned simulations count of the test.
SafeBreach.Test.simulator_executions String simulator executions of the test.
SafeBreach.Test.attack_executions String list of attacks that are part of the simulation.
SafeBreach.Test.ran_by String user who started the simulation.
SafeBreach.Test.simulator_count String simulators count per account.
SafeBreach.Test.end_time String End Time of the test.
SafeBreach.Test.start_time String start time of the test.
SafeBreach.Test.finalStatus.stopped String stopped count of attacks.
SafeBreach.Test.finalStatus.missed String missed count of attacks.
SafeBreach.Test.finalStatus.logged String logged count of attacks.
SafeBreach.Test.finalStatus.detected String detected count of attacks.
SafeBreach.Test.finalStatus.prevented String prevented count of attacks.

safebreach-delete-user


This command deletes a user with given data.

Base Command

safebreach-delete-user

Input

Argument Name Description Required
user_id ID of user to be deleted. The Id can be retrieved by using get-all-users command. Required

Context Output

Path Type Description
SafeBreach.User.id Number The ID of User whose data has been deleted.
SafeBreach.User.name String The name of User deleted.
SafeBreach.User.email String The email of User deleted.
SafeBreach.User.createdAt String the time at which the user who has been selected has been created
SafeBreach.User.updatedAt String last updated time.
SafeBreach.User.deletedAt String Deletion time of user.
SafeBreach.User.roles String The roles of User before they were deleted.
SafeBreach.User.description String The description of User who has been deleted.
SafeBreach.User.role String The roles and permissions of User who has been deleted.
SafeBreach.User.deployments String The deployments related to user before he was deleted.

safebreach-get-integration-issues


This command gives all integrations related issues and warning. this will show the integrations error and warnings which are generally displayed in installed integrations page.

Base Command

safebreach-get-integration-issues

Input

Argument Name Description Required
error_type this will help see issues which are either errors or warnings or both based on the input . Possible values are: , ERROR, WARNING. Optional

Context Output

Path Type Description
SafeBreach.Integration.integration_id Number The ID of Integration. A general notation that has been followed here is as follows, if the id has _default at the end then its a default connector else its a custom connector
SafeBreach.Integration.integration_name String Name of the integration
SafeBreach.Integration.action String The action of Integration error. This describes where exactly did the error occur, if its search,then it implies error/warning happened when connector was trying that process
SafeBreach.Integration.success_state String status of integration error. This implies whether the connector was able to successfully perform the operation or if it failed partway. So false implies it failed partway and true implies it was successfully completed
SafeBreach.Integration.error_description String This is the exact error description shown on safebreach integration error/warning page. This description can be used for understanding of what exactly happened for the integration to fail.
SafeBreach.Integration.timestamp String Time at which error/warning occurred. This can be used to pinpoint error which occurred across integrations if time of origin was remembered

safebreach-get-running-simulations


This command gets simulations which are in running or queued state.

Base Command

safebreach-get-running-simulations

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Test.test id String this is test ID of the simulation.
SafeBreach.Test.simulation_id String the simulation id of the simulation.
SafeBreach.Test.attack_id String the attack ID of the simulation.

safebreach-get-running-tests


This command gets tests which are in running state.

Base Command

safebreach-get-running-tests

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Test.id Number Id of Actively running test.
SafeBreach.Test.name String Name of the test being run.
SafeBreach.Test.description String Details related to the test being run.
SafeBreach.Test.success_criteria String success criterion for the test.
SafeBreach.Test.original_scenario_id String Original scenario ID of the running test
SafeBreach.Test.actions_count String number of actions
SafeBreach.Test.edges_count String number of edges.
SafeBreach.Test.created_at String details related to when test is created.
SafeBreach.Test.updated_at String details related to when test is last updated/changed
SafeBreach.Test.steps_count String number of steps in simulator.
SafeBreach.Test.scenario_id String scenario_id of the test.
SafeBreach.Test.original_scenario_id String scenario_id for reference.
SafeBreach.Test.ran_by String User who ran the scenario.
SafeBreach.Test.ran_from String Where the test ran from.
SafeBreach.Test.test_id String test id of the test.
SafeBreach.Test.priority String priority of tests.
SafeBreach.Test.retry_simulations String Should simulations be retried
SafeBreach.Test.pause_duration String is the test paused and if so till when
SafeBreach.Test.paused_date String when the test is paused
SafeBreach.Test.expected_simulations_amount String number of simulations expected
SafeBreach.Test.dispatched_simulations_amount String the number of simulations dispatched
SafeBreach.Test.skipped_simulations_amount String The number of simulations skipped
SafeBreach.Test.failed_simulations_amount String The number of simulations failed

safebreach-get-available-simulator-details


This command to get all available simulators. if details is set to true then it retrieves simulator details like name, hostname, internal and external ips, types of targets and attacker configurations this simulator is associated with etc. if its set to false then it retrieves just name, id, simulation users, proxies etc. if deleted is set to true then it retrieves the data which has been deleted.

Base Command

safebreach-get-available-simulator-details

Input

Argument Name Description Required
hostname if hostname to be included for search. Optional
external_ip if external IP details to be included for search. Optional
internal_ip if Internal IP are to be included for search. Optional
os operating system name to filter with, Eg: LINUX,WINDOWS etc, incase nothing is selected then this will be set as empty which means all are retrieved. Possible values are: , LINUX, MAC, WINDOWS. Optional
is_enabled if to search only enabled ones. Possible values are: true, false. Optional
is_connected status of connection of simulators to search. Possible values are: true, false. Optional
is_critical whether to search only for critical simulators or not. Possible values are: true, false. Optional
additional_details Whether to show additional details or not. Possible values are: true, false. Optional
status if simulator status are to be included for search. Possible values are: APPROVED, PENDING, ALL. Default is ALL. Optional

Context Output

Path Type Description
SafeBreach.Simulator.is_enabled String Whether the simulator is enabled or not.
SafeBreach.Simulator.simulator_id String The Id of given simulator.
SafeBreach.Simulator.name String name for given simulator.
SafeBreach.Simulator.account_id String Account Id of account Hosting given simulator.
SafeBreach.Simulator.is_critical String Whether the simulator is critical.
SafeBreach.Simulator.is_exfiltration String If Simulator is exfiltration target.
SafeBreach.Simulator.is_infiltration String If simulator is infiltration target.
SafeBreach.Simulator.is_mail_target String If simulator is mail target.
SafeBreach.Simulator.is_mail_attacker String If simulator is mail attacker.
SafeBreach.Simulator.is_pre_executor String Whether the simulator is pre executor.
SafeBreach.Simulator.is_aws_attacker String if the given simulator is aws attacker.
SafeBreach.Simulator.is_azure_attacker String If the given simulator is azure attacker.
SafeBreach.Simulator.external_ip String external ip of given simulator.
SafeBreach.Simulator.internal_ip String internal ip of given simulator.
SafeBreach.Simulator.is_web_application_attacker String Whether the simulator is Web application attacker.
SafeBreach.Simulator.preferred_interface String Preferred simulator interface.
SafeBreach.Simulator.preferred_ip String Preferred Ip of simulator.
SafeBreach.Simulator.hostname String Hostname of given simulator.
SafeBreach.Simulator.connection_type String connection_type of given simulator.
SafeBreach.Simulator.simulator_status String status of the simulator.
SafeBreach.Simulator.connection_status String connection status of simulator.
SafeBreach.Simulator.simulator_framework_version String Framework version of simulator.
SafeBreach.Simulator.operating_system_type String operating system type of given simulator.
SafeBreach.Simulator.operating_system String Operating system of given simulator.
SafeBreach.Simulator.execution_hostname String Execution Hostname of the given simulator.
SafeBreach.Simulator.deployments String deployments simulator is part of.
SafeBreach.Simulator.created_at String Creation datetime of simulator.
SafeBreach.Simulator.updated_at String Update datetime of given simulator.
SafeBreach.Simulator.deleted_at String deletion datetime of given simulator.
SafeBreach.Simulator.assets String Assets of given simulator.
SafeBreach.Simulator.simulation_users String simulator users list.
SafeBreach.Simulator.proxies String Proxies of simulator.
SafeBreach.Simulator.advanced_actions String Advanced simulator details.

safebreach-get-tests


This command gets tests with given modifiers.

Base Command

safebreach-get-tests

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Test.scenario_id String scenario ID of the test.
SafeBreach.Test.simulation_name String Name of the simulation.
SafeBreach.Test.security_action_per_control String Security Actions of the simulation.
SafeBreach.Test.test_id String Test id of the test.
SafeBreach.Test.status String status of the test.
SafeBreach.Test.planned_simulations_amount String Planned simulations count of the test.
SafeBreach.Test.simulator_executions String simulator executions of the test.
SafeBreach.Test.attack_executions String list of attacks that are part of the simulation.
SafeBreach.Test.ran_by String user who started the simulation.
SafeBreach.Test.simulator_count String simulators count per account.
SafeBreach.Test.end_time String End Time of the test.
SafeBreach.Test.start_time String start time of the test.
SafeBreach.Test.finalStatus.stopped String stopped count of attacks.
SafeBreach.Test.finalStatus.missed String missed count of attacks.
SafeBreach.Test.finalStatus.logged String logged count of attacks.
SafeBreach.Test.finalStatus.detected String detected count of attacks.
SafeBreach.Test.finalStatus.prevented String prevented count of attacks.

safebreach-get-tests-with-scenario-id


This command gets tests with given scenario ID as part of it.

Base Command

safebreach-get-tests-with-scenario-id

Input

Argument Name Description Required
scenario_id Scenario Id for test which has to be filtered. this can be found on UI, if unsure about this then please run safebreach-get-tests instead of this with same parameters as inputs. Required

Context Output

Path Type Description
SafeBreach.Test.scenario_id String scenario ID of the test.
SafeBreach.Test.simulation_name String Name of the simulation.
SafeBreach.Test.security_action_per_control String Security Actions of the simulation.
SafeBreach.Test.test_id String Test id of the test.
SafeBreach.Test.status String status of the test.
SafeBreach.Test.planned_simulations_amount String Planned simulations count of the test.
SafeBreach.Test.simulator_executions String simulator executions of the test.
SafeBreach.Test.attack_executions String list of attacks that are part of the simulation.
SafeBreach.Test.ran_by String user who started the simulation.
SafeBreach.Test.simulator_count String simulators count per account.
SafeBreach.Test.end_time String End Time of the test.
SafeBreach.Test.start_time String start time of the test.
SafeBreach.Test.finalStatus.stopped String stopped count of attacks.
SafeBreach.Test.finalStatus.missed String missed count of attacks.
SafeBreach.Test.finalStatus.logged String logged count of attacks.
SafeBreach.Test.finalStatus.detected String detected count of attacks.
SafeBreach.Test.finalStatus.prevented String prevented count of attacks.

safebreach-get-all-users


This command gives all users who are not deleted.

Base Command

safebreach-get-all-users

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.User.id Number The ID of User retrieved. this can be used to further link this user with user_id field of safebreach-update-user or safebreach-delete-user commands
SafeBreach.User.name String The name of User retrieved.
SafeBreach.User.email String The email of User retrieved. this can be used for updating user or deleting user for input email of commands safebreach-update-user or safebreach-delete-user

safebreach-get-custom-scenarios


This command retrieves scenarios which are saved by user as custom scenarios. they generally have configurations and everything set up and will be ready to run as tests

Base Command

safebreach-get-custom-scenarios

Input

Argument Name Description Required
schedule_details Details of custom scenarios (My scenarios). Possible values are: false, true. Default is true. Possible values are: false, true. Default is true. Optional

Context Output

Path Type Description
SafeBreach.Scenario.id String the Id of scenario.
SafeBreach.Scenario.name String the name of the scenario.
SafeBreach.Scenario.description String the description of the scenario.
SafeBreach.Scenario.success_criteria String success criteria the scenario.
SafeBreach.Scenario.original_scenario_id String original scenario id of scenario.
SafeBreach.Scenario.actions_list String actions list of the scenario.
SafeBreach.Scenario.edges_count String edges_count for the scenario.
SafeBreach.Scenario.steps_order String the order of steps of the scenario.
SafeBreach.Scenario.created_at String the creation datetime of the scenario.
SafeBreach.Scenario.updated_at String the last updated time the scenario.

safebreach-list-deployments


This command gets all deployments present for this instance.

Base Command

safebreach-list-deployments

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Deployment.id Number The ID of deployment
SafeBreach.Deployment.account_id String The accountId of user who created the deployment.
SafeBreach.Deployment.name String The name of deployment. this will be the name shown in deployment name field of table in deployments page in safebreach UI
SafeBreach.Deployment.created_at String The creation date and time of deployment.
SafeBreach.Deployment.updated_at String The last updated date and time of deployment.
SafeBreach.Deployment.description String This is description field of deployments table of safebreach UI
SafeBreach.Deployment.simulators String The simulators that are part of deployment.

safebreach-get-indicators


This command fetches SafeBreach Insights from which indicators are extracted, creating new indicators or updating existing indicators.

Base Command

safebreach-get-indicators

Input

Argument Name Description Required
test_id Test ID of the insight. Required
limit The maximum number of indicators to generate. The default is 1000. Optional
insightCategory Multi-select option for the category of the insights to get remediation data for:Network Access, Network Inspection, Endpoint, Email, Web, Data Leak. Optional
insightDataType Multi-select option for the remediation data type to get: Hash, Domain, URI, Command, Port, Protocol, Registry. Optional
behavioralReputation Select option for the category of behavioral reputation. Optional
nonBehavioralReputation Select option for the category of non-behavioral reputation. Optional

Context Output

Path Type Description
SafeBreach.Indicator.value String The value of the indicator
SafeBreach.Indicator.type String The type of the indicator
SafeBreach.Indicator.rawJSON.dataType String The data type of the indicator
SafeBreach.Indicator.rawJSON.insightTime String The time of the insight
SafeBreach.Indicator.rawJSON.value String The data type value of the indicator
SafeBreach.Indicator.fields.description String The description of the indicator
SafeBreach.Indicator.fields.safebreachseverity String The severity of the indicator
SafeBreach.Indicator.fields.safebreachseverityscore String The severity score of the indicator
SafeBreach.Indicator.fields.safebreachisbehavioral Boolean The behavioral of the indicator
SafeBreach.Indicator.fields.safebreachattackids Unknown The attack ids of the indicator
SafeBreach.Indicator.fields.port String The port of the indicator
SafeBreach.Indicator.fields.tags String The tags of the indicator
SafeBreach.Indicator.score Number The score of the indicator

safebreach-get-simulator-download-links


This command gets a list of links for download (item per operating system) for the latest available version.

Base Command

safebreach-get-simulator-download-links

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Installation.md5 String The MD5 generated from the contents of the file
SafeBreach.Installation.os String The operating system for which the update is intended
SafeBreach.Installation.sha1 String The sha1 generated from the contents of the file.
SafeBreach.Installation.sha256 String The sha256 generated from the contents of the file.
SafeBreach.Installation.sha512 String The sha512 generated from the contents of the file.
SafeBreach.Installation.sha512 String The sha512 generated from the contents of the file.
SafeBreach.Installation.url String The URL from which update can be downloaded.
SafeBreach.Installation.version String This indicates the simulator version.

safebreach-get-prebuilt-scenarios


This command gets scenarios which are built by safebreach. They will be available by default even in new instance of your safebreach instance. They can be modified and saved as custom scenarios or used as it is.

Base Command

safebreach-get-prebuilt-scenarios

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Scenario.id String the Id of scenario.
SafeBreach.Scenario.name String he name of the scenario.
SafeBreach.Scenario.description String the description of the scenario.
SafeBreach.Scenario.created_by String user id of user, who created the scenario.
SafeBreach.Scenario.created_at String creation datetime of scenario.
SafeBreach.Scenario.updated_at String the update datetime of the scenario.
SafeBreach.Scenario.recommended String the recommendation status of the scenario.
SafeBreach.Scenario.tags_list String the tags related to the scenario.
SafeBreach.Scenario.categories String the category ids of the scenario.
SafeBreach.Scenario.steps_order String the order of steps involved in the scenario.
SafeBreach.Scenario.order String the order of execution related to the scenario.
SafeBreach.Scenario.min_api_ver String the minimum version of API required for scenario to be executed

safebreach-get-scheduled-scenarios


This command retrieves schedules from safebreach which user has set and they will display it to user. By default Name is not shown, to retrieve and see it, please run ‘safebreach-get-custom-scenarios’ command to find name of scenario to which the schedule is associated with.

Base Command

safebreach-get-scheduled-scenarios

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Schedules.id String the Id of the schedule.
SafeBreach.Schedules.is_enabled Boolean if simulation is enabled.
SafeBreach.Schedules.user_schedule String the user readable form of the schedule.
SafeBreach.Schedules.run_date String the run date of the schedule.
SafeBreach.Schedules.cron_timezone String the time zone of the schedule.
SafeBreach.Schedules.description String the description of the schedule.
SafeBreach.Schedules.scenario_id String the matrix ID of the schedule.
SafeBreach.Schedules.created_at String the creation datetime of the schedule.
SafeBreach.Schedules.updated_at String the updated datetime of the schedule.
SafeBreach.Schedules.deleted_at String the deletion time of the schedule.

safebreach-get-services-status


This command facilitates the retrieval of service statuses from SafeBreach,presenting them to the user in a tabular format. In the event that services are inactive,pertinent details regarding their downtime or last operational status are also displayed.

Base Command

safebreach-get-services-status

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Service.name String the name of the service.
SafeBreach.Service.version String version of the service.
SafeBreach.Service.connection status String connection status of service.
SafeBreach.Service.error String error status of service.

safebreach-get-simulations


This command facilitates the retrieval of simulations and their associated data for a specified test. It can be used as a precursor command for the rerun-simulations command, streamlining the process of queuing simulations. It’s important to note that this command currently lacks pagination limiters, potentially resulting in the retrieval of a large volume of data.

Base Command

safebreach-get-simulations

Input

Argument Name Description Required
test_id This is ID of the test whose simulations will be retrieved. Optional

Context Output

Path Type Description
SafeBreach.Simulation.simulation_id String the id of the simulation.
SafeBreach.Simulation.attacker_node_name String Name of attacker node of simulation.
SafeBreach.Simulation.target_node_name String name of target of simulation.
SafeBreach.Simulation.dest_node_name String name of destination of simulation.
SafeBreach.Simulation.attack_name String name of attack
SafeBreach.Simulation.attacks_involved String attack types involved in of simulation.
SafeBreach.Simulation.result_details String result of simulation.
SafeBreach.Simulation.security_action String security status as per the simulation.
SafeBreach.Simulation.attack_description String attack details.

safebreach-get-available-simulator-count


This command gives all details related to account, we are using this to find assigned simulator quota.

Base Command

safebreach-get-available-simulator-count

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Account.id Number The account ID which is being used by integration.
SafeBreach.Account.name String The Account Name of account being queried.
SafeBreach.Account.contact_name String Contact name for given account.
SafeBreach.Account.contact_email String Email of the contact person.
SafeBreach.Account.user_quota String User Quota for the given account, maximum users which are allowed for the account.
SafeBreach.Account.simulators_quota Number The simulator quota for the given account. The maximum number of simulators which are available for the account.
SafeBreach.Account.registration_date Number The registration date of given account.
SafeBreach.Account.activation_date String The Activation date of given account.
SafeBreach.Account.expiration_date String Account expiration date.

safebreach-get-simulator-with-id


This command gives simulator with given id

Base Command

safebreach-get-simulator-with-id

Input

Argument Name Description Required
simulator_id simulator id. Required

Context Output

Path Type Description
SafeBreach.Simulator.is_enabled String Whether the simulator is enabled or not.
SafeBreach.Simulator.simulator_id String The Id of given simulator.
SafeBreach.Simulator.name String name for given simulator.
SafeBreach.Simulator.account_id String Account Id of account Hosting given simulator.
SafeBreach.Simulator.is_critical String Whether the simulator is critical.
SafeBreach.Simulator.is_exfiltration String If Simulator is exfiltration target.
SafeBreach.Simulator.is_infiltration String If simulator is infiltration target.
SafeBreach.Simulator.is_mail_target String If simulator is mail target.
SafeBreach.Simulator.is_mail_attacker String If simulator is mail attacker.
SafeBreach.Simulator.is_pre_executor String Whether the simulator is pre executor.
SafeBreach.Simulator.is_aws_attacker String if the given simulator is aws attacker.
SafeBreach.Simulator.is_azure_attacker String If the given simulator is azure attacker.
SafeBreach.Simulator.external_ip String external ip of given simulator.
SafeBreach.Simulator.internal_ip String internal ip of given simulator.
SafeBreach.Simulator.is_web_application_attacker String Whether the simulator is Web application attacker.
SafeBreach.Simulator.preferred_interface String Preferred simulator interface.
SafeBreach.Simulator.preferred_ip String Preferred Ip of simulator.
SafeBreach.Simulator.hostname String Hostname of given simulator.
SafeBreach.Simulator.connection_type String connection_type of given simulator.
SafeBreach.Simulator.simulator_status String status of the simulator.
SafeBreach.Simulator.connection_status String connection status of simulator.
SafeBreach.Simulator.simulator_framework_version String Framework version of simulator.
SafeBreach.Simulator.operating_system_type String operating system type of given simulator.
SafeBreach.Simulator.operating_system String Operating system of given simulator.
SafeBreach.Simulator.execution_hostname String Execution Hostname of the given simulator.
SafeBreach.Simulator.deployments String deployments simulator is part of.
SafeBreach.Simulator.created_at String Creation datetime of simulator.
SafeBreach.Simulator.updated_at String Update datetime of given simulator.
SafeBreach.Simulator.deleted_at String deletion datetime of given simulator.
SafeBreach.Simulator.assets String Assets of given simulator.
SafeBreach.Simulator.simulation_users String simulator users list.
SafeBreach.Simulator.proxies String Proxies of simulator.
SafeBreach.Simulator.advanced_actions String Advanced simulator details.

safebreach-get-simulators-versions-list


This command fetches the list of SafeBreach simulators

Base Command

safebreach-get-simulators-versions-list

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Simulator.id String Simulator Id
SafeBreach.Simulator.lastUpdateDate String Simulator last updated data
SafeBreach.Simulator.lastUpdateStatus String Simulator last updated status
SafeBreach.Simulator.currentStatus String Simulator current status
SafeBreach.Simulator.availableVersions Unknown Simulator available versions

safebreach-get-user-with-matching-name-or-email


The command retrieves users based on the provided inputs. If an email is provided, it returns the user associated with that email, as email is a unique identifierIf a name is provided, exact name matching is required to ensure accurate retrieval of a single user;otherwise, multiple users may be returned. It’s essential to note that either a name or an email must be populated as input;failure to provide either results in an error.

Base Command

safebreach-get-user-with-matching-name-or-email

Input

Argument Name Description Required
name Name of the user. Partial match is supported. Optional
email Email of the user. Exact match required. Optional

Context Output

Path Type Description
SafeBreach.User.id Number The ID of User retrieved. this can be used to further link this user with user_id field of safebreach-update-user or safebreach-delete-user commands
SafeBreach.User.name String The name of User retrieved.
SafeBreach.User.email String The email of User retrieved. this can be used for updating user or deleting user for input email of commands safebreach-update-user or safebreach-delete-user

safebreach-get-verification-token


This command retrieves existing verification token needed for verification of the simulators.

Base Command

safebreach-get-verification-token

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Token.token String the value of new verification token.

safebreach-pause/resume-simulations-tests


This command gets simulations/tests which are in running or queued state and pauses/resumes them based on input selected. The state selected will be applied for all running/queued state tasks whether they are simulations/tests.

Base Command

safebreach-pause/resume-simulations-tests

Input

Argument Name Description Required
simulation_or_test_state State of tests/simulators to set to:
1. pause will set all simulations/tests which are in queue/running to paused stated and resume all will be the state of button in running simulations page.
2. resume will queue all simulations/tests and will set them to running/queued depending on priority.
Note that this doe not affect the schedules and scheduled tasks unless they are running or active at the moment of execution of the command. Possible values are: resume, pause.
Required

Context Output

Path Type Description
SafeBreach.Test.status String the status of the simulations/tests.

safebreach-rerun-simulation


this commands puts given simulation ids into queue for running.

Base Command

safebreach-rerun-simulation

Input

Argument Name Description Required
simulation_ids ids of simulation we want to queue, please give ids of simulations as comma separated numbers. Required
test_name test name for the given test. Required

Context Output

Path Type Description
SafeBreach.Simulation.id String the Id of simulation.
SafeBreach.Simulation.name String the name of the simulation.
SafeBreach.Simulation.description String the description of the simulation.
SafeBreach.Simulation.success_criteria String success criteria the simulation.
SafeBreach.Simulation.original_scenario_id String original simulation id of simulation.
SafeBreach.Simulation.actions_list String actions list of the simulation.
SafeBreach.Simulation.steps_order String the order of steps of the simulation.
SafeBreach.Simulation.createdAt String the creation datetime of the simulation.
SafeBreach.Simulation.updatedAt String the last updated time the simulation.

safebreach-rerun-test


This command puts given test data in queue for execution.

Base Command

safebreach-rerun-test

Input

Argument Name Description Required
test_id test id for the given test, this is be test id field from get-all-tests-summary command. Required
test_name test name for the given test. Required

Context Output

Path Type Description
SafeBreach.Test.id String the Id of test.
SafeBreach.Test.name String the name of the test.
SafeBreach.Test.description String the description of the test.
SafeBreach.Test.success_criteria String success criteria the test.
SafeBreach.Test.original_scenario_id String original scenario id of test.
SafeBreach.Test.actions_list String actions list of the test.
SafeBreach.Test.edges_count String edges_count for the test.
SafeBreach.Test.steps_order String the order of steps of the test.
SafeBreach.Test.created_at String the creation datetime of the test.
SafeBreach.Test.updated_at String the last updated time the test.
SafeBreach.Test.scenario_id String the test id of the test.
SafeBreach.Test.ran_by String the user id of the user who ran the test.
SafeBreach.Test.ran_from String where the user ran the test from.
SafeBreach.Test.enable_feedback_loop String feedback loop status of the test.
SafeBreach.Test.test_id String test_id of the test.
SafeBreach.Test.priority String priority of the test.
SafeBreach.Test.retry_simulations String retry status of the test.

safebreach-rotate-verification-token


This command rotates generated verification token meaning it creates a new token which will be used for verification of simulator and adding the simulator.

Base Command

safebreach-rotate-verification-token

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
SafeBreach.Token.new_token String New token which has been generated due to the API call

safebreach-update-deployment


This command updates a deployment with given data. The deployment_id field of this command can be retrieved from ‘safebreach-list-deployments’ command. If the user wants to search with deployment ID then they can search it

Base Command

safebreach-update-deployment

Input

Argument Name Description Required
deployment_id ID of the deployment to update. Can be searched with list-deployments command. Required
updated_simulators_for_deployment Comma separated ID of all simulators to be part of the deployment Simulators can be retrieved by calling get-all-available-simulator-details command. Optional
updated_deployment_name Deployment name. Optional
updated_deployment_description Deployment description. Optional

Context Output

Path Type Description
SafeBreach.Deployment.id Number The ID of deployment whose values have been updated. ID cant be changed so this wont be updated.
SafeBreach.Deployment.account_id String The accountId of user who created the deployment.
SafeBreach.Deployment.name String The name of deployment which has been updated to the name given in updated_deployment_name. this will be the name shown in deployment name field of table in deployments page in safebreach UI
SafeBreach.Deployment.created_at String The creation date and time of deployment whose data has been updated.
SafeBreach.Deployment.updated_at String The last updated date and time of deployment whose data has been updated. This will generally be closer to the update deployment command run time for reference
SafeBreach.Deployment.description String The updated description of deployment which is provided in updated_deployment_description field of input . This will now be the description which is shown in description field of deployments table of safebreach UI
SafeBreach.Deployment.simulators String The simulators that are part of deployment. unless any simulators are given as input this field won’t be updated this field doesn’t reflect changes if simulators given as input are deleted

safebreach-update-simulator


This command updates simulator with given id. the given inputs for update fields will be updated to the selected filed values will be updated to given value.

Base Command

safebreach-update-simulator

Input

Argument Name Description Required
simulator_id Simulator ID. Required
connection_url The given value will be set as connection string, meaning this can be used to connect to this URL. Optional
cloud_proxy_url the given value will be set as cloud proxy url. Optional
name The given value will be set as name of simulator. This will be the name of simulator once the command runs. Optional
preferred_interface the given value will be set as preferred interface. Optional
preferred_ip the given value will be set as Preferred IP to connect to the simulator. Optional
tunnel the given value will be set as tunnel. Optional

Context Output

Path Type Description
SafeBreach.Simulator.is_enabled String Whether the simulator is enabled or not.
SafeBreach.Simulator.simulator_id String The Id of given simulator.
SafeBreach.Simulator.name String name for given simulator.
SafeBreach.Simulator.account_id String Account Id of account Hosting given simulator.
SafeBreach.Simulator.is_critical String Whether the simulator is critical.
SafeBreach.Simulator.is_exfiltration String If Simulator is exfiltration target.
SafeBreach.Simulator.is_infiltration String If simulator is infiltration target.
SafeBreach.Simulator.is_mail_target String If simulator is mail target.
SafeBreach.Simulator.is_mail_attacker String If simulator is mail attacker.
SafeBreach.Simulator.is_pre_executor String Whether the simulator is pre executor.
SafeBreach.Simulator.is_aws_attacker String if the given simulator is aws attacker.
SafeBreach.Simulator.is_azure_attacker String If the given simulator is azure attacker.
SafeBreach.Simulator.external_ip String external ip of given simulator.
SafeBreach.Simulator.internal_ip String internal ip of given simulator.
SafeBreach.Simulator.is_web_application_attacker String Whether the simulator is Web application attacker.
SafeBreach.Simulator.preferred_interface String Preferred simulator interface.
SafeBreach.Simulator.preferred_ip String Preferred Ip of simulator.
SafeBreach.Simulator.hostname String Hostname of given simulator.
SafeBreach.Simulator.connection_type String connection_type of given simulator.
SafeBreach.Simulator.simulator_status String status of the simulator.
SafeBreach.Simulator.connection_status String connection status of simulator.
SafeBreach.Simulator.simulator_framework_version String Framework version of simulator.
SafeBreach.Simulator.operating_system_type String operating system type of given simulator.
SafeBreach.Simulator.operating_system String Operating system of given simulator.
SafeBreach.Simulator.execution_hostname String Execution Hostname of the given simulator.
SafeBreach.Simulator.deployments String deployments simulator is part of.
SafeBreach.Simulator.created_at String Creation datetime of simulator.
SafeBreach.Simulator.updated_at String Update datetime of given simulator.
SafeBreach.Simulator.deleted_at String deletion datetime of given simulator.
SafeBreach.Simulator.assets String Assets of given simulator.
SafeBreach.Simulator.simulation_users String simulator users list.
SafeBreach.Simulator.proxies String Proxies of simulator.
SafeBreach.Simulator.advanced_actions String Advanced simulator details.

safebreach-upgrade-simulator


This command updates the simulator using the Simulator ID and available version.

Base Command

safebreach-upgrade-simulator

Input

Argument Name Description Required
simulator_id Simulator ID. Required
simulator_version The version should be in the format of the safebreach-get-simulators-versions-list command and that ‘latest’ can be used. The default is the latest. Required

Context Output

Path Type Description
SafeBreach.Simulator.nodeId String Simulator ID
SafeBreach.Simulator.status String Simulator status

safebreach-update-user


This command updates a user with given data.

Base Command

safebreach-update-user

Input

Argument Name Description Required
user_id user ID of user from safebreach to search. Required
name Update the user name to given value of this field. Optional
user_description Update the user Description to given value in this field. Optional
is_active Update the user Status based on the input, if this is set to false then user will be deactivated. unless this field is left empty, whatever is present here will be updated to user details. user will be selected based on user_id field mentioned above. Possible values are: true, false, . Optional
password Password of user to be updated with. this will be used for changing password for user. unless this field is left empty, whatever is present here will be updated to user details. user will be selected based on user_id field mentioned above. Optional
user_role Role of the user to be changed to. unless you want to change the user role and permissions, dont select anything in this field, user will be selected based on user_id field mentioned above. Possible values are: viewer, administrator, contentDeveloper, operator. Optional
deployments Comma separated ID of all deployments the user should be part of. unless this field is left empty, whatever is present here will be updated to user details.incase there are old deployments assigned to user then please include them too, else they will be replaced with new values.User will be selected based on user_id field mentioned above. Optional

Context Output

Path Type Description
SafeBreach.User.id Number The ID of User whose data has been updated.
SafeBreach.User.name String The name of User after running the update command according to safebreach records.
SafeBreach.User.email String the email of the user whose data has been updated by the command.
SafeBreach.User.createdAt String the time at which the user who has been selected has been created
SafeBreach.User.updatedAt String The last updated time of User selected for update. this will be the execution time for the command or close to it.
SafeBreach.User.deletedAt String The Deletion time of User selected to update. Generally this is empty unless user chosen to update is a deleted user
SafeBreach.User.roles String The roles of User updated. these will change if role has been updated during updating user details else they will be same as pre update.
SafeBreach.User.description String The description of User after updating user, if description field has been given any new value during update then its updated else this will be left unchanged from previous value.
SafeBreach.User.role String The roles and permissions related to user who has been selected for update.unless this field has been given a value , this will not be updated and will stay the same as previous value.
SafeBreach.User.deployments String The deployments related to user, this will be comma separated values of deployment IDs

Configuration parameters

  • base_url — Server URL (required)
  • credentials — (required)
  • account_id — Account ID (required)
  • verify — Verify SSL Certificate
  • proxy — Use system proxy settings

Commands (39)

  • safebreach-approve-simulator

    This command approves the simulator with the specified simulator_id.

  • safebreach-clear-integration-issues

    This command deletes connector-related errors and warnings for the specified connector_id (retrieved using the get-all-integration-issues command).

  • safebreach-create-deployment

    This command creates a deployment, grouping the list of simulators provided with a name and optionally a description.

  • safebreach-create-user

    This command creates a user, including credentials and permissions.

  • safebreach-delete-api-key

    This command deletes the API key with the name as specified in SafeBreach Management. It is not case sensitive.

  • safebreach-delete-deployment

    This command deletes a deployment with the deployment_id (retrieved using the get-all-deployments command).

  • safebreach-delete-scheduled-scenarios

    This command deletes the scheduled scenario with the specified schedule_id.

  • safebreach-delete-simulator

    The provided command facilitates the deletion of a simulator identified by its unique ID.To obtain the respective simulator ID, execute the "safebreach-get-all-simulators" command.

  • safebreach-delete-test-with-id

    This command deletes tests with given test ID.

  • safebreach-delete-user

    This command deletes a user with given data.

  • safebreach-generate-api-key

    This command creates an API key with the name and optionally the description provided. The API key created will be shown on the Settings > API Keys page of SafeBreach Management. Important: The API key generated can be seen only once, so it is recommended to store/save it in a safe place for further use.

  • safebreach-get-all-users

    This command gives all users who are not deleted.

  • safebreach-get-available-simulator-count

    This command gives all details related to account, we are using this to find assigned simulator quota.

  • safebreach-get-available-simulator-details

    This command to get all available simulators. if details is set to true then it retrieves simulator details like name, hostname, internal and external ips, types of targets and attacker configurations this simulator is associated with etc. if its set to false then it retrieves just name, id, simulation users, proxies etc. if deleted is set to true then it retrieves the data which has been deleted.

  • safebreach-get-custom-scenarios

    This command retrieves scenarios which are saved by user as custom scenarios. they generally have configurations and everything set up and will be ready to run as tests.

  • safebreach-get-indicators

    This command fetches SafeBreach Insights from which indicators are extracted, creating new indicators or updating existing indicators.

  • safebreach-get-integration-issues

    This command gives all integrations related issues and warning. this will show the integrations error and warnings which are generally displayed in installed integrations page.

  • safebreach-get-prebuilt-scenarios

    This command gets scenarios which are built by safebreach. They will be available by default even in new instance of your safebreach instance. They can be modified and saved as custom scenarios or used as it is.

  • safebreach-get-running-simulations

    This command gets simulations which are in running or queued state.

  • safebreach-get-running-tests

    This command gets tests which are in running state.

  • safebreach-get-scheduled-scenarios

    This command retrieves schedules from safebreach which user has set and they will display it to user. By default Name is not shown, to retrieve and see it, please run 'safebreach-get-custom-scenarios' command to find name of scenario to which the schedule is associated with.

  • safebreach-get-services-status

    This command facilitates the retrieval of service statuses from SafeBreach,presenting them to the user in a tabular format. In the event that services are inactive,pertinent details regarding their downtime or last operational status are also displayed.

  • safebreach-get-simulations

    This command facilitates the retrieval of simulations and their associated data for a specified test. It can be used as a precursor command for the rerun-simulations command, streamlining the process of queuing simulations. It's important to note that this command currently lacks pagination limiters, potentially resulting in the retrieval of a large volume of data.

  • safebreach-get-simulator-download-links

    This command gets a list of links for download (item per operating system) for the latest available version.

  • safebreach-get-simulator-with-id

    This command gives simulator with given id.

  • safebreach-get-simulators-versions-list

    This command fetches the list of SafeBreach simulators.

  • safebreach-get-tests

    This command gets tests with given modifiers.

  • safebreach-get-tests-with-scenario-id

    This command gets tests with given scenario ID as part of it.

  • safebreach-get-user-with-matching-name-or-email

    The command retrieves users based on the provided inputs. If an email is provided, it returns the user associated with that email, as email is a unique identifierIf a name is provided, exact name matching is required to ensure accurate retrieval of a single user;otherwise, multiple users may be returned. It's essential to note that either a name or an email must be populated as input;failure to provide either results in an error.

  • safebreach-get-verification-token

    This command retrieves existing verification token needed for verification of the simulators.

  • safebreach-list-deployments

    This command gets all deployments present for this instance.

  • safebreach-pause/resume-simulations-tests

    This command gets simulations/tests which are in running or queued state and pauses/resumes them based on input selected. The state selected will be applied for all running/queued state tasks whether they are simulations/tests.

  • safebreach-rerun-simulation

    this commands puts given simulation ids into queue for running.

  • safebreach-rerun-test

    This command puts given test data in queue for execution.

  • safebreach-rotate-verification-token

    This command rotates generated verification token meaning it creates a new token which will be used for verification of simulator and adding the simulator.

  • safebreach-update-deployment

    This command updates a deployment with given data. The deployment_id field of this command can be retrieved from 'safebreach-list-deployments' command. If the user wants to search with deployment ID then they can search it.

  • safebreach-update-simulator

    This command updates simulator with given id. the given inputs for update fields will be updated to the selected filed values will be updated to given value.

  • safebreach-update-user

    This command updates a user with given data.

  • safebreach-upgrade-simulator

    This command updates the simulator using the Simulator ID and available version.

import json
import re
from ast import literal_eval
from collections.abc import Iterable
from copy import deepcopy
from typing import Any

import demistomock as demisto
from CommonServerPython import *

DATE_FORMAT = "%Y-%m-%d %H:%M:%S UTC"  # ISO8601 format with UTC, default in XSOAR

CATEGORY_MAPPER = {
    "Network Access": [1, 2, 3, 4, 19, 20, 21, 22],
    "Network Inspection": [7, 10, 11, 12, 18],
    "Endpoint": [8, 9, 13, 14, 17],
    "Email": [15, 24],
    "Web": [5, 6],
    "Data Leak": [16],
}

INSIGHT_DATA_TYPE_MAPPER = {
    1: "Port",
    2: "Protocol",
    3: "Port",
    4: "Port",
    5: "Domain",
    6: "URI",
    7: "Hash",
    9: "Hash",
    10: "Protocol",
    14: "Command",
    15: "Hash",
    17: "CVE",  # Not Supported Yet.
    18: "CVE",  # Not Supported Yet.
    19: "Port",
    20: "Protocol",
    21: "Port",
    22: "Port",
    24: "Hash",
}

SAFE_BREACH_TYPES = [
    "Protocol",
    "FQDN/IP",
    "Port",
    "URI",
    "SHA256",
    "Attack",
    "Proxies",
    "Impersonated User",
    "Commands",
    "Drop Path",
    "Registry Path",
    "Outbound",
    "Inbound",
    "Server Header",
    "Client Header",
]

# mapper from SB data type to demisto data type that given when the integration was configured.
INDICATOR_TYPE_SB_TO_DEMISTO_MAPPER = {
    "SHA256": "Hash",
    "Port": "Port",
    "FQDN/IP": "Domain",
    "Command": "Command",
    "Protocol": "Protocol",
    "URI": "URI",
}

INDICATOR_TYPE_MAPPER = {
    "FQDN/IP": FeedIndicatorType.Domain,
    "SHA256": FeedIndicatorType.File,
    "Domain": FeedIndicatorType.Domain,
    "Port": "SafeBreach Port",
    "Protocol": "SafeBreach Protocol",
    "Process": "SafeBreach Process",
    "Registry": "SafeBreach Registry",
    "Command": "SafeBreach Command",
    "URI": FeedIndicatorType.URL,
    "IP": FeedIndicatorType.IP,
}

DEMISTO_INDICATOR_REPUTATION = {"None": 0, "Good": 1, "Suspicious": 2, "Bad": 3}

IP_REGEX = r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"

simulator_details_inputs = [
    InputArgument(name="hostname", description="if hostname to be included for search.", required=False, is_array=False),
    InputArgument(
        name="external_ip",
        description="if external IP details to be included for search.",
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="internal_ip", description="if Internal IP are to be included for search.", required=False, is_array=False
    ),
    InputArgument(
        name="os",
        description="operating system name to filter with, Eg: LINUX,WINDOWS etc, incase nothing "
        + "is selected then this will be set as empty which means all are retrieved",
        options=["", "LINUX", "MAC", "WINDOWS"],
        default="",
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="is_enabled",
        description="if to search only enabled ones.",
        options=["true", "false"],
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="is_connected",
        description="status of connection of simulators to search.",
        options=["true", "false"],
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="is_critical",
        description="whether to search only for critical simulators or not.",
        options=["true", "false"],
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="additional_details",
        description="Whether to show additional details or not.",
        options=["true", "false"],
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="status",
        description="if simulator status are to be included for search.",
        options=["APPROVED", "PENDING", "ALL"],
        default="ALL",
        required=False,
        is_array=False,
    ),
]

simulators_output_fields = [
    OutputArgument(name="is_enabled", description="Whether the simulator is enabled or not.", output_type=str),
    OutputArgument(name="simulator_id", description="The Id of given simulator.", output_type=str),
    OutputArgument(name="name", description="name for given simulator.", output_type=str),
    OutputArgument(name="account_id", description="Account Id of account Hosting given simulator.", output_type=str),
    OutputArgument(name="is_critical", description="Whether the simulator is critical.", output_type=str),
    OutputArgument(name="is_exfiltration", description="If Simulator is exfiltration target.", output_type=str),
    OutputArgument(name="is_infiltration", description="If simulator is infiltration target.", output_type=str),
    OutputArgument(name="is_mail_target", description="If simulator is mail target.", output_type=str),
    OutputArgument(name="is_mail_attacker", description="If simulator is mail attacker.", output_type=str),
    OutputArgument(name="is_pre_executor", description="Whether the simulator is pre executor.", output_type=str),
    OutputArgument(name="is_aws_attacker", description="if the given simulator is aws attacker.", output_type=str),
    OutputArgument(name="is_azure_attacker", description="If the given simulator is azure attacker.", output_type=str),
    OutputArgument(name="external_ip", description="external ip of given simulator.", output_type=str),
    OutputArgument(name="internal_ip", description="internal ip of given simulator.", output_type=str),
    OutputArgument(
        name="is_web_application_attacker",
        description="Whether the simulator is Web application attacker.",
        output_type=str,
    ),
    OutputArgument(name="preferred_interface", description="Preferred simulator interface.", output_type=str),
    OutputArgument(name="preferred_ip", description="Preferred Ip of simulator.", output_type=str),
    OutputArgument(name="hostname", description="Hostname of given simulator.", output_type=str),
    OutputArgument(name="connection_type", description="connection_type of given simulator.", output_type=str),
    OutputArgument(name="simulator_status", description="status of the simulator.", output_type=str),
    OutputArgument(name="connection_status", description="connection status of simulator.", output_type=str),
    OutputArgument(name="simulator_framework_version", description="Framework version of simulator.", output_type=str),
    OutputArgument(name="operating_system_type", description="operating system type of given simulator.", output_type=str),
    OutputArgument(name="operating_system", description="Operating system of given simulator.", output_type=str),
    OutputArgument(name="execution_hostname", description="Execution Hostname of the given simulator.", output_type=str),
    OutputArgument(name="deployments", description="deployments simulator is part of.", output_type=str),
    OutputArgument(name="created_at", description="Creation datetime of simulator.", output_type=str),
    OutputArgument(name="updated_at", description="Update datetime of given simulator.", output_type=str),
    OutputArgument(name="deleted_at", description="deletion datetime of given simulator.", output_type=str),
    OutputArgument(name="assets", description="Assets of given simulator.", output_type=str),
    OutputArgument(name="simulation_users", description="simulator users list.", output_type=str),
    OutputArgument(name="proxies", description="Proxies of simulator.", output_type=str),
    OutputArgument(name="advanced_actions", description="Advanced simulator details.", output_type=str),
]

simulator_details_for_update_fields = [
    InputArgument(
        name="connection_url",
        required=False,
        is_array=False,
        description="The given value will be set as " + "connection string, meaning this can be used to connect to this URL.",
    ),
    InputArgument(
        name="cloud_proxy_url",
        description="the given value will be set as cloud proxy url.",
        required=False,
        is_array=False,
    ),
    InputArgument(
        name="name",
        required=False,
        is_array=False,
        description="The given value will be set as name of simulator. "
        + "This will be the name of simulator once the command runs.",
    ),
    InputArgument(
        name="preferred_interface",
        required=False,
        is_array=False,
        description="the given value will be set as preferred interface.",
    ),
    InputArgument(
        name="preferred_ip",
        required=False,
        is_array=False,
        description="the given value will be set as Preferred IP to connect to the simulator.",
    ),
    InputArgument(name="tunnel", required=False, is_array=False, description="the given value will be set as tunnel."),
]

test_summaries_output_fields = [
    OutputArgument(name="scenario_id", description="scenario ID of the test.", output_type=str),
    OutputArgument(name="simulation_name", description="Name of the simulation.", output_type=str),
    OutputArgument(name="security_action_per_control", description="Security Actions of the simulation.", output_type=str),
    OutputArgument(name="test_id", description="Test id of the test.", output_type=str),
    OutputArgument(name="status", description="status of the test.", output_type=str),
    OutputArgument(name="planned_simulations_amount", description="Planned simulations count of the test.", output_type=str),
    OutputArgument(name="simulator_executions", description="simulator executions of the test.", output_type=str),
    OutputArgument(name="attack_executions", description="list of attacks that are part of the simulation.", output_type=str),
    OutputArgument(name="ran_by", description="user who started the simulation.", output_type=str),
    OutputArgument(name="simulator_count", description="simulators count per account.", output_type=str),
    OutputArgument(name="end_time", description="End Time of the test.", output_type=str),
    OutputArgument(name="start_time", description="start time of the test.", output_type=str),
    OutputArgument(name="finalStatus.stopped", description="stopped count of attacks.", output_type=str),
    OutputArgument(name="finalStatus.missed", description="missed count of attacks.", output_type=str),
    OutputArgument(name="finalStatus.logged", description="logged count of attacks.", output_type=str),
    OutputArgument(name="finalStatus.detected", description="detected count of attacks.", output_type=str),
    OutputArgument(name="finalStatus.prevented", description="prevented count of attacks.", output_type=str),
]

tests_outputs = [
    OutputArgument(name="id", description="Id of Actively running test.", output_type=int),
    OutputArgument(name="name", description="Name of the test being run.", output_type=str),
    OutputArgument(name="description", description="Details related to the test being run.", output_type=str),
    OutputArgument(name="success_criteria", description="success criterion for the test.", output_type=str),
    OutputArgument(name="original_scenario_id", description="Original scenario ID of the running test", output_type=str),
    OutputArgument(name="actions_count", description="number of actions", output_type=str),
    OutputArgument(name="edges_count", description="number of edges.", output_type=str),
    OutputArgument(name="created_at", description="details related to when test is created.", output_type=str),
    OutputArgument(name="updated_at", description="details related to when test is last updated/changed", output_type=str),
    OutputArgument(name="steps_count", description="number of steps in simulator.", output_type=str),
    OutputArgument(name="scenario_id", description="scenario_id of the test.", output_type=str),
    OutputArgument(name="original_scenario_id", description="scenario_id for reference.", output_type=str),
    OutputArgument(name="ran_by", description="User who ran the scenario.", output_type=str),
    OutputArgument(name="ran_from", description="Where the test ran from.", output_type=str),
    OutputArgument(name="test_id", description="test id of the test.", output_type=str),
    OutputArgument(name="priority", description="priority of tests.", output_type=str),
    OutputArgument(name="retry_simulations", description="Should simulations be retried", output_type=str),
    OutputArgument(name="pause_duration", description="is the test paused and if so till when", output_type=str),
    OutputArgument(name="paused_date", description="when the test is paused", output_type=str),
    OutputArgument(name="expected_simulations_amount", description="number of simulations expected", output_type=str),
    OutputArgument(name="dispatched_simulations_amount", description="the number of simulations dispatched", output_type=str),
    OutputArgument(name="skipped_simulations_amount", description="The number of simulations skipped", output_type=str),
    OutputArgument(name="failed_simulations_amount", description="The number of simulations failed", output_type=str),
]

test_outputs_headers_list = [
    "id",
    "name",
    "description",
    "successCriteria",
    "originalScenarioId",
    "actions_count",
    "edges_count",
    "createdAt",
    "updatedAt",
    "steps_count",
    "planId",
    "original_scenario_id",
    "ranBy",
    "ranFrom",
    "planRunId",
    "priority",
    "retrySimulations",
    "pause_duration",
    "paused_ate",
    "expectedSimulationsAmount",
    "dispatchedSimulationsAmount",
    "skippedSimulationsAmount",
    "failedSimulationsAmount",
]


def test_outputs_headers_transform(header):
    return_map = {
        "id": "id",
        "name": "name",
        "description": "description",
        "successCriteria": "success_criteria",
        "originalScenarioId": "original_scenario_id",
        "actions_count": "actions_count",
        "edges_count": "edges_count",
        "createdAt": "created_at",
        "updatedAt": "updated_at",
        "steps_count": "steps_count",
        "planId": "scenario_id",
        "original_scenario_id": "original_scenario_id",
        "ranBy": "ran_by",
        "ranFrom": "ran_from",
        "planRunId": "test_id",
        "priority": "priority",
        "retrySimulations": "retry_simulations",
        "pauseDuration": "pause_duration",
        "pausedDate": "paused_date",
        "expectedSimulationsAmount": "expected_simulations_amount",
        "dispatchedSimulationsAmount": "dispatched_simulations_amount",
        "skippedSimulationsAmount": "skipped_simulations_amount",
        "failedSimulationsAmount": "failed_simulations_amount",
    }

    return return_map.get(header, header)


metadata_collector = YMLMetadataCollector(
    integration_name="Safebreach",
    description="For enterprises using SafeBreach and XSOAR, integrating this package streamlines"
    + "operations by allowing you to operate SafeBreach through XSOAR, making SafeBreach "
    + "an integral part of the enterprise workflows. This integration includes "
    + "commands for managing tests, insight indicators, simulators "
    + "and deployments, users, API keys, integration issues, and more.",
    display="Safebreach",
    category="Deception & Breach Simulation",
    docker_image="demisto/python3:3.10.13.88772",
    is_fetch=False,
    long_running=False,
    long_running_port=False,
    is_runonce=False,
    image="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAAkCAYAAACdSkYEAAAJ+klEQV"
    + "R42u3dA5DkQBiG4bk927Zt27Zt27Zt27Zt27Zts+"
    + "/dqlxVriuDzCZze1v5q57DTnfXTDLfoNPJ2qyyyiqrrLLKKqusssoqq6yyyswKkDh7UZyBwGtMRAi"
    + "NdomwAd8UG5BIo10IZYzXEMrYRW1+sRJnD4cu2IdXEPiOJ9iJXghls8qv7O"
    + "+k6Iy5OI4TZoWyOISGPfCnahcdzzTaPUN0VTt/Sl"
    + "+hobgf20mF8ALCgR8IabPKT5TyIixULpsVzLMQdhRVtRvhoN0I6d1X2HHWBxvEP3KiIyZiDmZj"
    + "FFoiC7w8uIPS4TOEE2c8cF8CIS4yoQBKowyKIRcSI7DNqv8qmMKBbqp2OyG8BUSgv9vtULXr5mhM"
    + "NzaEF5rjAYQTTzAOcT2wg45CSN5hO1ZgPz5goon3oTru4ieEE99xCgMR32aVrw/mSwg76tuUqpogw/h"
    + "ZcVOf3hgn5fMtcVL+9Ob9b352xvs21Xj1HYz3UudGCIrNEDr1NnnnpIeQbEAoqV1gRDP9SaLfdwxDAJ"
    + "tVvjaYIyE0vEYEm1IEsSCEHQVV40XAGwgNI3VuhAUQOv1EHJN3TkcIlc8I5wueJHotsVnla4MZBGsgVF"
    + "4gn9yWALbHDwjFD7TXGDMfXkhjrkEQHRsgA4TkF1aiBvKhOOpjHK5DYIcHds4UCJV9vuRJ8hntUQ+1FG0"
    + "wFfcgNJS3WeWrgimHKSOaogJC22tHEOOgniKOg/FCo4IyZkY3NsBICElrJ31ye/PAzlkMobLUlzxJXjv5"
    + "rt4Ov6Q+G21WGRVMv1/KJIpQ+YSABs1gpkNVdEBv9EEbFEFwN4K5AmHsCORgHH/IjJboi+6oiTiGBVMugi"
    + "j1eeBCnyBIjwpoitbK36WQwIX+IVASndEXHVECoXTOyidHBbRDT2WsDiiN8G4+H7yQRXk8vRUtURhhXAom"
    + "7VAQjdEaDZEPwf1iMI9AqHwwYMz+Lhzi+IDRCOFyMB2rYyeQjXAbQsMv7EI6E4LZX+rzyk67mOiO/fgK4c"
    + "AWO2OExwR8gtDwCeMR2sl9boB3EA58wzxEcfG5EABtcc/JJNlyJ9v8m9JOaPiMKYjg7sfYiOiHjWUSZNzT"
    + "PH76bnY+xgZEY6xSNIbmu5j3GN5jeY+pjB1RZ4i2QUhK+TCYQyBcdBqhDA8mr6LY4GLfL6hscDDnSH0uGD"
    + "CxNNHOcd4HLva/hpgO7nNDCBfdR2wn2yCK9MLvyBoDJtxuI7beUMbHYwhvY+KlEYRN9IyXbrcUykDYBSHZh"
    + "UDqtj3ipdvjfRtjqSd/HiO+znc3IXmNMj4IZn0IHSYbGUzlnXILhA5fkN6IYHJ7SnyQ+gwzIJjyi098vIDQ"
    + "4TQC2rkvOXSOtd3BNgiFSzrGamXQTPhh+NMTzHXqhQObVKFrEj99WVUwW0LY0dKmlHefPz/fhIDK2Ip1OkI"
    + "U28HHzp0oDi83jj/OQxPkRTLEVX7eFe80QhHShWBewhA70qn6tYCQHEIRREcK9MM3qc1+F8PzBpFVYihjVs"
    + "A0jY+UDxDBxbHfKvetIgqjDDpgFZJIffdrHMKagLSIjlx2jk+3tHNfImIl2qAgkiMeUqM5HkFIktsZa7adhSn9UQw5URo9cQTJnGyXj5iK"
    + "+iiKMspYryEkxfUE8yuEtwiJsv0VuC7x0q1TBXMThB2bbEp591HfxpjqYH7VGaTmLnxs6Y"
    + "+YBn2vzQ8hKeJCMBe7OIHyTH5l13qXUCaAhCSFwccxryOZ0bOPygumkDSx8z17vdTuopv7LZHGi3grjXYpNFZJHUV"
    + "4o2dl+XkajRfYmX4imMoDbObChM03LEA8A8J5Xj5EY1AwK2ksbo/voP1FqX0Hg1f+dEFQE4K5Sup3xEHbFBCSmG7ut"
    + "9XSOOM12ozTmJyJbdrhEr6fSm3P+u6Psvo3ekLMwRcIBz6htg+DuVJe3mdQMKfLH2GdtB8vtV9gwsqfe8hsVDCVd8E"
    + "3Ur82jrpofIoo6eZ+GyaNM1+jzXWtbWpiMDvIXwd8PPnjPYFj2uSP+6GJhHY4A2HHL1Rz8uW/PubjBO7gPi5gLa6YFMxDGh8lZztwWmq/"
    + "x4UnySe0lnTGcGzGRwjJWyQwKJixNCdiHD9OOcjN7IwdGJUxA4dwAw9xGVtwwuE+YTZcY3FFfZODWRlCzZ3DJf1NPFzS39XD"
    + "JTqX7C2WN7biDcJr9KmNVxCOmBTMmxA+cMqAWdkwmAohmW9QMDNC+FBnO+e93tM5jhzMhBptipoczJIOgunHixUWdqbmWzk8"
    + "VOL5YD7wWDD1n7L2Hl4GBDOr0cFUJuS+QfgwmEk12uQ3OZhF/H4w9c8ELlHdHhpvNZ6Mo1AaWZANVXHOpGBelicCMEaHVgY"
    + "GsxeEJLoBwUwFIVmh83HmlL6zXtOY6JuJSsiqKI89ToIZE0JS0bcH0/xF7OaH85XdMz7YARrfQ7M5XDxgfDDlhQWbbDrL"
    + "wGB2hpAkMCCYYTS+WtTxweNLAyGp4eKqLjmY/jUmDwf42mAqp32t1jjtK6/Bp32tRhCPB1P7/MmbNsrDweynMVET4R8Fc"
    + "63GC1VIgw6XXJVPIPfB4yunsVAhgFvB1J6Auwwv3xRM33+iNCFGXhfbFoaQzFPd3k667Tn8eTiYGex8zPPyaDB5B8MvrQ"
    + "P7BgVzOISksqMueiZPENbtYGofXmrj+WAaf2mRM1xO5IV0aZHTplxahI/Bqu9iHZESATUuHdnYzkxrXVW70hCSFp4MptJvH4RkF7LDv8bpaVnQF"
    + "+F0LJuLiADSYYLEqI5NEBraGRjMOPiqdSkTzYP5BE0JYFuN25JBSEbDy81gRsB7jXfhoYiqcTpYWpQxMpimXowrkELVbqfhF"
    + "+NSVnJIfuAZ7uCFk9X8QVRjhZQnfxQXMBNjMAlL8MTEYKZ0sIrpPS7jHG5LpxLlcmOh+S8XL9J1EoGMCqbStweEHY9xHhfxFE"
    + "LxyJWPxoo7mIcxmIgFuO3SPmESzcH2uoszuKpaV3zOQ8H0/ZevVN4Jv0K44R0ya6+79fzhEo2+ZfAFQofGJl3z5wAi2yiDg+kP"
    + "cyB00T4xuSh++uxwifalYVz0BQE8EEzff8Fn5QTbKfig+9Qaxwuz2+CTx4OpfZbLcQgXDTE4mGdRD15mXkJDOYvnJYQLfiGdnXHK"
    + "44VhwVROvtZx35IaFUyzf0XCV4W5vyKBcVAeE3AQj/FN9d3gqfLzUcihY1lfKyzEMVzFdZzHEazCWDRBdjunfZVGF5XSbk5u5MU4"
    + "HMIDvMYzXMBq9EUeeDl4LBXQE1OwDJuwG3uVv7diMYaiFuK5eN+ySI+xoZuPMTjqYjHO4xle4wGOYC5aIL6TcUKiHmbhEK7gBi7iONZhElohH8K7"
    + "+PxqoGy3y3iOF7iDPRiDEgjgznZRTilU2imsssoqq6yyyiqrrLLKKqusssrk+g3rPqPix+2WlgAAAA90RVh0Q29tbWVudABjaW1hZ2"
    + "Vz5AUQeQAAAABJRU5ErkJggg==",
    integration_subtype="python3",
    integration_type="python",
    fromversion="6.6.0",
    conf=[
        ConfKey(
            name="base_url",
            display="Server URL",
            required=True,
            additional_info="This is base URL for your instance.",
            key_type=ParameterTypes.STRING,
        ),
        ConfKey(
            name="credentials",
            display="API Key",
            required=True,
            additional_info="This is API key for your instance, this can be created in Safe Breach User \
                Administration -> API keys, it must be saved as there is no way to view it again.",
            key_type=ParameterTypes.AUTH,
        ),
        ConfKey(
            name="account_id",
            display="Account ID",
            required=True,
            additional_info="This is account ID of account with which we want to get data from safebreach",
            key_type=ParameterTypes.NUMBER,
        ),
        ConfKey(
            name="verify",
            display="Verify SSL Certificate",
            required=False,
            default_value=False,
            additional_info="This Field is useful for checking if the certificate of SSL for HTTPS is valid or not",
            key_type=ParameterTypes.BOOLEAN,
        ),
        ConfKey(
            name="proxy",
            display="Use system proxy settings",
            required=False,
            default_value=False,
            additional_info="This Field is useful for asking integration to use default system proxy settings.",
            key_type=ParameterTypes.BOOLEAN,
        ),
    ],
)


def sb_error_string(error_data, sb_code):
    sbcode_error_dict = {
        700: f"{error_data} value is below permitted minimum",
        701: f"{error_data} value is above permitted maximum",
        702: f"{error_data} length is more than permitted length",
        703: f"{error_data} field is expected to be integer but received something else",
        704: f"{error_data} field cant be empty",
        705: f"{error_data} field cant permit this value",
        706: f"{error_data} field value is supposed to be unique, value is already taken",
        707: f"{error_data} Requested value not found",
        708: f"{error_data} expected UUID but found something else",
        709: f"{error_data} cannot be null",
        710: f"{error_data} is not a valid URL",
        711: f"{error_data} field should not be changed but has been changed",
        712: "license is invalid",
        713: f"{error_data} fields have opposite Attributes",
        714: f"{error_data} fields block association with each other",
        715: "weak password is set",
        716: "account name and account number dont match",
        718: "license expired",
        719: "Connection Refused",
        720: "passwords dont match",
        721: "gateway timeout",
    }
    return sbcode_error_dict.get(sb_code, "")


def format_sb_code_error(errors_data):
    """
    This function gets all errors for when we get a 400 status and formats the errors accordingly

    Args:
        errors_data (dict): This is all errors with sbcodes returned by safebreach API

    Returns:
        (str,optional): returns error codes which are formatted as string
    """
    error_data = ""

    try:
        errors = errors_data.get("errors")
        if errors_data.get("statusCode") == 400:
            if errors_data.get("message"):
                return json.dumps({"issue": errors_data.get("message"), "details": errors_data.get("additionalData")})
        else:
            issues = []
            for error in errors:
                if error.get("sbcode"):
                    sb_code = error.get("sbcode")
                    fields = error.get("data", {}).get("fields")
                    if not fields:
                        return sb_error_string(error_data, sb_code=sb_code)
                    for field in fields:
                        error_data = field
                        issue = sb_error_string(error_data=error_data, sb_code=sb_code)
                        issues.append(issue)
            return "; ".join(issues)
    except AttributeError:
        return errors_data

    final_error_string = ""
    # here we are formatting errors and then we are making them as a string
    for error in errors:
        error_data = error.get("data")
        error_code = error.get("sbcode")
        final_error_string = final_error_string + " " + sb_error_string(error_data=error_data, sb_code=int(error_code))

    return final_error_string


class Client(BaseClient):
    """
    Client class to interact with the service API

    This Client implements API calls, and does not contain any XSOAR logic.
    Should only do requests and return data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    For this  implementation, no special attributes defined
    """

    def __init__(self, api_key: str, account_id: int, base_url: str, verify: bool, proxy: bool):
        super().__init__(base_url=base_url, verify=verify, proxy=proxy)

        self.base_url = base_url
        self.api_key = api_key
        self.account_id = account_id
        self.proxies = handle_proxy()

    def validate_email(self, email):
        """
        This function is to validate email.

        Args:
            email (str): Email address which we need to validate.

        Returns:
            (boo): boolean value
        """
        # Regular expression for basic email validation
        pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
        return bool(re.match(pattern, email))

    def get_response(self, url: str = "", method: str = "GET", request_params: dict = {}, body: dict = None):
        """
        This function calls API endpoint and handles the response

        Args:
            url (str, optional): endpoint url which follows base URL will be this input . Defaults to "".
            method (str, optional): HTTP method to be used, Defaults to "GET".
            request_params (dict, optional): request parameters if any. Defaults to {}.
            body (dict, optional): request body for API call. Defaults to None.

        Returns:
            (dict,list,Exception): a dictionary or list with data based on API call OR Throws an error based on status code
        """  # noqa: W291
        # base_url = demisto.params().get("base_url", "").strip()

        base_url = self.base_url.strip()
        base_url = base_url if base_url[-1] != "/" else base_url[0:-1]

        url = url if url[0] != "/" else url[1:]
        # full_url = urljoin(self.base_url, url')

        request_url = f"{base_url}/api/{url}"
        api_key = self.api_key.strip()
        headers = {"accept": "application/json", "content-type": "application/json", "x-apitoken": api_key}
        response = self._http_request(
            method=method,
            full_url=request_url,
            json_data=body,
            headers=headers,
            params=request_params,
            ok_codes=[200, 201, 204, 400, 409, 404],
        )
        return (
            response
            if not ((type(response) == dict) and (response.get("error") and not response.get("errorCode")))  # noqa: E721
            else self.handle_sbcodes(response)
        )

    def handle_sbcodes(self, response: dict):
        """
            This function handles errors related to SBcodes if the endpoint gives sbcode in errors

        Args:
            response (dict): all errors given by 400 response code will be accepted as dictionary and are formatted based on
            the state of error

        Raises:
            Exception: all errors will be formatted and then thrown as exception string which will show as error_results in XSOAR
        """
        demisto.debug(f"error being sent to format_sb_code_error function is {response.get('error')}")
        exception_string = format_sb_code_error(response.get("error"))
        raise DemistoException(exception_string)

    def get_all_users_for_test(self):
        """
        This function is being used for testing connection with safebreach
        after API credentials re taken from user when creating instance

        Returns:
            str: This is just status string, if "ok" then it will show test as success else it throws error
        """
        try:
            account_id = self.account_id
            url = f"/config/v1/accounts/{account_id}/users"
            response = self.get_response(url=url)
            if response and response.get("data"):
                return "ok"
            elif response.get("data") == []:
                return "Please check the Account Id and try again"
            return "Could not verify the connection"
        except Exception as exc:
            if "Error in API call [404] - Not Found" in str(exc):
                raise DemistoException(f"Please check the URL configured and try again. Error:-{exc}")
            elif "Error in API call [401] - Unauthorized" in str(exc):
                raise DemistoException(f"Please check the API used and try again. Error:-{exc}")
            elif "SSL Certificate Verification Failed" in str(exc):
                raise DemistoException(
                    "Unable to verify SSL certificate. Please ensure that the SSL "
                    + f"certificate is valid and properly configured. Error:-{exc}"
                )
            else:
                raise DemistoException(exc)

    def get_users_list(self):
        """
            This function returns all users present based on modifiers

        Returns:
            list: this is list of users queried based on modifiers specified
        """
        account_id = self.account_id
        url = f"/config/v1/accounts/{account_id}/users"
        params = {"details": "true", "deleted": "false"}
        response = self.get_response(url=url, request_params=params)
        user_data = response["data"]
        return user_data

    def delete_user(self):
        """
            This function deletes a given user based on arguments of commands

        Returns:
            dict: user data related to the user who has been deleted
        """
        user_id = demisto.args().get("user_id")
        account_id = self.account_id
        method = "DELETE"
        url = f"/config/v1/accounts/{account_id}/users/{user_id}"

        deleted_user = self.get_response(url=url, method=method)
        return deleted_user

    def update_user_with_details(self, user_id: str, user_details: dict):
        """
            This function updates user with given details

        Args:
            user_id (str): this is ID of user to update
            user_details (dict): this is list of user details to update

        Returns:
            dict: user data post update
        """
        # we dont want to update details as empty if user is not giving data in inputs , hence remove false values
        for key in list(user_details.keys()):
            if not user_details[key]:
                user_details.pop(key)

        account_id = self.account_id
        method = "PUT"
        url = f"/config/v1/accounts/{account_id}/users/{int(user_id)}"
        updated_user = self.get_response(url=url, method=method, body=user_details)
        return updated_user

    def list_deployments(self):
        """
            This function lists all deployments we extracted from safebreach

        Returns:
            list: List of deployments data retrieved
        """
        account_id = self.account_id
        url = f"/config/v1/accounts/{account_id}/deployments"

        response = self.get_response(url=url)
        deployments = response["data"]
        return deployments

    def create_deployment_data(self):
        """
            This function creates a deployment based on data given by user, this will be called by an external function \
                which is triggered with a command for creating deployment

        Returns:
            dict: the data of deployment created
        """
        account_id = self.account_id
        name = demisto.args().get("name", "").strip()
        description = demisto.args().get("description", "").strip()
        simulators = demisto.args().get("simulators", [])
        if simulators:
            simulators = simulators.split()
        deployment_payload = {
            "nodes": simulators,
            "name": name,
            "description": description,
        }
        method = "POST"
        url = f"/config/v1/accounts/{account_id}/deployments"
        created_deployment = self.get_response(url=url, method=method, body=deployment_payload)
        return created_deployment

    def update_deployment(self):
        """
            This function is called when we want to update a deployment data

        Raises:
            Exception: This will raise an exception if a deployment with given name or id couldn't be found

        Returns:
            dict: updated deployment data
        """

        deployment_payload = None
        account_id = self.account_id
        deployment_id = int(demisto.args().get("deployment_id", ""))
        if not deployment_id:
            raise DemistoException("Inputs Error: Deployment ID is a required field which has to have a value as input")

        name = demisto.args().get("updated_deployment_name", "").strip()
        simulators = demisto.args().get("updated_simulators_for_deployment", [])
        if simulators:
            simulators = simulators.split()
        description = demisto.args().get("updated_deployment_description", "").strip()
        if name or simulators or description:
            deployment_payload = {"nodes": simulators, "name": name, "description": description}
        method = "PUT"
        url = f"/config/v1/accounts/{account_id}/deployments/{deployment_id}"
        updated_deployment = self.get_response(url=url, method=method, body=deployment_payload)
        return updated_deployment

    def delete_deployment(self):
        """
            This function deletes a deployment with given id or name

        Raises:
            Exception: raised when a deployment with given name or id could not be found

        Returns:
            dict: deleted deployment data
        """
        account_id = self.account_id
        deployment_id = demisto.args().get("deployment_id", None)

        method = "DELETE"
        url = f"/config/v1/accounts/{account_id}/deployments/{deployment_id}"
        deleted_deployment = self.get_response(url=url, method=method)
        return deleted_deployment

    def get_tests_with_args(self):
        """
            This function calls GET of testsummaries endpoint and returns data related to test
            The parameters are all optional

        parameters include:
        1. simulator_id : this is simulator ID

        Returns:
            List[Dict]: Returns test data as a list of dictionaries
        """

        test_summaries = []
        # sort_map = {
        #     "endTime": "endTime",
        #     "startTime": "startTime",
        #     "testID": "planRunId",
        #     "stepRunId": "stepRunId"
        # }

        account_id = self.account_id

        # include_archived = "false"
        # size = demisto.args().get("entries_per_page")
        # status = demisto.args().get("status","")
        scenario_id = demisto.args().get("scenario_id")
        simulation_id = demisto.args().get("simulation_id")
        # sort_by = sort_map.get(demisto.args().get("sort_by"), "endTime")

        parameters = {}
        method = "GET"
        if scenario_id:
            url = f"/data/v1/accounts/{account_id}/testsummaries/{scenario_id}"
        else:
            url = f"/data/v1/accounts/{account_id}/testsummaries"
            for param in [("simulationId", simulation_id)]:
                parameters.update({} if not param[1] else {param[0]: param[1]})
        test_summaries = self.get_response(url=url, method=method, request_params=parameters)

        # When scenario_id is provided, API returns a single dict instead of a list
        # Wrap it in a list for consistent processing

        if scenario_id and isinstance(test_summaries, dict):
            test_summaries = [test_summaries]
        return test_summaries

    def flatten_test_summaries(self, test_summaries):
        """
            This function flattens the test summaries related data for table view

        Args:
            test_summaries (dict): This returns a list of dictionaries of test summaries
            which are flattened versions of data retrieved for tests
        """
        for test_summary in test_summaries:
            for key in list(test_summary.keys()):
                if key == "moveExecutions":
                    test_summary["attack_executions"] = ", ".join(
                        [str(attack["moveId"]) for attack in test_summary.get("moveExecutions", [])]
                    )
                elif key == "securityActionPerControl":
                    test_summary["security_actions"] = ", ".join(
                        [str(control["product"]) for control in test_summary.get("securityActionPerControl", [])]
                    )
                elif key == "finalStatus":
                    data_dict = {
                        "stopped": test_summary[key].get("stopped", 0),
                        "missed": test_summary[key].get("missed", 0),
                        "logged": test_summary[key].get("logged", 0),
                        "detected": test_summary[key].get("detected", 0),
                        "prevented": test_summary[key].get("prevented", 0),
                    }
                    test_summary.update(data_dict)
                elif key in ["endTime", "startTime"] and isinstance(test_summary[key], int):
                    test_summary[key] = datetime.utcfromtimestamp((test_summary[key]) / 1000).strftime(DATE_FORMAT)
        return test_summaries

    def flatten_tests_data(self, tests):
        """
            this function flattens tests data which is used for formatting running and queued tests

        Args:
            tests (list[dict]): This is list of dictionary containing data for tests which are queued

        Returns:
            list[dict]: the same data will have other keys which will be of more use to show in table
        """
        return_list = []
        return_obj = {}
        for test in tests:
            for key in list(test.keys()):
                if key == "actions":
                    return_obj["actions_count"] = len(test[key])
                elif key == "edges":
                    return_obj["edges_count"] = len(test[key])
                elif key == "steps":
                    return_obj["steps count"] = len(test[key])
                elif key == "originalPlan":
                    return_obj["original_scenario_id"] = test[key].get("id")
                return_obj[key] = test[key]
            return_list.append(return_obj)
        return return_list

    def flatten_simulations_data(self, simulations):
        """
            This function flattens simulations data which is used for formatting running and queued simulations

        Args:
            simulations (dict[lists]): This is list of dictionary containing data for simulations which are queued

        Returns:
            list[dict]: the same data will have other keys which will be of more use to show in table
        """
        simulations_copy = deepcopy(simulations)
        if simulations_copy.get("QUEUED"):
            simulations_copy.pop("QUEUED")
        return_list = []
        for simulation in simulations_copy:
            return_obj = {}
            return_obj["status"] = simulation
            simulation_type = simulations[simulation]
            items = list(simulation_type.values())
            for data in items:
                for key in data:
                    if key == "metadata":
                        return_obj["moveId"] = data["metadata"]["moveId"]
                        # skipping params keys because it is full of ID's and useless for user
                    # elif key == "actions":
                    #     return_obj['simulator_ids_involved'] = ""
                    #     return_obj["simulator_names_involved"] = ""
                    #     for object in data["actions"]:
                    #         return_obj['simulator_ids_involved'] =
                    # f"{return_obj['simulator_ids_involved']} ; {object['nodeId']}"
                    #         return_obj['simulator_names_involved'] = f"{return_obj['simulator_names_involved']} \
                    #             ; {object.get('nodeNameInMove','') or object.get('nodeNameInMoveDescription','')}"
                    else:
                        return_obj[key] = data[key]
                return_list.append(return_obj)

        return return_list

    def delete_test_result_of_test(self):
        """
            This function deletes test results of a given test ID by calling related endpoint

        Returns:
            dict: Deleted test data results
        """
        account_id = self.account_id
        test_id = demisto.args().get("test_id")

        method = "DELETE"
        url = f"/data/v1/accounts/{account_id}/tests/{test_id}"
        request_parameters = {"softDelete": "false"}

        test_summaries = self.get_response(url=url, method=method, request_params=request_parameters)
        return test_summaries

    def get_all_integration_issues(self, error_logs, connector_map, connector):
        integration_log = {}
        if error_logs.get("lastTestConnection") is not None:
            integration_log.update(self.map_error_log_data(connector, connector_map))
        return integration_log

    def map_error_log_data(self, integration_id, connector_map):
        log = {}
        integration_data = connector_map.get("result").get("providers")
        if integration_data:
            for data in integration_data:
                if data.get("id", "") == integration_id:
                    log["integration_name"] = data.get("name", "")
                    log["connector"] = integration_id  # Assuming integration_id is the correct value
        return log

    def get_all_errors(self, error_log, log):
        errors = []
        error_list = error_log.get("errors", [])
        if error_list:
            for error in error_list:
                log.update(
                    {
                        "action": error.get("action", ""),
                        "success": error.get("success", ""),
                        "error": error.get("error", ""),
                        "timestamp": error.get("timestamp", ""),
                    }
                )
                errors.append(log)
        return errors

    def flatten_error_logs_for_table_view(self, error_logs):
        """
            This function flattens error logs into a single leveled dictionary for table view

        Args:
            error_logs (dict): This is list of dictionaries which have multiple levels of data

        Returns:
            dict : flattened error logs which are easier to display on table
        """
        flattened_logs_list = []
        error_map = {"ERROR": "errors", "WARNING": "warnings"}
        log = {}
        preference = demisto.args().get("error_type") if demisto else None
        preference = error_map.get(preference, "")  # type: ignore[arg-type]
        connector_map = self.get_integration_details()
        for connector in error_logs:
            # if preference is empty means we need to fetch both the errors and warnings.
            if preference == "":
                log = self.get_all_integration_issues(error_logs[connector], connector_map, connector)
            elif error_logs[connector].get(preference):
                log = self.map_error_log_data(connector, connector_map)
            else:
                continue
            error_list = self.get_all_errors(error_logs[connector], log)
            for error in error_list:
                flattened_logs_list.append(error)
        return flattened_logs_list

    def get_all_integration_error_logs(self):
        """
            This function retrieves all error logs of a given account

        Returns:
            dict: This will be having dict containing results and status
        """
        account_id = self.account_id
        method = "GET"
        url = f"/siem/v1/accounts/{account_id}/config/providers/status"

        error_logs = self.get_response(url=url, method=method)
        return error_logs

    def get_integration_details(self):
        """
            This function retrieves all integrations of a given account and their configurations

        Returns:
            dict: This will be having dict containing results and status
        """
        account_id = self.account_id
        method = "GET"
        url = f"/siem/v1/accounts/{account_id}/config/providers"

        error_logs = self.get_response(url=url, method=method)
        return error_logs

    def delete_integration_error_logs(self):
        """
            This function accepts connector ID related to a connector and then returns a status

        Returns:
            dict: status stating whether its success and how many errors are remaining incase of failure to delete some
        """
        account_id = self.account_id
        connector_id = demisto.args().get("integration_id", "").strip()

        method = "DELETE"
        url = f"/siem/v1/accounts/{account_id}/config/providers/status/delete/{connector_id}"

        error_logs = self.get_response(url=url, method=method)
        return error_logs

    def generate_api_key(self):
        """
            This function calls generate API key endpoint

        Returns:
            dict: response of generate API key API call which contains generated \
                API key and name along with additional details
        """
        account_id = self.account_id
        name = demisto.args().get("name", "").strip()
        description = demisto.args().get("description", "").strip()
        method = "POST"
        url = f"/config/v1/accounts/{account_id}/apikeys"
        data = {}
        if name:
            data["name"] = name
        if description:
            data["description"] = description

        generated_api_key = self.get_response(method=method, url=url, body=data)
        return generated_api_key

    def get_all_active_api_keys_with_details(self):
        """
            This function retrieves all available API keys

        Returns:
            dict: This function retrieves API keys which are active for the given account
        """
        account_id = self.account_id
        method = "GET"
        url = f"config/v1/accounts/{account_id}/apikeys"
        request_params = {"details": "true"}
        keys_data = self.get_response(url=url, method=method, request_params=request_params)
        return keys_data

    def filter_api_key_with_key_name(self, key_name):
        """
            This function retrieves all active keys and then filters key based on given input name

        Args:
            key_name (str): The API key name for searching API key

        Raises:
            Exception: if it couldn't find API key with given name

        Returns:
            string: key ID for API key
        """
        active_keys = self.get_all_active_api_keys_with_details()
        demisto.debug(f"active api keys count is {len(active_keys.get('data'))}")
        required_key_object = list(filter(lambda key_obj: key_obj["name"].lower() == key_name.lower(), active_keys.get("data")))
        if not required_key_object:
            raise DemistoException(f"couldn't find API key with given name: {key_name}")
        return required_key_object[0]["id"]

    def delete_api_key(self):
        """
            This function calls API key delete endpoint

        Returns:
            dict: Deleted API key data
        """
        key_name = demisto.args().get("key_name", "").strip()
        key_id = self.filter_api_key_with_key_name(key_name=key_name)
        account_id = self.account_id
        method = "DELETE"
        url = f"/config/v1/accounts/{account_id}/apikeys/{key_id}"
        deleted_api_key = self.get_response(method=method, url=url)
        return deleted_api_key

    def get_simulator_quota(self):
        """
            This function calls Account details end point which will return account details
            which has nodesQuota

        Returns:
            dict: user details related to the queried account
        """
        account_id = self.account_id
        method = "GET"
        url = f"/config/v1/accounts/{account_id}"
        simulator_details = self.get_response(method=method, url=url)
        return simulator_details

    def get_simulators_details(self, request_params):
        """
            This function queries for simulators along with modifiers which are request_params
            based on that we get simulator related details and this raises an exception if
            no simulator with given details are found

        Args:
            request_params (dict): filters when querying the data related to simulators

        Raises:
            Exception: Raised when no entries are found related to given filters

        Returns:
            list(dict): returns simulator related data which fulfils the given input parameters
        """
        account_id = self.account_id
        method = "GET"
        url = f"/config/v1/accounts/{account_id}/nodes/bulk"

        simulators_details = self.get_response(method=method, url=url, request_params=request_params)
        if not simulators_details.get("data", {}).get("count"):
            raise DemistoException(f"No Matching simulators found with details not found details are {request_params}")
        return simulators_details

    def get_simulators_details_with_id(self):
        """
            This function queries for simulators along with modifiers which are request_params
            based on that we get simulator related details and this raises an exception if
            no simulator with given details are found

        Args:
            request_params (dict): filters when querying the data related to simulators

        Raises:
            Exception: Raised when no entries are found related to given filters

        Returns:
            list(dict): returns simulator related data which fulfils the given input parameters
        """
        account_id = self.account_id
        simulator_id = demisto.args().get("simulator_id")
        method = "GET"
        url = f"/config/v1/accounts/{account_id}/nodes/{simulator_id}"

        simulators_details = self.get_response(method=method, url=url)
        return simulators_details

    def create_search_simulator_params(self):
        """
            This function creates parameters related to simulator as a dictionary

        Returns:
            dict: parameters dictionary
        """
        possible_inputs = {
            "hostname": "hostname",
            "external_ip": "externalIp",
            "internal_ip": "internalIp",
            "os": "os",
            "status": "status",
            "is_enabled": "isEnabled",
            "is_connected": "isConnected",
            "is_critical": "isCritical",
            "additional_details": "additionalDetails",
        }
        request_params = {
            "details": "true",
            "deleted": "false",
            "secret": "false",
        }
        for parameter in possible_inputs:
            if demisto.args().get(parameter):
                request_params[possible_inputs[parameter]] = (
                    argToBoolean(demisto.args().get(parameter))
                    if (
                        demisto.args().get(parameter) not in ["true", "false"]
                        and parameter in ["details", "deleted", "is_enabled", "is_connected", "is_critical", "additional_details"]
                    )
                    else demisto.args().get(parameter).strip()
                )
        return request_params

    def flatten_simulator_details(self, simulators):
        """
            This function will flatten the nested simulator data
            into a flatter structure for table display

        Args:
            simulators List(dict): This is list of simulators which are to be flattened

        Returns:
            List(dict): This is list of simulators related data for table which is flattened
            List : This is list of keys which are present in the dict
        """
        keys = None
        flattened_simulators = []
        for simulator in simulators:
            simulator_details = {
                "is_enabled": simulator.get("isEnabled"),
                "simulator_id": simulator.get("id"),
                "simulator_name": simulator.get("name"),
                "account_id": simulator.get("accountId"),
                "is_critical": simulator.get("isCritical"),
                "is_exfiltration": simulator.get("isExfiltration"),
                "is_infiltration": simulator.get("isInfiltration"),
                "is_mail_target": simulator.get("isMailTarget"),
                "is_mail_attacker": simulator.get("isMailAttacker"),
                "is_pre_executor": simulator.get("isPreExecutor"),
                "is_aws_attacker": simulator.get("isAWSAttacker"),
                "is_azure_attacker": simulator.get("isAzureAttacker"),
                "is_web_application_attacker": simulator.get("isWebApplicationAttacker"),
                "external_ip": simulator.get("externalIp"),
                "internal_ip": simulator.get("internalIp"),
                "preferred_interface": simulator.get("preferredInterface"),
                "preferred_ip": simulator.get("preferredIp"),
                "hostname": simulator.get("hostname"),
                "connection_type": simulator.get("connectionType"),
                "simulator_status": simulator.get("status"),
                "connection_status": simulator.get("isConnected"),
                "simulator_framework_version": simulator.get("frameworkVersion"),
                "operating_system_type": simulator.get("nodeInfo", {}).get("MACHINE_INFO", {}).get("TYPE", ""),
                "operating_system": simulator.get("nodeInfo", {})
                .get("MACHINE_INFO", {})
                .get("PLATFORM", {})
                .get("PRETTY_NAME", ""),
                "execution_hostname": simulator.get("nodeInfo", {})
                .get("CURRENT_CONFIGURATION", {})
                .get("EXECUTION_HOSTNAME", ""),
                "deployments": simulator.get("group"),
                "created_at": simulator.get("createdAt"),
                "updated_at": simulator.get("updatedAt"),
                "deleted_at": simulator.get("deletedAt"),
                "assets": simulator.get("assets"),
                "simulation_users": simulator.get("simulationUsers"),
                "advanced_actions": simulator.get("advancedActions"),
                "proxies": simulator.get("proxies"),
            }

            if not keys:
                keys = list(simulator_details.keys())
            flattened_simulators.append(simulator_details)

        return flattened_simulators, keys

    def delete_simulator_with_given_id(self, simulator_id, force: str):
        """
            This function calls delete simulator on simulator with given ID

        Args:
            simulator_id (str): This is simulator ID to delete
            force (str): If the simulator is to be force deleted even if its not connected

        Returns:
            dict: Deleted simulator data
        """
        request_params = {"force": force}
        method = "DELETE"
        account_id = demisto.params().get("account_id")
        request_url = f"/config/v1/accounts/{account_id}/nodes/{simulator_id}"

        deleted_simulator = self.get_response(url=request_url, method=method, request_params=request_params)
        return deleted_simulator

    def delete_simulator_with_given_name(self):
        """
            This function deletes a simulator with given name,
            This achieves this by retrieving ID by querying all simulators
            and then retrieving ID of name if it matches.
            Then it calls a function which makes API call with this ID

        Returns:
            dict: deleted simulator related data
        """
        simulator_id = demisto.args().get("simulator_id")
        demisto.debug(f"simulator id of given simulator is {simulator_id}")

        force_delete = "false"
        result = self.delete_simulator_with_given_id(simulator_id=simulator_id, force=force_delete)
        return result

    def make_update_simulator_payload(self):
        # this is created under assumption that only these fields will be  chosen to be updated by user
        """
            This function returns a payload with update related data

        Returns:
            dict: Update simulators payload
        """
        data_dict = {
            "connectionUrl": demisto.args().get("connection_url", "").lower().strip(),
            "cloudProxyUrl": demisto.args().get("cloud_proxy_url", "").strip(),
            "name": demisto.args().get("name", "").strip(),
            "tunnel": demisto.args().get("tunnel", "").strip(),
            "preferredInterface": demisto.args().get("preferred_interface", "").strip(),
            "preferredIp": demisto.args().get("preferred_ip", "").strip(),
        }
        for key, value in tuple(data_dict.items()):
            if not value:
                data_dict.pop(key)
        return data_dict

    def update_simulator_api_call(self, simulator_id, simulator_data):
        """
            This function calls update simulators details API and returns updated data

        Args:
            simulator_id (str): ID of simulators to update
            simulator_data (dict): Payload for PUT call

        Returns:
            dict: Updated simulators details
        """
        method = "PUT"
        account_id = demisto.params().get("account_id")
        request_url = f"/config/v1/accounts/{account_id}/nodes/{simulator_id}"

        updated_simulator = self.get_response(url=request_url, method=method, body=simulator_data)
        return updated_simulator

    def update_simulator(self):
        """
            This function updates simulator with given name

        Returns:
            dict: this is updated simulators details for given simulators ID
        """
        simulator_id = demisto.args().get("simulator_id")

        payload = self.make_update_simulator_payload()

        updated_simulator = self.update_simulator_api_call(simulator_id=simulator_id, simulator_data=payload)
        return updated_simulator

    def approve_simulator(self):
        """
            This function updates simulator with given name

        Returns:
            dict: this is updated simulators details for given simulators ID
        """
        simulator_id = demisto.args().get("simulator_id")
        payload = {"status": "APPROVED"}

        approved_simulator = self.update_simulator_api_call(simulator_id=simulator_id, simulator_data=payload)
        return approved_simulator

    def rotate_verification_token(self):
        """
            This function rotates a verification token thus generating a new token

        Returns:
            dict: dict containing a new token
        """
        method = "POST"
        account_id = demisto.params().get("account_id")
        request_url = f"/config/v1/accounts/{account_id}/nodes/secret/rotate"

        new_token = self.get_response(url=request_url, method=method, body={})
        return new_token

    def create_user_data(self):
        """
            This function takes user inputs and then formats it and
            then calls create user endpoint.

        Returns:
            dict: created user data
        """
        account_id = self.account_id
        name = demisto.args().get("name", "").strip()
        email = demisto.args().get("email", "").strip()
        is_active = argToBoolean(demisto.args().get("is_active"))
        send_email_post_creation = argToBoolean(demisto.args().get("email_post_creation"))
        password = demisto.args().get("password")
        change_password = argToBoolean(demisto.args().get("change_password_on_create"))
        role = demisto.args().get("user_role", "").strip()
        deployment_list = demisto.args().get("deployments", None)
        try:
            deployment_list = list(map(int, deployment_list.split(","))) if deployment_list else []
        except ValueError:
            raise DemistoException(
                "Input Error: Deployments ids are numbers, please give deployments ids as comma separated values"
            )

        if not email:
            raise DemistoException(
                "Inputs Error: Email is necessary when creating user, please give a valid email which hasn't \
                been used before for user creation"
            )

        if not self.validate_email(email):
            raise DemistoException("Inputs Error: Please enter valid email")

        user_payload = {
            "name": name,
            "password": password,
            "email": email,
            "mustChangePassword": change_password,
            "sendMail": send_email_post_creation,
            "role": role,
            "isActive": is_active,
            "deployments": deployment_list,
        }

        method = "POST"
        url = f"/config/v1/accounts/{account_id}/users"
        created_user = self.get_response(url=url, method=method, body=user_payload)
        return created_user

    def update_user_data(self):
        """
            This function takes user inputs and then formats it and
            then makes a call to function that handles updating user.

        Returns:
            dict: updated user data
        """

        user_id = demisto.args().get("user_id")
        # user_email = demisto.args().get("email", "").strip()
        name = demisto.args().get("name", "")
        if name:
            name = name.strip()
        is_active = demisto.args().get("is_active")
        if is_active:
            is_active = argToBoolean(is_active)
        description = demisto.args().get("user_description", "")
        if description:
            description = description.strip()
        role = demisto.args().get("user_role", "")
        if role:
            role = role.strip()
        password = demisto.args().get("password")
        deployment_list = demisto.args().get("deployments", [])
        try:
            deployment_list = list(map(int, deployment_list.split(","))) if deployment_list else []
        except ValueError:
            raise DemistoException(
                "Input Error: deployments ids are numbers, please give deployments ids as comma separated values"
            )

        # formatting the update user payload, we remove false values after passing to function which calls endpoint
        details = {
            "name": name,
            "isActive": is_active,
            "deployments": deployment_list,
            "description": description,
            "role": role,
            "password": password,
        }
        user = self.update_user_with_details(user_id, details)
        return user

    def get_active_tests(self):
        """
            This function calls GET of active tests being run endpoint

        Returns:
            Dict: Returns a dictionary containing:
                - data.queue: List of queued test objects
                - data.isPause: Boolean indicating if queue is paused
                - data.slotState: List of slot state information
                - data.testRunState: Dictionary of running test objects keyed by planRunId
        """
        account_id = self.account_id

        method = "GET"
        url = f"/orch/v3/accounts/{account_id}/queue"
        tests = self.get_response(url=url, method=method)
        return tests

    def get_active_simulations(self):
        """
            This function calls GET of active tests being run endpoint

        Returns:
            Dict: Returns test data as a dictionary per test which is array as value for "data" key
        """
        method = "GET"
        url = "/execution/v2/tasks"
        simulations_details = self.get_response(url=url, method=method)
        return simulations_details

    def set_simulations_status(self):
        account_id = self.account_id

        method = "PUT"
        url = f"orch/v3/accounts/{account_id}/queue/state"
        data = {"status": demisto.args().get("simulation_or_test_state", "").strip()}
        simulations_details = self.get_response(url=url, method=method, body=data)
        return simulations_details

    def get_schedules(self):
        account_id = self.account_id

        method = "GET"
        url = f"/config/v1/accounts/{account_id}/schedules"
        request_params = {"details": "true", "deleted": "false"}
        schedule_data = self.get_response(url=url, method=method, request_params=request_params)
        return schedule_data

    def append_cron_to_schedule(self, schedules):
        for schedule in schedules:
            if schedule["cronString"]:
                schedule["user_schedule"] = CronString(schedule["cronString"], schedule["cronTimezone"]).to_string()
        return schedules

    def delete_schedule(self):
        account_id = self.account_id
        schedule_id = int(demisto.args().get("schedule_id"))

        method = "DELETE"
        url = f"/config/v2/accounts/{account_id}/plans/{schedule_id}"
        schedule_data = self.get_response(url=url, method=method)
        return schedule_data

    def extract_default_scenario_fields(self, scenarios):
        return_list = []
        for scenario in deepcopy(scenarios):
            return_obj = {"tags_list": "", "steps_order": ""}
            for key in scenario:
                if key == "tags" and scenario[key]:
                    return_obj["tags_list"] = ", ".join(scenario.get(key, []))
                elif key == "steps" and scenario[key]:
                    steps_involved = [step.get("name") for step in scenario[key]]
                    return_obj["steps_order"] = "; ".join(steps_involved)
                return_obj[key] = scenario[key]
            return_list.append(return_obj)
        return return_list

    def extract_custom_scenario_fields(self, scenarios):
        return_list = []
        for scenario in deepcopy(scenarios):
            return_obj = {"actions_list": "", "steps_order": "", "edges_count": 0}
            for key in scenario:
                if key == "actions" and scenario[key]:
                    actions_list = [
                        f"{action.get('type')} with identity: "
                        + f"{action.get('data',{}).get('uuid','') or action.get('data',{}).get('id','')}"
                        for action in scenario[key]
                    ]
                    return_obj["actions_list"] = "; ".join(actions_list)
                elif key == "steps" and scenario[key]:
                    steps_involved = [step.get("name") for step in scenario[key] if step.get("name") is not None]
                    return_obj["steps_order"] = "; ".join(steps_involved)
                return_obj[key] = scenario[key]
            return_obj["custom_data_object_for_rerun_simulation"] = json.dumps(
                {"name": return_obj.get("name"), "steps": return_obj.get("steps")}
            )
            return_list.append(return_obj)

        return return_list

    def extract_test_fields(self, test):
        return_obj = {"actions_list": "", "steps_order": "", "edges_count": 0}
        for key in test:
            if key == "actions" and test[key]:
                new_list = [f"{action.get('type')} with id:{action.get('id','')}" for action in test[key]]
                return_obj["actions_list"] = "; ".join(new_list)
            elif key == "steps" and test[key]:
                steps_involved = [
                    f"{step.get('name')}- with test ID {step.get('planRunId')}"
                    for step in test[key]
                    if step.get("name") is not None
                ]
                return_obj["steps_order"] = "; ".join(steps_involved)
            return_obj[key] = test[key]
        return return_obj

    def format_services_response(self, services):
        return_list = []
        for service in deepcopy(services):
            if isinstance(service, dict):
                # Now proceed with the formatting
                service["connection_status"] = (
                    f"Service {service['name']} is {'running' if service['isUp'] else 'not running'} as on {service['lastCheck']}"
                )
                return_list.append(service)
        return return_list

    def get_prebuilt_scenarios(self):
        method = "GET"
        url = "/content-manager/v18/scenarios"

        scenarios = self.get_response(url=url, method=method)
        return scenarios

    def get_custom_scenarios(self):
        account_id = self.account_id

        method = "GET"
        url = f"/config/v2/accounts/{account_id}/plans"
        request_params = {"details": demisto.args().get("schedule_details", "").strip()}
        scenarios = self.get_response(url=url, method=method, request_params=request_params)
        return scenarios

    def get_services_status(self):
        method = "GET"
        url = "/lighthouse/v1/services"
        services_data = self.get_response(url=url, method=method)
        return services_data

    def get_simulations(self):
        account_id = self.account_id
        method = "GET"
        url = f"/data/v1/accounts/{account_id}/executionsHistoryResults"

        request_params = {"runId": demisto.args().get("test_id")}

        simulations_data = self.get_response(url=url, method=method, request_params=request_params)
        return simulations_data

    def get_verification_token(self):
        account_id = self.account_id
        method = "GET"
        url = f"/config/v1/accounts/{account_id}/nodes/secret"
        verification_data = self.get_response(url=url, method=method)
        return verification_data

    def rerun_test_or_simulation(self):
        account_id = self.account_id
        test_data: dict
        if demisto.command() == "safebreach-rerun-test":
            test_data = {
                "testId": demisto.args().get("test_id"),
                "name": demisto.args().get("test_name", ""),
            }
        elif demisto.command() == "safebreach-rerun-simulation":
            simulation_ids = demisto.args().get("simulation_ids").strip()
            simulation_ids = simulation_ids.replace('"', "").split(",")
            simulations_list = []
            for simulation in simulation_ids:
                try:
                    simulations_list.append(int(simulation))
                except ValueError:
                    raise DemistoException(
                        "Input Error: simulation_ids are numbers and not strings, please \
                        enter valid simulation ids"
                    )
            test_data = {
                "name": demisto.args().get("test_name", "").strip(),
                "steps": [
                    {
                        "attacksFilter": {},
                        "attackerFilter": {},
                        "targetFilter": {},
                        "systemFilter": {"simulations": {"operator": "is", "values": simulations_list}},
                    }
                ],
            }
        else:
            test_data = {}
            demisto.debug(f"{demisto.command()=} didn't match any condition. {test_data=}")
        method = "POST"
        url = f"/orch/v3/accounts/{account_id}/queue"
        tests_data = self.get_response(url=url, method=method, body={"plan": test_data})  # , request_params=request_params)
        return tests_data

    def contains(self, list_a, list_b):
        return list(set(list_a) & set(list_b))

    def get_category_and_data_type_filters(self, insight_category, insight_data_type):
        # The User can provide the arguments as the following: insightCategory=`Web,Network Inspection`
        if isinstance(insight_category, str):
            insight_category = insight_category.split(",")
        if isinstance(insight_data_type, str):
            insight_data_type = insight_data_type.split(",")

        # if the user provide invalid category or data type raise an ValueError.
        if not self.contains(insight_category, ["Network Access", "Network Inspection", "Endpoint", "Email", "Web", "Data Leak"]):
            raise ValueError(f"Category {insight_category} is not a valid category")
        if not self.contains(insight_data_type, ["Hash", "Domain", "URI", "Command", "Port", "Protocol"]):
            raise ValueError(f"Data type {insight_data_type} is not a valid data type")
        return insight_category, insight_data_type

    def get_insights_ids_by_category(self, insight_category):
        output = []
        for category in insight_category:
            if CATEGORY_MAPPER.get(category):
                output.append(CATEGORY_MAPPER.get(category))
        return list({y for x in output if x is not None for y in x})

    def get_insights(self, test_id):
        account_id = self.account_id
        url = f"/data/v1/accounts/{account_id}/insights?type=actionBased&planRunIds={test_id}"
        return self.get_response(url=url, method="GET")

    def get_remediation_data(self, insight_id, test_id):
        account_id = self.account_id
        url = f"/data/v1/accounts/{account_id}/insights/{insight_id}/remediation?planRunIds={test_id}"
        return self.get_response(url=url, method="GET")

    def get_insights_command(self, args, test_id):
        insight_ids = args.get("insightIds")
        insights = []
        if isinstance(insight_ids, str):
            insight_ids = literal_eval(insight_ids)
        if isinstance(insight_ids, int):
            insight_ids = [insight_ids]
        response = self.get_insights(test_id)

        try:
            insights = sorted(response, key=lambda i: i.get("ruleId"))
        except TypeError:
            demisto.debug("Failed to sort SafeBreach insights, skip")

        if insight_ids and len(insight_ids) > 0:
            # Verify that insight_ids holds List[int]
            if isinstance(insight_ids, list):
                insight_ids = list(map(int, insight_ids))
            insights = [item for item in insights if int(item.get("ruleId")) in insight_ids]

        return insights

    def extract_safebreach_error(self, response):
        errors = response.json().get("error") and response.json().get("error").get("errors")
        if not errors:
            return f'Failed to extract error!\n{response.json().get("error")}'
        return ",".join([e.get("data").get("message") for e in errors])

    def unescape_string(self, string):
        try:
            return string.encode("utf-8").decode("unicode_escape")
        except Exception as e:
            demisto.debug("Failed to unescape_string", e)

    def extract_data(self, data):
        output = []
        list_of_seen_items = []

        parent_key = list(data.keys())[0]
        first_level_data = list(data[parent_key].keys())
        list_of_seen_items.extend(first_level_data)
        if parent_key != "Attack":
            output.extend([{"type": parent_key, "value": o} for o in first_level_data])

        for indicator in data[parent_key]:
            if self.contains(SAFE_BREACH_TYPES, list(data[parent_key][indicator].keys())):
                for inner_type in data[parent_key][indicator]:
                    formated_inner_type = inner_type.replace(" ", "")
                    for item in data[parent_key][indicator][inner_type]:
                        if item == "N/A":
                            continue
                        if isinstance(item, str):
                            item = self.unescape_string(item)
                        if item not in list_of_seen_items:
                            list_of_seen_items.append(item)
                            output.append({"type": formated_inner_type, "value": item})
        return output

    def get_remediation_data_command(self, args, test_id):
        insight_id = args.get("insightId")
        response = self.get_remediation_data(insight_id, test_id)
        insight = self.get_insights_command({"insightIds": [insight_id]}, test_id)

        if insight:
            insight = insight[0]

        sb_remediation_data = response.get("remediationData")
        processed_data = self.extract_data(sb_remediation_data)
        return processed_data

    def is_ip(self, value):
        if isinstance(value, int):
            value = str(value)
        return re.match(IP_REGEX, value)

    def get_simulators_versions_list(self):
        url = "/updater/v2/updates/nodes"
        return self.get_response(url=url, method="GET")

    def get_installation_links(self):
        url = "/updater/v2/installationLinks"
        return self.get_response(url=url, method="GET")

    def update_simulator_with_id(self):
        nodeId = demisto.args().get("simulator_id", "")
        simulator_version = demisto.args().get("simulator_version", "")
        url = "/updater/v2/simulatorUpdate/"
        body_dict = {
            "enableUpdates": True,
            "nodeId": nodeId,
            "selectedVersion": simulator_version,
        }
        simulator_data = self.get_response(url=url, method="POST", body=body_dict)
        return simulator_data

    def get_indicators_command(self):
        indicators: list = []
        insights: Iterable[Any]
        count = 0
        limit = int(demisto.args().get("limit", 1000))
        insightCategory = demisto.args().get("insightCategory", "")
        insightDataType = demisto.args().get("insightDataType", "")
        test_id = demisto.args().get("test_id", 0)

        insight_category, insight_data_type = self.get_category_and_data_type_filters(insightCategory, insightDataType)

        # Convert category into insight id
        insights_ids = self.get_insights_ids_by_category(insight_category)
        raw_insights = self.get_insights(test_id)
        # Filter insight by category
        insights = [item for item in raw_insights if isinstance(item, dict) and int(item.get("ruleId", 0)) in insights_ids]

        for insight in insights:
            # Fetch remediation data for each insight
            processed_data = self.get_remediation_data_command({"insightId": insight.get("ruleId")}, test_id)
            for item in processed_data:
                # if the data type is not in the filter data types continue,
                if INDICATOR_TYPE_SB_TO_DEMISTO_MAPPER.get(item["type"]) not in insight_data_type:
                    continue
                if not INDICATOR_TYPE_MAPPER.get(str(item["type"])) or item["value"] == "N/A":
                    continue
                if isinstance(item["type"], int):
                    demisto.debug("Data type is int", item["type"], insight["ruleId"])

                is_behavioral = item["type"] not in ["Domain", "FQDN/IP", "SHA256", "URI", "Hash"]
                raw_json = {
                    "value": str(item["value"]),
                    "dataType": item["type"],
                    "insightTime": insight.get("maxExecutionTime"),
                }
                attacks = filter(lambda x: x is not None, insight.get("attacks"))
                mapping = {
                    "description": "SafeBreach Insight - {}".format(insight["actionBasedTitle"]),
                    item["type"].lower(): item["value"],
                    "safebreachseverity": insight.get("severity"),
                    "safebreachseverityscore": str(insight.get("severityScore")),
                    "safebreachisbehavioral": is_behavioral,
                    "safebreachattackids": list(map(str, attacks)),
                    "tags": [
                        f"SafeBreachInsightId: {insight.get('ruleId')}",
                    ],
                }

                indicator = {
                    "value": str(item["value"]),
                    "type": INDICATOR_TYPE_MAPPER.get(str(item["type"])),
                    "rawJSON": raw_json,
                    "fields": mapping,
                }
                if self.is_ip(item["value"]):
                    indicator["type"] = FeedIndicatorType.IP

                count += 1
                if count > limit:
                    return indicators
                indicators.append(indicator)
        return indicators


def get_simulators_and_display_in_table(client: Client):
    """
        This function gets all simulators and displays in table

    Args:
        client (Client): Client class for API calls
        just_name (bool, optional): This will be used to know whether to search and return all
        simulators or only one. Defaults to False.

    Returns:
        CommandResults : table showing simulator details
        dict: simulator details
    """
    request_params = client.create_search_simulator_params()

    result = client.get_simulators_details(request_params=request_params)
    demisto.debug(f"Related simulations are: {result}")

    flattened_simulators, keys = client.flatten_simulator_details(result.get("data", {}).get("rows", {}))

    if flattened_simulators:
        human_readable = tableToMarkdown(name="Simulators Details", t=flattened_simulators, headers=keys)
    else:
        human_readable = f"No simulators found:- {flattened_simulators}"
    outputs = result.get("data", {}).get("rows")
    outputs_prefix = "SafeBreach.Simulator"
    result = CommandResults(outputs_prefix=outputs_prefix, outputs=outputs, readable_output=human_readable)
    return result


def format_simulations_data(simulations):
    return_list = []
    if simulations:
        for simulation in simulations:
            return_object = {}
            for key in simulation:
                if key in [
                    "id",
                    "targetNodeName",
                    "attackerNodeName",
                    "destNodeName",
                    "moveDesc",
                    "moveName",
                    "securityAction",
                    "resultDetails",
                ]:
                    return_object[key] = simulation.get(key)
                elif key == "Attack_Type":
                    return_object["attacks_involved"] = ", ".join([attack["displayName"] for attack in simulation[key]])
            return_list.append(return_object)
    return return_list


def get_specific_simulator_details(client: Client):
    """
        This function simulator details and displays in table

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults : table showing simulator details
        dict: simulator details
    """

    result = client.get_simulators_details_with_id()
    demisto.debug(f"Result of simulator details is: {result}")

    flattened_simulators, keys = client.flatten_simulator_details([result.get("data", {})])
    if flattened_simulators:
        human_readable = tableToMarkdown(name="Simulators Details", t=flattened_simulators, headers=keys)
    else:
        human_readable = f"No simulator details found: {flattened_simulators}"
    outputs = result.get("data", {})
    outputs_prefix = "SafeBreach.Simulator"
    result = CommandResults(outputs_prefix=outputs_prefix, outputs=outputs, readable_output=human_readable)
    return result


def tests_header_transformer(header):
    return_map = {
        "planId": "scenario_id",
        "planName": "scenario_name",
        "securityActionPerControl": "security_action_per_control",
        "planRunId": "test_id",
        "status": "status",
        "plannedSimulationsAmount": "planned_simulations_amount",
        "simulatorExecutions": "simulator_executions",
        "ranBy": "ran_by",
        "security_actions": "security_actions",
        "attack_executions": "attack_executions",
        "simulatorCount": "simulator_count",
        "endTime": "end_time",
        "startTime": "start_time",
        "stopped": "stopped",
        "missed": "missed",
        "logged": "logged",
        "detected": "detected",
        "prevented": "prevented",
    }
    return return_map.get(header, header)


def get_tests_summary(client: Client):
    """
        This function retrieves tests and then flattens them and shows them in  a table

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: This returns a table view of data and a dictionary as output
    """
    test_summaries = client.get_tests_with_args()
    demisto.debug(f"Get tests summary is: {test_summaries}")
    client.flatten_test_summaries(test_summaries)
    if test_summaries:
        human_readable = tableToMarkdown(
            name="Test Results",
            t=test_summaries,
            headerTransform=tests_header_transformer,
            headers=[
                "planId",
                "planName",
                "planRunId",
                "status",
                "ranBy",
                "security_actions",
                "securityActionPerControl",
                "plannedSimulationsAmount",
                "simulatorExecutions",
                "simulatorCount",
                "attack_executions",
                "endTime",
                "startTime",
                "stopped",
                "missed",
                "logged",
                "detected",
                "prevented",
            ],
        )
    else:
        human_readable = f"No test summaries found: {test_summaries}"
    outputs = {"tests_data": test_summaries}
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-get-all-users",
    inputs_list=None,
    outputs_prefix="SafeBreach.User",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.User",
            output_type=int,
            description="The ID of User retrieved. this can be used to further link this user with\
                      user_id field of safebreach-update-user or safebreach-delete-user commands",
        ),
        OutputArgument(name="name", prefix="SafeBreach.User", output_type=str, description="The name of User retrieved."),
        OutputArgument(
            name="email",
            prefix="SafeBreach.User",
            output_type=str,
            description="The email of User retrieved. this can be used for updating user or\
                      deleting user for input email of commands safebreach-update-user or safebreach-delete-user ",
        ),
    ],
    description="This command gives all users who are not deleted.",
)
def get_all_users(client: Client):
    """
        This function is executed when 'safebreach-get-all-users' command is executed

    Args:
        client (Client): This is client class

    Returns:
        CommandResults,dict: This returns all user data retrieved based on given parameters,
        as a table and as a dictionary
    """
    user_data = client.get_users_list()
    demisto.debug(f"Get all users result is :{user_data}")

    if user_data:
        human_readable = tableToMarkdown(name="user data", t=user_data, headers=["id", "name", "email"])
    else:
        human_readable = f"No users found: {user_data}"
    result = CommandResults(outputs_prefix="SafeBreach.User", outputs=user_data, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-get-user-with-matching-name-or-email",
    inputs_list=[
        InputArgument(name="name", required=False, is_array=False, description="Name of the user. Partial match is supported"),
        InputArgument(name="email", required=False, is_array=False, description="Email of the user. Exact match required"),
    ],
    outputs_prefix="SafeBreach.User",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.User",
            output_type=int,
            description="The ID of User retrieved. this can be used to further link this user with user_id field of \
                      safebreach-update-user or safebreach-delete-user commands",
        ),
        OutputArgument(name="name", prefix="SafeBreach.User", output_type=str, description="The name of User retrieved."),
        OutputArgument(
            name="email",
            prefix="SafeBreach.User",
            output_type=str,
            description="The email of User retrieved. this can be used for updating user or deleting user \
                      for input email of commands safebreach-update-user or safebreach-delete-user",
        ),
    ],
    description="The command retrieves users based on the provided inputs. "
    + "If an email is provided, it returns the user associated with that email, as email is a unique identifier"
    + "If a name is provided, exact name matching is required to ensure accurate retrieval of a single user;"
    + "otherwise, multiple users may be returned. It's essential to note that either a name or an email must be populated as input;"  # noqa: E501
    + "failure to provide either results in an error.",
)
def get_user_id_by_name_or_email(client: Client):
    """
        This Command Returns a user or their email by a given name or email.

    Args:
        client (Client): Client class for calling API

    Raises:
        Exception: Raised when no user with given name or email or found

    Returns:
        CommandResults,dict,Exception: We create a table showing all details related to users found and
        give JSON which has all data related to filtered users if any users match given criterion,
        else we raise an exception which is shown as error_result in XSOAR saying user is not found
    """

    name = demisto.args().get("name", "").strip()
    email = demisto.args().get("email", "").strip()
    if not (name or email):
        raise DemistoException("Incorrect inputs: either name or email are to be given.")
    user_list = client.get_users_list()
    demisto.debug(f"User list: {user_list}")

    filtered_user_list = list(
        filter(
            lambda user_data: (
                (name.lower() == user_data["name"].lower() if name else False) or (email.lower() == user_data["email"].lower())
            ),
            user_list,
        )
    )

    if filtered_user_list:
        human_readable = tableToMarkdown(name="user data", t=filtered_user_list, headers=["id", "name", "email"])
        outputs = filtered_user_list

        result = CommandResults(outputs_prefix="SafeBreach.User", outputs=outputs, readable_output=human_readable)

        return result
    raise DemistoException(f"User with name {name} was not found")


@metadata_collector.command(
    command_name="safebreach-create-user",
    inputs_list=[
        InputArgument(name="name", required=True, is_array=False, description="Name of the user to create."),
        InputArgument(name="email", required=True, is_array=False, description="Email of the user to Create."),
        InputArgument(
            name="is_active",
            description="If the user will be activated upon creation. Setting this parameter "
            + "to 'true' active as soon as this command succeeds. Setting to 'false', will require to activate the "
            + "user by an administrator. Possible values are: true, false. Default is true.",
            required=False,
            is_array=False,
            options=["true", "false"],
            default="true",
        ),
        InputArgument(
            name="email_post_creation",
            required=False,
            is_array=False,
            options=["true", "false"],
            default="true",
            description="Whether to send an email with login information to a newly crated user. Possible "
            + "values are: true, false. Default is false.",
        ),
        InputArgument(
            name="password",
            required=True,
            is_array=False,
            description="Enforce password change on user creation." + " Possible values are: true, false. Default is false.",
        ),
        InputArgument(
            name="change_password_on_create",
            required=False,
            is_array=False,
            options=["true", "false"],
            default="false",
            description="Should user change password on creation. when this is set to true then "
            + "user will have to reset password on the next login, this can be used if we want user to reset password "
            + "as soon as they login.",
        ),
        InputArgument(
            name="user_role",
            required=False,
            is_array=False,
            description="Role of the user being created. "
            + "Possible values are: viewer, administrator, contentDeveloper, operator. Default is viewer.",
            options=["viewer", "administrator", "contentDeveloper", "operator"],
            default="viewer",
        ),
        InputArgument(
            name="deployments",
            required=False,
            is_array=True,
            description="Comma separated ID of all deployments the user should be part of. The deployment IDs can "
            + "be retrieved from 'list-deployments' command or from UI directly but care should be noted that "
            + "only deployment ids of deployments which haven't been deleted will be shown here and after creation of "
            + "user. for example if 1,2,3 are deployment ids given while creation but if 2 is deleted then when user"
            + " is created , he will only have 1,3.",
        ),
    ],
    outputs_prefix="SafeBreach.User",
    outputs_list=[
        OutputArgument(name="id", description="The ID of User created.", prefix="SafeBreach.User", output_type=int),
        OutputArgument(name="name", description="The name of User created.", prefix="SafeBreach.User", output_type=str),
        OutputArgument(name="email", description="The email of User created.", prefix="SafeBreach.User", output_type=str),
        OutputArgument(name="createdAt", prefix="SafeBreach.User", output_type=str, description="The creation time of User."),
        OutputArgument(
            name="roles",
            prefix="SafeBreach.User",
            output_type=str,
            description="The roles and permissions of User created.",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.User",
            output_type=str,
            description="The description of User if any is given at creation time, it will be populated here.",
        ),
        OutputArgument(
            name="role",
            prefix="SafeBreach.User",
            output_type=str,
            description="The role assigned to user during creation.",
        ),
        OutputArgument(
            name="deployments",
            prefix="SafeBreach.User",
            output_type=str,
            description="The deployments user is part of.",
        ),
    ],
    description="This command creates a user, including credentials and permissions.",
)
def create_user(client: Client):
    """
        This function is executed when 'safebreach-create-user' is called and this creates a user.
        This function calls another function which handles getting inputs and calling API,
        This function just handles creating table and returning table and json

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: This will show a dictionary based on user data created
    """
    created_user = client.create_user_data()
    demisto.debug(f"Create user result is: {created_user}")
    if created_user:
        human_readable = tableToMarkdown(
            name="Created User Data",
            t=created_user.get("data", {}),
            headers=[
                "id",
                "name",
                "email",
                "mustChangePassword",
                "roles",
                "description",
                "role",
                "is_active",
                "deployments",
                "created_at",
            ],
        )
    else:
        human_readable = f"Unable to create user: {created_user}"
    outputs = created_user.get("data", {})
    result = CommandResults(
        outputs_prefix="SafeBreach.User",
        outputs=outputs,
        outputs_key_field="SafeBreach.User",
        readable_output=human_readable,
    )
    return result


@metadata_collector.command(
    command_name="safebreach-update-user",
    inputs_list=[
        InputArgument(name="user_id", required=True, is_array=False, description="user ID of user from safebreach to search."),
        InputArgument(
            name="name",
            required=False,
            is_array=False,
            description="Update the user name to given value of " + "this field",
        ),
        InputArgument(
            name="user_description",
            required=False,
            is_array=False,
            description="Update the user Description to " + "given value in this field.",
        ),
        InputArgument(
            name="is_active",
            required=False,
            is_array=False,
            options=["true", "false", ""],
            default="",
            description=" Update the user Status based on the input, if this is set to false then user will be "
            + "deactivated. unless this field is left empty, whatever is present here will be updated to user details."
            + " user will be selected based on user_id field mentioned above.",
        ),
        InputArgument(
            name="password",
            required=False,
            is_array=False,
            description="Password of user to be updated with. "
            + "this will be used for changing password for user. unless this field is left empty, whatever is present "
            + "here will be updated to user details. user will be selected based on user_id field mentioned above.",
        ),
        InputArgument(
            name="user_role",
            required=False,
            is_array=False,
            options=["viewer", "administrator", "contentDeveloper", "operator"],
            default="",
            description=" Role of "
            + "the user to be changed to. unless you want to change the user role and permissions, dont select "
            + "anything in this field, user will be selected based on user_id field mentioned above.",
        ),
        InputArgument(
            name="deployments",
            required=False,
            is_array=True,
            description="Comma separated ID of all deployments the "
            + "user should be part of. unless this field is left empty, whatever is present here will be updated to"
            + " user details.incase there are old deployments assigned to user then please include them too, else "
            + "they will be replaced with new values.User will be selected based on user_id field mentioned above.",
        ),
    ],
    outputs_prefix="SafeBreach.User",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.User",
            output_type=int,
            description="The ID of User whose data has been updated.",
        ),
        OutputArgument(
            name="name",
            prefix="SafeBreach.User",
            output_type=str,
            description="The name of User after running the update command according to safebreach records.",
        ),
        OutputArgument(
            name="email",
            prefix="SafeBreach.User",
            output_type=str,
            description="the email of the user whose data has been updated by the command.",
        ),
        OutputArgument(
            name="createdAt",
            prefix="SafeBreach.User",
            output_type=str,
            description="the time at which the user who has been selected has been created",
        ),
        OutputArgument(
            name="updatedAt",
            prefix="SafeBreach.User",
            output_type=str,
            description="The last updated time of User selected for update. \
                      this will be the execution time for the command or close to it.",
        ),
        OutputArgument(
            name="deletedAt",
            prefix="SafeBreach.User",
            output_type=str,
            description="The Deletion time of User selected to update. Generally this is empty unless\
                      user chosen to update is a deleted user",
        ),
        OutputArgument(
            name="roles",
            prefix="SafeBreach.User",
            output_type=str,
            description="The roles of User updated. these will change if role has been updated during\
                      updating user details else they will be same as pre update.",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.User",
            output_type=str,
            description="The description of User after updating user, if description field has been given any\
                      new value during update then its updated else this will be left unchanged from previous value.",
        ),
        OutputArgument(
            name="role",
            prefix="SafeBreach.User",
            output_type=str,
            description="The roles and permissions related to user who has been selected for update.unless this field\
                      has been given a value , this will not be updated and will stay the same as previous value.",
        ),
        OutputArgument(
            name="deployments",
            prefix="SafeBreach.User",
            output_type=str,
            description="The deployments related to user, this will be comma separated values of deployment IDs",
        ),
    ],
    description="This command updates a user with given data.",
)
def update_user_with_details(client: Client):
    """
        This function is executed when 'safebreach-update-user' command is being executed.
        This function will call another function which receives inputs from user and creates payload for upload user.

    Args:
        client (Client): Client class for API call

    Returns:
        CommandResults,dict: This function returns updated user in form of table and dictionary
    """
    updated_user = client.update_user_data()
    demisto.debug(f"Update user result is: {updated_user}")
    if updated_user.get("data", {}):
        human_readable = tableToMarkdown(
            name="Updated User Data",
            t=updated_user.get("data", {}),
            headers=[
                "id",
                "name",
                "email",
                "deletedAt",
                "roles",
                "description",
                "role",
                "deployments",
                "createdAt",
                "updatedAt",
            ],
        )
    else:
        human_readable = f"Unable to update user: {updated_user}"
    outputs = updated_user.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.User", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-delete-user",
    inputs_list=[
        InputArgument(
            name="user_id",
            required=True,
            is_array=False,
            description="ID of user to be deleted. The Id can be retrieved by using get-all-users command.",
        ),
    ],
    outputs_prefix="SafeBreach.User",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.User",
            output_type=int,
            description="The ID of User whose data has been deleted.",
        ),
        OutputArgument(
            name="name",
            prefix="SafeBreach.User",
            output_type=str,
            description="The name of User deleted.",
        ),
        OutputArgument(name="email", description="The email of User deleted.", prefix="SafeBreach.User", output_type=str),
        OutputArgument(
            name="createdAt",
            prefix="SafeBreach.User",
            output_type=str,
            description="the time at which the user who has been selected has been created",
        ),
        OutputArgument(name="updatedAt", prefix="SafeBreach.User", output_type=str, description="last updated time."),
        OutputArgument(name="deletedAt", prefix="SafeBreach.User", output_type=str, description="Deletion time of user."),
        OutputArgument(
            name="roles",
            description="The roles of User before they were deleted.",
            prefix="SafeBreach.User",
            output_type=str,
        ),
        OutputArgument(
            name="description",
            description="The description of User who has been deleted.",
            prefix="SafeBreach.User",
            output_type=str,
        ),
        OutputArgument(
            name="role",
            description="The roles and permissions of User who has been deleted.",
            prefix="SafeBreach.User",
            output_type=str,
        ),
        OutputArgument(
            name="deployments",
            description="The deployments related to user before he was deleted.",
            prefix="SafeBreach.User",
            output_type=str,
        ),
    ],
    description="This command deletes a user with given data.",
)
def delete_user_with_details(client: Client):
    """
        This function deletes user with given details, The inputs are being received in function which this function calls.
        It returns deleted user details

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: This is details of user that has been deleted
    """
    deleted_user = client.delete_user()
    demisto.debug(f"Delete user result is: {deleted_user}")

    if deleted_user.get("data", {}):
        human_readable = tableToMarkdown(
            name="Deleted User Data",
            t=deleted_user.get("data", {}),
            headers=["id", "name", "email", "deletedAt", "roles", "description", "role", "deployments", "createdAt"],
        )
    else:
        human_readable = f"Unable to delete user: {deleted_user}"
    outputs = deleted_user.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.User", outputs=outputs, readable_output=human_readable)
    return result


def deployment_transformer(header):
    return_map = {
        "id": "id",
        "accountId": "accountId",
        "name": "name",
        "createdAt": "createdAt",
        "description": "description",
        "nodes": "simulators",
        "updatedAt": "updatedAt",
    }

    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-list-deployments",
    inputs_list=None,
    outputs_prefix="SafeBreach.Deployment",
    outputs_list=[
        OutputArgument(name="id", prefix="SafeBreach.Deployment", output_type=int, description="The ID of deployment"),
        OutputArgument(
            name="account_id",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The accountId of user who created the deployment.",
        ),
        OutputArgument(
            name="name",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The name of deployment.\
                        this will be the name shown in deployment name field of table in deployments page in safebreach UI",
        ),
        OutputArgument(
            name="created_at",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The creation date and time of deployment.",
        ),
        OutputArgument(
            name="updated_at",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The last updated date and time of deployment.",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="This is description field of deployments table of safebreach UI",
        ),
        OutputArgument(
            name="simulators",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The simulators that are part of deployment.",
        ),
    ],
    description="This command gets all deployments present for this instance.",
)
def get_deployments(client: Client):
    """
        This function is executed on command "safebreach-list-deployments"

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict:Deployment data as a table and a dictionary
    """
    deployments = client.list_deployments()
    demisto.debug(f"Get deployment list result is: {deployments}")

    if deployments:
        human_readable = tableToMarkdown(
            name="Deployments",
            t=deployments,
            headerTransform=deployment_transformer,
            headers=["id", "accountId", "name", "createdAt", "description", "nodes", "updatedAt"],
        )
    else:
        human_readable = f"No deployments found: {deployments}"
    outputs = deployments
    result = CommandResults(outputs_prefix="SafeBreach.Deployment", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-create-deployment",
    inputs_list=[
        InputArgument(
            name="name",
            required=True,
            is_array=False,
            description="Name of the deployment to create. this will " + "be shown as name in deployments page of safebreach",
        ),
        InputArgument(
            name="description",
            required=False,
            is_array=False,
            description="Description of the deployment to create. This will show as description of the deployment "
            + "in your safebreach instance. It is generally preferable to give description while creating a deployment "
            + "for easier identification",
        ),
        InputArgument(
            name="simulators",
            required=False,
            is_array=True,
            description="Deployment manages multiple simulators as "
            + "single group. This parameter receives a comma separated list of IDs of all simulators that should be "
            + "part of this deployment Simulator ID can be retrieved from safebreach-get-all-simulator-details .",
        ),
    ],
    outputs_prefix="SafeBreach.Deployment",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.Deployment",
            output_type=int,
            description="The ID of deployment created. this Id can be used to update ,delete deployment as\
                      deployment_id field of the deployment.",
        ),
        OutputArgument(
            name="account_id",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="This field shows account ID of user who has created the account.",
        ),
        OutputArgument(
            name="name",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The name of deployment created. this will be name which will be shown on deployments page\
                      of safebreach and name that is given as input to the command.",
        ),
        OutputArgument(
            name="created_at",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The creation date and time of deployment , this will be closer to\
                      command execution time if the deployment creation is successful.",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The description of the deployment created will be shown in description \
                          part of the table in safebreach.",
        ),
        OutputArgument(
            name="simulators",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The simulators that are part of deployment.",
        ),
    ],
    description="This command creates a deployment, grouping the list of simulators provided with"
    + " a name and optionally a description.",
)
def create_deployment(client: Client):
    """
        This function is executed on command "safebreach-create-deployment"

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: Created deployment data as a table and a dictionary
    """
    created_deployment = client.create_deployment_data()
    demisto.debug(f"Create deployment result is: {created_deployment}")

    if created_deployment:
        human_readable = tableToMarkdown(
            name="Created Deployment",
            t=created_deployment.get("data", {}),
            headerTransform=deployment_transformer,
            headers=["id", "accountId", "name", "createdAt", "description", "nodes"],
        )
    else:
        human_readable = f"Unable to create deployment: {created_deployment}"
    outputs = created_deployment.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.Deployment", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-update-deployment",
    inputs_list=[
        InputArgument(
            name="deployment_id",
            required=True,
            is_array=False,
            description="ID of the deployment to update. " + "Can be searched with list-deployments command.",
        ),
        InputArgument(
            name="updated_simulators_for_deployment",
            required=False,
            is_array=False,
            description="Comma separated ID of all simulators to be part of the deployment Simulators can be "
            + " retrieved by calling get-all-available-simulator-details command",
        ),
        InputArgument(name="updated_deployment_name", required=False, is_array=False, description="Deployment name"),
        InputArgument(
            name="updated_deployment_description", required=False, is_array=False, description="Deployment description."
        ),
    ],
    outputs_prefix="SafeBreach.Deployment",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.Deployment",
            output_type=int,
            description="The ID of deployment whose values have been updated.\
                          ID cant be changed so this wont be updated.",
        ),
        OutputArgument(
            name="account_id",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The accountId of user who created the deployment.",
        ),
        OutputArgument(
            name="name",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The name of deployment which has been updated to the name given in updated_deployment_name.\
                        this will be the name shown in deployment name field of table in deployments page in safebreach UI",
        ),
        OutputArgument(
            name="created_at",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The creation date and time of deployment whose data has been updated.",
        ),
        OutputArgument(
            name="updated_at",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The last updated date and time of deployment whose data has been updated.\
                      This will generally be closer to the update deployment command run time for reference",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The updated description of deployment which is provided in updated_deployment_description\
                      field of input . This will now be the description which is shown in description field of deployments\
                      table of safebreach UI",
        ),
        OutputArgument(
            name="simulators",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The simulators that are part of deployment. unless any simulators are given as input this \
                          field won't be updated this field doesn't reflect changes if simulators given as input are deleted",
        ),
    ],
    description="This command updates a deployment with given data. The deployment_id field of this command can be retrieved "
    + "from 'safebreach-list-deployments' command. If the user wants to search with deployment ID then they can search it ",
)
def update_deployment(client: Client):
    """
        This function is executed on command "safebreach-update-deployment"

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: updated deployment data as a table and a dictionary
    """
    updated_deployment = client.update_deployment()
    demisto.debug(f"Update deployment result is: {updated_deployment}")

    if updated_deployment:
        human_readable = tableToMarkdown(
            name="Updated Deployment",
            t=updated_deployment.get("data", {}),
            headerTransform=deployment_transformer,
            headers=["id", "accountId", "name", "createdAt", "description", "nodes", "updatedAt"],
        )
    else:
        human_readable = f"Unable to update deployment: {updated_deployment}"
    outputs = updated_deployment.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.Deployment", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-delete-deployment",
    inputs_list=[
        InputArgument(
            name="deployment_id",
            required=True,
            is_array=False,
            description="ID of the deployment to delete. " + "The ID his can be searched with list-deployments command",
        )
    ],
    outputs_prefix="SafeBreach.Deployment",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.Deployment",
            output_type=int,
            description="The ID of deployment which has been deleted.",
        ),
        OutputArgument(
            name="account_id",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The account Id of user who deleted the deployment.",
        ),
        OutputArgument(
            name="name",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The name of deployment before the deployment was deleted.",
        ),
        OutputArgument(
            name="created_at",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The creation date and time of deployment which has been deleted.",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The description of deployment before it was deleted.",
        ),
        OutputArgument(
            name="simulators",
            prefix="SafeBreach.Deployment",
            output_type=str,
            description="The simulators that are part of deployment before it was deleted.",
        ),
    ],
    description="This command deletes a deployment with the deployment_id (retrieved using the get-all-deployments command).",
)
def delete_deployment(client: Client):
    """
        This function is executed on command "safebreach-delete-deployment"

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: deleted deployment data as a table and a dictionary
    """
    deleted_deployment = client.delete_deployment()
    demisto.debug(f"Delete deployment result is: {deleted_deployment}")

    if deleted_deployment:
        human_readable = tableToMarkdown(
            name="Deleted Deployment",
            t=deleted_deployment.get("data", {}),
            headerTransform=deployment_transformer,
            headers=["id", "accountId", "name", "createdAt", "description", "nodes", "updatedAt"],
        )
    else:
        human_readable = f"Unable to delete deployment: {deleted_deployment}"
    outputs = deleted_deployment.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.Deployment", outputs=outputs, readable_output=human_readable)
    return result


def apikey_transformer(header):
    return_map = {
        "name": "name",
        "description": "description",
        "createdAt": "created_at",
        "createdBy": "created_by",
        "deletedBy": "deleted_by",
        "updatedBy": "updated_by",
        "key": "key",
    }

    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-generate-api-key",
    inputs_list=[
        InputArgument(
            name="name",
            required=True,
            is_array=False,
            description="""
                      Name of the API Key to create. This will be the name shown in UI for API key under API keys section
                      """,
        ),
        InputArgument(
            name="description",
            required=False,
            is_array=False,
            description="""
                      Description of the API Key to create. This is not a required field but it is recommended to store a
                      description for easier identification if your use case requires using multiple API keys for multiple tasks.
                      """,
        ),
    ],
    outputs_prefix="SafeBreach.API",
    outputs_list=[
        OutputArgument(
            name="name",
            prefix="SafeBreach.API",
            output_type=str,
            description="The Name of API Key generated through this command, \
                          This will match the input name of the command.",
        ),
        OutputArgument(
            name="description",
            prefix="SafeBreach.API",
            output_type=str,
            description="The Description of API Key created. \
                          this will be same as input description given for the command.",
        ),
        OutputArgument(
            name="created_by",
            prefix="SafeBreach.API",
            output_type=str,
            description="The id of user who generated this API key.",
        ),
        OutputArgument(
            name="created_bt",
            prefix="SafeBreach.API",
            output_type=str,
            description="The creation date and time of API key.",
        ),
        OutputArgument(
            name="key",
            prefix="SafeBreach.API",
            output_type=str,
            description="The value of API key generated. store this for further use as this will only be shown once",
        ),
    ],
    description="This command creates an API key with the name and optionally the description provided. The API key created "
    + "will be shown on the Settings > API Keys page of SafeBreach Management. Important: The API key generated can be seen "
    + "only once, so it is recommended to store/save it in a safe place for further use.",
)
def create_api_key(client: Client):
    """
        This function generates API key and returns API key, Executed for command 'safebreach-generate-api-key'

    Args:
        client (Client): Client class for API call

    Returns:
        CommandResults,dict: Command results for generated API key details table and dict containing data
    """
    generated_api_key = client.generate_api_key()
    demisto.debug(f"Generated API key is: {generated_api_key}")
    if generated_api_key:
        human_readable = tableToMarkdown(
            name="Generated API key Data",
            headerTransform=apikey_transformer,
            t=generated_api_key.get("data"),
            headers=["name", "description", "createdBy", "createdAt", "key"],
        )
    else:
        human_readable = f"Unable to generate API key: {generated_api_key}"
    outputs = generated_api_key.get("data")

    result = CommandResults(outputs_prefix="SafeBreach.API", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-delete-api-key",
    inputs_list=[
        InputArgument(
            name="key_name",
            required=True,
            is_array=False,
            description="Name of the API Key to Delete. This will be "
            + "used for searching key with given name and then once it matches, that API key will be deleted.",
        ),
    ],
    outputs_prefix="SafeBreach.API",
    outputs_list=[
        OutputArgument(name="name", description="The Name of API Key deleted.", prefix="SafeBreach.API", output_type=int),
        OutputArgument(
            name="description", description="Description of API Key deleted.", prefix="SafeBreach.API", output_type=str
        ),
        OutputArgument(
            name="created_by",
            description="The id of user who generated this API key.",
            prefix="SafeBreach.API",
            output_type=str,
        ),
        OutputArgument(
            name="created_at",
            description="The creation time and date of API key.",
            prefix="SafeBreach.API",
            output_type=str,
        ),
        OutputArgument(
            name="deleted_at",
            prefix="SafeBreach.API",
            output_type=str,
            description="The deletion time and date of API key. The deletion date and time are generally\
                      close to the command execution time and date.",
        ),
    ],
    description="This command deletes the API key with the name as specified in SafeBreach Management. It is not case sensitive.",
)
def delete_api_key(client: Client):
    """
        This function deletes API key and returns API key, Executed for command 'safebreach-delete-api-key'

    Args:
        client (Client): Client class for API call

    Returns:
        CommandResults,dict: Command results for deleted API key details table and dict containing data
    """
    deleted_api_key = client.delete_api_key()
    demisto.debug(f"Delete API key is: {deleted_api_key}")
    if deleted_api_key.get("data"):
        human_readable = tableToMarkdown(
            name="Deleted API key Data",
            t=deleted_api_key.get("data"),
            headerTransform=apikey_transformer,
            headers=["name", "description", "createdBy", "createdAt", "deletedAt"],
        )
    else:
        human_readable = f"Unable to delete API key: {deleted_api_key}"
    outputs = deleted_api_key.get("data")
    result = CommandResults(outputs_prefix="SafeBreach.API", outputs=outputs, readable_output=human_readable)
    return result


def integration_issues_transformer(header):
    return_map = {
        "connector": "integration_id",
        "action": "action",
        "success": "success_state",
        "error": "error_description",
        "timestamp": "timestamp",
    }
    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-get-integration-issues",
    inputs_list=[
        InputArgument(
            name="error_type",
            required=False,
            is_array=False,
            options=["", "ERROR", "WARNING"],
            description="this will help see issues which are either errors or warnings or both based on the input ",
        ),
    ],
    outputs_prefix="SafeBreach.Integration",
    outputs_list=[
        OutputArgument(
            name="integration_id",
            prefix="SafeBreach.Integration",
            output_type=int,
            description="The ID of Integration. A general notation that has been followed here is\
                      as follows, if the  id has _default at the end then its a default connector else its a custom connector",
        ),
        OutputArgument(
            name="integration_name",
            prefix="SafeBreach.Integration",
            output_type=str,
            description="Name of the integration",
        ),
        OutputArgument(
            name="action",
            prefix="SafeBreach.Integration",
            output_type=str,
            description="The action of Integration error. This describes where exactly did the error occur,\
                        if its search,then it implies error/warning happened when connector was trying that process",
        ),
        OutputArgument(
            name="success_state",
            prefix="SafeBreach.Integration",
            output_type=str,
            description="status of integration error. This implies whether the connector was able to \
                      successfully perform the operation or if it failed partway. \
                      So false implies it failed partway and true implies it was successfully completed",
        ),
        OutputArgument(
            name="error_description",
            prefix="SafeBreach.Integration",
            output_type=str,
            description="This is the exact error description shown on safebreach integration error/warning page.\
                        This description can be used for understanding of what exactly happened for the integration to fail.",
        ),
        OutputArgument(
            name="timestamp",
            prefix="SafeBreach.Integration",
            output_type=str,
            description="Time at which error/warning occurred. This can be used to pinpoint error which occurred\
                      across integrations if time of origin was remembered",
        ),
    ],
    description="This command gives all integrations related issues and warning. this will show the integrations error and"
    + " warnings which are generally displayed in installed integrations page.",
)
def get_all_integration_error_logs(client: Client):
    """
        This function retrieves all error logs and shows them in form of table

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,Dict: This function returns all errors along with integration details in a table and we get data as json
    """
    formatted_error_logs = []
    error_logs = client.get_all_integration_error_logs()
    if error_logs is None:
        raise ValueError("Failed to retrieve integration error logs")
    demisto.debug(f"Get all integration error logs result is: {error_logs}")

    formatted_error_logs = client.flatten_error_logs_for_table_view(error_logs.get("result"))
    if formatted_error_logs:
        human_readable = tableToMarkdown(
            name="Integration errors",
            headerTransform=integration_issues_transformer,
            t=formatted_error_logs,
            headers=["connector", "action", "success", "error", "timestamp", "integration_name"],
        )
    else:
        human_readable = f"No integration {demisto.args().get('error_type')} logs found: {formatted_error_logs}"
    outputs = error_logs.get("result")
    result = CommandResults(outputs_prefix="SafeBreach.Integration", outputs=outputs, readable_output=human_readable)
    return result


def get_integration_details(client: Client):
    connector_details = client.get_integration_details()
    demisto.debug(f"Get integration details result is: {connector_details}")

    installed_connectors = connector_details.get("result", {}).get("providers", [])
    connectors_map = {}
    for connector in installed_connectors:
        connectors_map[connector["id"]] = connector["name"]

    return connectors_map


@metadata_collector.command(
    command_name="safebreach-clear-integration-issues",
    inputs_list=[
        InputArgument(
            name="integration_id",
            required=True,
            is_array=False,
            description="The ID of Integration to have its errors/warnings deleted. "
            + "Both errors and warnings will be deleted.",
        ),
    ],
    outputs_prefix="SafeBreach.Integration",
    outputs_list=[
        OutputArgument(
            name="error",
            description="Error count after deletion of errors for the given Integration.",
            prefix="SafeBreach.Integration",
            output_type=int,
        ),
        OutputArgument(
            name="result",
            description="error deletion status whether true or false.",
            prefix="SafeBreach.Integration",
            output_type=str,
        ),
    ],
    description="This command deletes connector-related errors and warnings for the specified connector_id"
    + " (retrieved using the get-all-integration-issues command).",
)
def delete_integration_error_logs(client: Client):
    """
        This function deletes integration errors of a given integration

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,Dict: This returns a table of data showing deleted details and dict showing same in outputs
    """
    error_logs = client.delete_integration_error_logs()
    demisto.debug(f"Delete integration error logs result is: {error_logs}")
    headers = ["result", "error"]
    if error_logs.get("errorMessage"):
        headers = ["error", "errorMessage"]
    if error_logs:
        human_readable = tableToMarkdown(name="Integration errors status", t=error_logs, headers=headers)
    else:
        human_readable = f"Unable to delete integration error logs: {error_logs}"
    outputs = error_logs
    result = CommandResults(outputs_prefix="SafeBreach.Integration", outputs=outputs, readable_output=human_readable)
    return result


def simulator_count_transformer(header):
    return_map = {
        "contactName": "contact_name",
        "contactEmail": "contact_email",
        "userQuota": "user_quota",
        "nodesQuota": "simulator_quota",
        "registrationDate": "registration_date",
        "activationDate": "activation_date",
        "expirationDate": "expiration_date",
    }
    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-get-available-simulator-count",
    inputs_list=None,
    outputs_prefix="SafeBreach.Account",
    outputs_list=[
        OutputArgument(
            name="id",
            prefix="SafeBreach.Account",
            output_type=int,
            description="The account ID which is being used by integration.",
        ),
        OutputArgument(
            name="name",
            description="The Account Name of account being queried.",
            prefix="SafeBreach.Account",
            output_type=str,
        ),
        OutputArgument(
            name="contact_name",
            description="Contact name for given account.",
            prefix="SafeBreach.Account",
            output_type=str,
        ),
        OutputArgument(
            name="contact_email", description="Email of the contact person.", prefix="SafeBreach.Account", output_type=str
        ),
        OutputArgument(
            name="user_quota",
            prefix="SafeBreach.Account",
            output_type=str,
            description="User Quota for the given account, maximum users which are allowed for the account.",
        ),
        OutputArgument(
            name="simulators_quota",
            prefix="SafeBreach.Account",
            output_type=int,
            description="The simulator quota for the given account. The maximum number of "
            + "simulators which are available for the account.",
        ),
        OutputArgument(
            name="registration_date",
            description="The registration date of given account.",
            prefix="SafeBreach.Account",
            output_type=int,
        ),
        OutputArgument(
            name="activation_date",
            description="The Activation date of given account.",
            prefix="SafeBreach.Account",
            output_type=str,
        ),
        OutputArgument(
            name="expiration_date", description="Account expiration date.", prefix="SafeBreach.Account", output_type=str
        ),
    ],
    description="This command gives all details related to account, we are using this to find assigned simulator quota.",
)
def get_simulator_quota_with_table(client: Client):
    """
        This will be used to show account simulator quota and details in table

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,dict: this shows a table with account details and a dict with account details
    """
    simulator_details = client.get_simulator_quota()
    demisto.debug(f"Simulator details result is: {simulator_details}")
    if simulator_details.get("data"):
        human_readable = tableToMarkdown(
            name="Account Details",
            t=simulator_details.get("data"),
            headerTransform=simulator_count_transformer,
            headers=[
                "id",
                "name",
                "contactName",
                "contactEmail",
                "userQuota",
                "nodesQuota",
                "registrationDate",
                "activationDate",
                "expirationDate",
            ],
        )
    else:
        human_readable = f"Unable to get simulator quota and details: {simulator_details}"
    outputs = {
        "account_details": simulator_details.get("data"),
        "simulator_quota": simulator_details.get("data").get("nodesQuota"),
    }
    simulator_details = CommandResults(outputs_prefix="SafeBreach.Account", outputs=outputs, readable_output=human_readable)
    return simulator_details


@metadata_collector.command(
    command_name="safebreach-get-available-simulator-details",
    inputs_list=simulator_details_inputs,
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=simulators_output_fields,
    description="This command to get all available simulators. if details is set to true then it retrieves simulator details "
    + "like name, hostname, internal and external ips, types of targets and attacker configurations this simulator is associated "
    + "with etc. if its set to false then it retrieves just name, id, simulation users, proxies etc. if deleted is set to true "
    + "then it retrieves the data which has been deleted.",
)
def get_all_simulator_details(client: Client):
    """
        This function returns simulator details of all simulators

    Args:
        client (Client): Client class for API calls

    Returns:
        List(dict): This is list of all simulators data
    """
    return get_simulators_and_display_in_table(client=client)


@metadata_collector.command(
    command_name="safebreach-get-simulator-with-id",
    inputs_list=[InputArgument(name="simulator_id", required=True, is_array=False, description="simulator id")],
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=simulators_output_fields,
    description="This command gives simulator with given id",
)
def get_simulator_with_name(client: Client):
    """
        this function returns simulator with given name as  table and dict

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,data: This is data of simulator with given name
    """
    return get_specific_simulator_details(client=client)


@metadata_collector.command(
    command_name="safebreach-delete-simulator",
    inputs_list=[
        InputArgument(name="simulator_id", required=True, is_array=False, description="Id of the simulator we want to delete")
    ],
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=simulators_output_fields,
    description="The provided command facilitates the deletion of a simulator identified by its unique ID."
    + 'To obtain the respective simulator ID, execute the "safebreach-get-all-simulators" command.',
)
def delete_simulator_with_given_name(client: Client):
    """
        This function deletes simulator with given id

    Args:
        client (Client): This is client class for API calls

    Returns:
        CommandResults,Dict: this is for table showing deleted simulator data and dict with data
    """
    deleted_simulator = client.delete_simulator_with_given_name()
    demisto.debug(f"Delete simulator with given name result is: {deleted_simulator}")
    flattened_simulators, keys = client.flatten_simulator_details([deleted_simulator.get("data", {})])
    if flattened_simulators:
        human_readable = tableToMarkdown(name="Deleted Simulators Details", t=flattened_simulators, headers=keys)
    else:
        human_readable = f"Unable to delete simulator with given ID: {flattened_simulators}"
    outputs = deleted_simulator.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.Simulator", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-update-simulator",
    inputs_list=[InputArgument(name="simulator_id", required=True, is_array=False, description="Simulator ID.")]
    + simulator_details_for_update_fields,
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=simulators_output_fields,
    description="This command updates simulator with given id. the given inputs for update fields will be "
    + "updated to the selected filed values will be updated to given value.",
)
def update_simulator(client: Client):
    """
        This function updates simulator with given data having name as given input

    Args:
        client (Client): This is client class for API calls

    Returns:
        CommandResults,Dict: This will return table and dict containing updated simulator data
    """
    updated_simulator = client.update_simulator()
    demisto.debug(f"Update simulator result is: {updated_simulator}")
    flattened_simulators, keys = client.flatten_simulator_details([updated_simulator.get("data", {})])
    if flattened_simulators:
        human_readable = tableToMarkdown(name="Updated Simulators Details", t=flattened_simulators, headers=keys)
    else:
        human_readable = f"Unable to update simulator: {flattened_simulators}"
    outputs = updated_simulator.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.Simulator", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-approve-simulator",
    inputs_list=[
        InputArgument(
            name="simulator_id",
            required=True,
            is_array=False,
            description="ID of simulator to approve, in case "
            + "unsure then please call safebreach-get-all-simulators and search for simulator name.",
        )
    ],
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=simulators_output_fields,
    description="This command approves the simulator with the specified simulator_id.",
)
def approve_simulator(client: Client):
    """
        This function approves simulator with given data having name as given input

    Args:
        client (Client): This is client class for API calls

    Returns:
        CommandResults,Dict: This will return table and dict containing approved simulator data
    """
    approved_simulator = client.approve_simulator()
    demisto.debug(f"Approve simulator result is: {approved_simulator}")
    flattened_simulators, keys = client.flatten_simulator_details([approved_simulator.get("data", {})])
    if flattened_simulators:
        human_readable = tableToMarkdown(name="Approved Simulators Details", t=flattened_simulators, headers=keys)
    else:
        human_readable = f"Unable to approve simulator: {flattened_simulators}"
    outputs = approved_simulator.get("data", {})
    result = CommandResults(outputs_prefix="SafeBreach.Simulator", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-rotate-verification-token",
    inputs_list=None,
    outputs_prefix="SafeBreach.Token",
    outputs_list=[
        OutputArgument(name="new_token", output_type=str, description="New token which has been generated due to the API call"),
    ],
    description="This command rotates generated verification token meaning it creates a new token which will be used for "
    + "verification of simulator and adding the simulator.",
)
def return_rotated_verification_token(client: Client):
    """
        This function is called when rotate-verification-token command is called and will
        help with calling API of rotate verification token
    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,Dict: This returns a table showing new token and a dict as output stating same
    """
    new_token = client.rotate_verification_token()
    if new_token.get("data"):
        human_readable = tableToMarkdown(name="New Token Details", t=new_token.get("data"), headers=["secret"])
    else:
        human_readable = f"Unable to rotate verification token: {new_token}"
    outputs = new_token.get("data", {}).get("secret", "")
    result = CommandResults(outputs_prefix="SafeBreach.Token", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-get-tests",
    inputs_list=None,
    outputs_prefix="SafeBreach.Test",
    outputs_list=test_summaries_output_fields,
    description="This command gets tests with given modifiers.",
)
def get_all_tests_summary(client: Client):
    """
        This function gets all tests summary and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns all tests related summary as a table and gives a dictionary as outputs for the same
    """
    return get_tests_summary(client=client)


@metadata_collector.command(
    command_name="safebreach-get-tests-with-scenario-id",
    inputs_list=[
        InputArgument(
            name="scenario_id",
            required=True,
            is_array=False,
            description="Scenario Id for test which has to be filtered. this can be found on UI, if unsure about this "
            + "then please run safebreach-get-tests instead of this with same parameters as inputs.",
        )
    ],
    outputs_prefix="SafeBreach.Test",
    outputs_list=test_summaries_output_fields,
    description="This command gets tests with given scenario ID as part of it.",
)
def get_all_tests_summary_with_scenario_id(client: Client):
    """
        This function takes scenario ID and returns test summaries with that scenario ID

    Args:
        client (Client): Client class for API calls

    Returns:
        CommandResults,List(dict): This will return  a table with all details and
        a list of dictionaries with details related to tests with given scenario ID
    """
    return get_tests_summary(client=client)


@metadata_collector.command(
    command_name="safebreach-delete-test-with-id",
    inputs_list=[
        InputArgument(name="test_id", description="Id of test to be deleted.", required=True, is_array=False),
    ],
    outputs_prefix="SafeBreach.Test",
    outputs_list=test_summaries_output_fields,
    description="This command deletes tests with given test ID.",
)
def delete_test_result_of_test(client: Client):
    """
        This function deletes test with given Test ID

    Args:
        client (Client): Client class for API call

    Returns:
        CommandResults,Dict: A table showing deletion results and a dict of outputs showing the same
    """
    test_summaries = client.delete_test_result_of_test()
    demisto.debug(f"Delete test with ID result is: {test_summaries}")
    if test_summaries.get("data"):
        human_readable = tableToMarkdown(name="Deleted Test", t=test_summaries.get("data", {}), headers=["id"])
    else:
        human_readable = f"Unable to delete test result with given test Id: {test_summaries}"
    outputs = [test_summaries.get("data")]
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-get-running-tests",
    inputs_list=None,
    outputs_prefix="SafeBreach.Test",
    outputs_list=tests_outputs,
    description="This command gets tests which are in running state.",
)
def get_all_running_tests_summary(client: Client):
    """
        This function gets all running tests summary and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns all tests related summary as a table and gives a dictionary as outputs for the same
    """
    running_tests = client.get_active_tests()
    demisto.debug(f"Get all running tests summary result is: {running_tests}")

    if isinstance(running_tests, str):
        return_error(f"API returned error: {running_tests}")
    elif not isinstance(running_tests, dict):
        return_error(f"Unexpected API response type: {type(running_tests)}")

    data = running_tests.get("data", {})
    if not isinstance(data, dict):
        return_error(f"Invalid 'data' field in response: {type(data)}")

    # API returns: {"data": {"queue": [], "testRunState": {"planRunId1": {...}, "planRunId2": {...}}}}
    test_run_state = data.get("testRunState", {})
    tests_list = list(test_run_state.values()) if isinstance(test_run_state, dict) else []

    queued_tests = data.get("queue", [])
    if isinstance(queued_tests, list):
        tests_list.extend(queued_tests)

    flattened_running_tests_for_table = client.flatten_tests_data(tests_list)
    if flattened_running_tests_for_table:
        human_readable = tableToMarkdown(
            name="Running Tests",
            t=flattened_running_tests_for_table,
            headerTransform=test_outputs_headers_transform,
            headers=test_outputs_headers_list,
        )
    else:
        human_readable = f"No running tests summary found: {flattened_running_tests_for_table}"
    outputs = running_tests
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs, readable_output=human_readable)

    return result


def simulations_output_transform(header):
    return_map = {
        "jobId": "simulation_id",
        "moveId": "attack_id",
        "timestamp": "timestamp",
        "planRunId": "test_id",
        "simulator_ids_involved": "simulator_ids_involved",
        "simulator_names_involved": "simulator_names_involved",
    }
    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-get-running-simulations",
    inputs_list=None,
    outputs_prefix="SafeBreach.Test",
    outputs_list=[
        OutputArgument(
            name="test id", description="this is test ID of the simulation.", prefix="SafeBreach.Test", output_type=str
        ),
        OutputArgument(
            name="simulation_id",
            description="the simulation id of the simulation.",
            prefix="SafeBreach.Test",
            output_type=str,
        ),
        OutputArgument(
            name="attack_id", description="the attack ID of the simulation.", prefix="SafeBreach.Test", output_type=str
        ),
    ],
    description="This command gets simulations which are in running or queued state.",
)
def get_all_running_simulations_summary(client: Client):
    """
        This function gets all running simulations summary and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns all simulations related summary as table and gives a dictionary as outputs
    """
    running_simulations = client.get_active_simulations()
    flattened_simulations_data_for_table = client.flatten_simulations_data(running_simulations.get("data", {}))
    demisto.debug(f"Get all running simulations summary result is: {running_simulations}")
    if flattened_simulations_data_for_table:
        human_readable = tableToMarkdown(
            name="Running Simulations",
            t=flattened_simulations_data_for_table,
            headerTransform=simulations_output_transform,
            headers=[
                # "timestamp",
                "planRunId",
                "jobId",
                "moveId",
            ],
        )
    else:
        human_readable = f"No running simulations summary found: {flattened_simulations_data_for_table}"
    outputs = running_simulations
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-pause/resume-simulations-tests",
    inputs_list=[
        InputArgument(
            name="simulation_or_test_state",
            options=["resume", "pause"],
            required=True,
            is_array=False,
            description="State of tests/simulators to set to:\n"
            + "1. pause will set all simulations/tests which are in queue/running to paused stated and resume all "
            + "will be the state of button in running simulations page. \n"
            + "2. resume will queue all simulations/tests and will set them to running/queued depending on priority. \n"
            + "Note that this doe not affect the schedules and scheduled tasks unless they are running or active at the"
            + " moment of execution of the command.",
        )
    ],
    outputs_prefix="SafeBreach.Test",
    outputs_list=[
        OutputArgument(
            name="status",
            prefix="SafeBreach.Test",
            output_type=str,
            description="the status of the simulations/tests.",
        )
    ],
    description="This command gets simulations/tests which are in running or queued state and pauses/resumes them based on "
    + "input selected. The state selected will be applied for all running/queued state tasks whether they are simulations/tests.",
)
def pause_resume_tests_and_simulations(client: Client):
    """
        This function gets all tests/simulations summary and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns all tests related summary as a table and gives a dictionary as outputs for the same
    """
    simulations_status = client.set_simulations_status()
    if simulations_status.get("data"):
        human_readable = tableToMarkdown(name="Simulations/tests status", t=simulations_status.get("data"), headers=["status"])
    else:
        human_readable = f"No tests and simulations summary found: {simulations_status}"

    outputs = simulations_status
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs.get("data"), readable_output=human_readable)

    return result


def safebreach_schedules_transformer(header):
    return_map = {
        "id": "id",
        "isEnabled": "is_enabled",
        "name": "name",
        "user_schedule": "user_schedule",
        "runDate": "runDate",
        "cronTimezone": "cron_timezone",
        "taskId": "task_id",
        "description": "description",
        "planId": "scenario_id",
        "matrixId": "scenario_id",
        "createdAt": "created_at",
        "updatedAt": "updated_at",
        "deletedAt": "deleted_at",
    }

    return return_map.get(header, header)


def plan_id_name_map(test_summaries):
    plan_map = {}
    for summary in test_summaries:
        plan_map[str(summary["planId"])] = summary["planName"]

    return plan_map


@metadata_collector.command(
    command_name="safebreach-get-scheduled-scenarios",
    inputs_list=None,
    outputs_prefix="SafeBreach.Schedules",
    outputs_list=[
        OutputArgument(name="id", description="the Id of the schedule.", prefix="schedules", output_type=str),
        OutputArgument(name="is_enabled", description="if simulation is enabled.", prefix="schedules", output_type=bool),
        OutputArgument(
            name="user_schedule",
            description="the user readable form of the schedule.",
            prefix="SafeBreach.Schedules",
            output_type=str,
        ),
        OutputArgument(
            name="run_date", description="the run date of the schedule.", prefix="SafeBreach.Schedules", output_type=str
        ),
        OutputArgument(
            name="cron_timezone", description="the time zone of the schedule.", prefix="SafeBreach.Schedules", output_type=str
        ),
        OutputArgument(
            name="description", description="the description of the schedule.", prefix="SafeBreach.Schedules", output_type=str
        ),
        OutputArgument(
            name="scenario_id", description="the matrix ID of the schedule.", prefix="SafeBreach.Schedules", output_type=str
        ),
        OutputArgument(
            name="created_at",
            description="the creation datetime of the schedule.",
            prefix="SafeBreach.Schedules",
            output_type=str,  # noqa: E501
        ),
        OutputArgument(
            name="updated_at", description="the updated datetime of the schedule.", prefix="SafeBreach.Schedules", output_type=str
        ),
        OutputArgument(
            name="deleted_at", description="the deletion time of the schedule.", prefix="SafeBreach.Schedules", output_type=str
        ),
    ],
    description="This command retrieves schedules from safebreach which user has set and they will display it to user. By "
    + "default Name is not shown, to retrieve and see it, please run 'safebreach-get-custom-scenarios' command to find name of "
    + "scenario to which the schedule is associated with.",
)
def get_schedules(client: Client):
    """
        This function retrieves schedules and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns schedules as a table and gives a dictionary as outputs for the same
    """
    headers = [
        "id",
        "name",
        "isEnabled",
        "user_schedule",
        "runDate",
        "cronTimezone",
        "description",
        "planId",
        "createdAt",
        "updatedAt",
        "deletedAt",
    ]

    schedules_data = client.get_schedules()
    new_schedules_data = client.append_cron_to_schedule(deepcopy(schedules_data.get("data")))
    demisto.debug(f"Get schedules result is: {schedules_data}")
    if new_schedules_data:
        human_readable = tableToMarkdown(
            name="Schedules", headerTransform=safebreach_schedules_transformer, t=new_schedules_data, headers=headers
        )
    else:
        human_readable = f"No schedules found: {new_schedules_data}"
    outputs = schedules_data.get("data")
    result = CommandResults(outputs_prefix="SafeBreach.Schedules", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-delete-scheduled-scenarios",
    inputs_list=[
        InputArgument(
            name="schedule_id", description="schedule ID of scheduled scenario to delete", required=True, is_array=False
        )
    ],
    outputs_prefix="SafeBreach.Scenario",
    outputs_list=[
        OutputArgument(
            name="id",
            description="the Id of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="name",
            description="the name of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="accountId",
            description="the account ID of the scheduled scenario.",
            prefix="SafeBreach.Schedules",
            output_type=str,
        ),
        OutputArgument(
            name="description",
            description="the description of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="successCriteria",
            description="the success criteria of the scheduled scenario.",
            prefix="SafeBreach.Schedules",
            output_type=str,
        ),
        OutputArgument(
            name="originalScenarioId",
            description="the original test ID of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="systemFilter",
            description="the systemFilter of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="tags",
            description="the tags of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="createdAt",
            description="the creation datetime of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="updatedAt",
            description="the updated datetime of the scheduled scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
    ],
    description="This command deletes the scheduled scenario with the specified schedule_id.",
)
def delete_schedules(client: Client):
    """
        This function delete schedules and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns deleted schedules as a table and gives a dictionary as outputs for the same
    """

    schedules_data = client.delete_schedule()
    demisto.debug(f"Delete schedules result is: {schedules_data}")

    if schedules_data.get("data"):
        human_readable = tableToMarkdown("Deleted scheduled scenario:", schedules_data.get("data"))
    else:
        human_readable = f"Unable to delete schedules: {schedules_data.get('data')}"

    outputs = schedules_data.get("data")
    result = CommandResults(outputs_prefix="SafeBreach.Scenario", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-get-prebuilt-scenarios",
    inputs_list=None,
    outputs_prefix="SafeBreach.Scenario",
    outputs_list=[
        OutputArgument(name="id", description="the Id of scenario.", prefix="SafeBreach.Scenario", output_type=str),
        OutputArgument(name="name", description="he name of the scenario.", prefix="SafeBreach.Scenario", output_type=str),
        OutputArgument(
            name="description",
            description="the description of the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="created_by",
            description="user id of user, who created the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="created_at",
            description="creation datetime of scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="updated_at",
            description="the update datetime of the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="recommended",
            prefix="SafeBreach.Scenario",
            output_type=str,
            description="the recommendation status of the scenario.",
        ),
        OutputArgument(
            name="tags_list",
            description="the tags related to the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="categories",
            description="the category ids of the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="steps_order",
            description="the order of steps involved in the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="order",
            description="the order of execution related to the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="min_api_ver",
            description="the minimum version of API required for scenario to be executed",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
    ],
    description="This command gets scenarios which are built by safebreach. They will be available by default even in new "
    + "instance of your safebreach instance. They can be modified and saved as custom scenarios or used as it is.",
)
def get_prebuilt_scenarios(client: Client):
    """
        This function gets prebuilt scenarios and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns all tests related summary as a table and gives a dictionary as outputs for the same
    """
    prebuilt_scenarios = client.get_prebuilt_scenarios()
    demisto.debug(f"Get prebuilt scenarios result is: {prebuilt_scenarios}")

    flattened_simulations_data_for_table = client.extract_default_scenario_fields(prebuilt_scenarios)
    if flattened_simulations_data_for_table:
        human_readable = tableToMarkdown(
            name="Scenarios",
            headerTransform=scenarios_transformer,
            t=flattened_simulations_data_for_table,
            headers=[
                "id",
                "name",
                "description",
                "createdBy",
                "createdAt",
                "updatedAt",
                "recommended",
                "tags_list",
                "categories",
                "steps_order",
                "order",
                "minApiVer",
            ],
        )
    else:
        human_readable = f"No prebuilt scenarios found: {flattened_simulations_data_for_table}"
    outputs = prebuilt_scenarios
    result = CommandResults(outputs_prefix="SafeBreach.Scenario", outputs=outputs, readable_output=human_readable)

    return result


def scenarios_transformer(header):
    return_map = {
        "id": "id",
        "name": "name",
        "description": "description",
        "successCriteria": "success_criteria",
        "originalScenarioId": "original_scenario_id",
        "actions_list": "actions_list",
        "steps_order": "steps_order",
        "createdAt": "created_at",
        "updatedAt": "updated_at",
        "createdBy": "created_by",
        "recommended": "recommended",
        "tags_list": "tags_list",
        "categories": "categories",
        "order": "order",
        "minApiVer": "min_api_ver",
    }
    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-get-custom-scenarios",
    inputs_list=[
        InputArgument(
            name="schedule_details",
            default="true",
            options=["false", "true"],
            required=False,
            is_array=False,
            description="Details of custom scenarios (My scenarios)." + " Possible values are: false, true. Default is true.",
        ),
    ],
    outputs_prefix="SafeBreach.Scenario",
    outputs_list=[
        OutputArgument(name="id", description="the Id of scenario.", prefix="SafeBreach.Scenario", output_type=str),
        OutputArgument(name="name", description="the name of the scenario.", prefix="SafeBreach.Scenario", output_type=str),
        OutputArgument(
            name="description",
            description="the description of the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="success_criteria",
            description="success criteria the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="original_scenario_id",
            description="original scenario id of scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="actions_list", description="actions list of the scenario.", prefix="SafeBreach.Scenario", output_type=str
        ),
        OutputArgument(
            name="edges_count", description="edges_count for the scenario.", prefix="SafeBreach.Scenario", output_type=str
        ),
        OutputArgument(
            name="steps_order",
            description="the order of steps of the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="created_at",
            description="the creation datetime of the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
        OutputArgument(
            name="updated_at",
            description="the last updated time the scenario.",
            prefix="SafeBreach.Scenario",
            output_type=str,
        ),
    ],
    description="This command  retrieves scenarios which are saved by user as custom scenarios. they generally have "
    + "configurations and everything set up and will be ready to run as tests",
)
def get_custom_scenarios(client: Client):
    """
        This function gets custom scenarios and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns custom scenarios as a table and gives a dictionary as outputs for the same
    """
    custom_scenarios = client.get_custom_scenarios()
    demisto.debug(f"Get custom scenarios result is: {custom_scenarios}")

    if demisto.args().get("schedule_details") == "true":
        flattened_simulations_data_for_table = client.extract_custom_scenario_fields(custom_scenarios.get("data", {}))
    else:
        flattened_simulations_data_for_table = custom_scenarios.get("data", {})

    if flattened_simulations_data_for_table:
        human_readable = tableToMarkdown(
            name="Scenarios",
            headerTransform=scenarios_transformer,
            t=flattened_simulations_data_for_table,
            headers=[
                "id",
                "name",
                "description",
                "successCriteria",
                "originalScenarioId",
                "actions_list",
                "steps_order",
                "createdAt",
                "updatedAt",
            ],
        )
    else:
        human_readable = f"No custom scenarios found: {flattened_simulations_data_for_table}"
    outputs = custom_scenarios
    result = CommandResults(outputs_prefix="SafeBreach.Scenario", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-get-services-status",
    inputs_list=None,
    outputs_prefix="SafeBreach.Service",
    outputs_list=[
        OutputArgument(name="name", description="the name of the service.", prefix="SafeBreach.Service", output_type=str),
        OutputArgument(name="version", description="version of the service.", prefix="SafeBreach.Service", output_type=str),
        OutputArgument(
            name="connection status",
            description="connection status of service.",
            prefix="SafeBreach.Service",
            output_type=str,
        ),
        OutputArgument(name="error", description="error status of service.", prefix="SafeBreach.Service", output_type=str),
    ],
    description="This command facilitates the retrieval of service statuses from SafeBreach,"
    + "presenting them to the user in a tabular format. In the event that services are inactive,"
    + "pertinent details regarding their downtime or last operational status are also displayed.",
)
def get_services_status(client: Client):
    """
        This function get services status and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns services status as a table and gives a dictionary as outputs for the same
    """
    services = client.get_services_status()
    demisto.debug(f"Services API call is {services}")

    modified_services_data = client.format_services_response(services)
    if modified_services_data:
        human_readable = tableToMarkdown(
            name="Services", t=modified_services_data, headers=["name", "version", "connection_status"]
        )
    else:
        human_readable = f"Unable to retrieve status of services: {modified_services_data}"

    outputs = services
    result = CommandResults(outputs_prefix="SafeBreach.Service", outputs=outputs, readable_output=human_readable)
    return result


def simulations_transformer(header):
    return_map = {
        "attackerNodeName": "attacker_node_name",
        "id": "simulation_id",
        "targetNodeName": "target_node_name",
        "destNodeName": "dest_node_name",
        "moveDesc": "attack_description",
        "moveName": "attack_name",
        "attacks_involved": "attacks_involved",
        "resultDetails": "result_details",
        "securityAction": "security_action",
    }

    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-get-simulations",
    inputs_list=[
        InputArgument(
            name="test_id",
            required=False,
            is_array=False,
            description="This is ID of the test whose simulations will be retrieved.",
        ),
    ],
    outputs_prefix="SafeBreach.Simulation",
    outputs_list=[
        OutputArgument(
            name="simulation_id", description="the id of the simulation.", prefix="SafeBreach.Simulation", output_type=str
        ),
        OutputArgument(
            name="attacker_node_name",
            description="Name of attacker node of simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="target_node_name",
            description="name of target of simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="dest_node_name",
            description="name of destination of simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(name="attack_name", description="name of attack", prefix="SafeBreach.Simulation", output_type=str),
        OutputArgument(
            name="attacks_involved",
            description="attack types involved in of simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="result_details", description="result of simulation.", prefix="SafeBreach.Simulation", output_type=str
        ),
        OutputArgument(
            name="security_action",
            description="security status as per the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(name="attack_description", description="attack details.", prefix="SafeBreach.Simulation", output_type=str),
    ],
    description="This command facilitates the retrieval of simulations and their associated data for a specified test. It can be used as a precursor command for the rerun-simulations command, streamlining the process of queuing simulations. It's important to note that this command currently lacks pagination limiters, potentially resulting in the retrieval of a large volume of data.",  # noqa: E501
)
def get_simulations(client: Client):
    """
        This function gets simulations and shows in a table

    Args:
        client (Client): Client class object for API calls

    Returns:
        CommandResults,Dict: This returns simulations as a table and gives a dictionary as outputs for the same
    """

    headers = [
        "attackerNodeName",
        "id",
        "moveName",
        "resultDetails",
        "securityAction",
        "targetNodeName",
        "attacks_involved",
    ]
    simulations = client.get_simulations()
    demisto.debug(f"Result of simulations is: {simulations}")
    modified_simulations_data = format_simulations_data(simulations.get("simulations"))
    if modified_simulations_data:
        human_readable = tableToMarkdown(
            name="Simulations Details for test",
            t=modified_simulations_data,
            headerTransform=simulations_transformer,
            headers=headers,
        )
    else:
        human_readable = f"No simulations found: {modified_simulations_data}"
    outputs = simulations
    result = CommandResults(outputs_prefix="SafeBreach.Simulation", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-get-verification-token",
    inputs_list=None,
    outputs_prefix="SafeBreach.Token",
    outputs_list=[
        OutputArgument(
            name="token",
            description="the value of new verification token.",
            prefix="SafeBreach.Token",
            output_type=str,
        ),
    ],
    description="This command retrieves existing verification token needed for verification of the simulators.",
)
def get_verification_token(client):
    token_data = client.get_verification_token()
    demisto.debug(f"Get verification token result is: {token_data}")
    if token_data.get("data"):
        human_readable = tableToMarkdown(name="Verification Token", t=token_data.get("data"), headers=["secret"])
    else:
        human_readable = f"No verification token found: {token_data}"
    outputs = token_data
    result = CommandResults(outputs_prefix="SafeBreach.Token", outputs=outputs, readable_output=human_readable)

    return result


def tests_scenarios_transformer(header):
    return_map = {
        "name": "name",
        "originalScenarioId": "original_scenario_id",
        "actions_list": "actions_list",
        "steps_order": "steps_order",
        "planRunId": "test_id",
        "planId": "scenario_id",
        "ranBy": "ran_by",
        "ranFrom": "ran_from",
        "priority": "priority",
        "retrySimulations": "retry_simulations",
        "createdAt": "created_at",
        "updatedAt": "updated_at",
        "successCriteria": "success_criteria",
    }
    return return_map.get(header, header)


@metadata_collector.command(
    command_name="safebreach-rerun-test",
    inputs_list=[
        InputArgument(
            name="test_id",
            description="test id for the given test, \
            this is be test id field from get-all-tests-summary command",
            required=True,
            is_array=False,
        ),
        InputArgument(name="test_name", description="test name for the given test", required=True, is_array=False),
    ],
    outputs_prefix="SafeBreach.Test",
    outputs_list=[
        OutputArgument(name="id", description="the Id of test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(name="name", description="the name of the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(name="description", description="the description of the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(
            name="success_criteria", description="success criteria the test.", prefix="SafeBreach.Test", output_type=str
        ),
        OutputArgument(
            name="original_scenario_id",
            description="original scenario id of test.",
            prefix="SafeBreach.Test",
            output_type=str,
        ),
        OutputArgument(name="actions_list", description="actions list of the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(name="edges_count", description="edges_count for the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(
            name="steps_order", description="the order of steps of the test.", prefix="SafeBreach.Test", output_type=str
        ),
        OutputArgument(
            name="created_at", description="the creation datetime of the test.", prefix="SafeBreach.Test", output_type=str
        ),
        OutputArgument(
            name="updated_at", description="the last updated time the test.", prefix="SafeBreach.Test", output_type=str
        ),
        OutputArgument(name="scenario_id", description="the test id of the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(
            name="ran_by",
            description="the user id of the user who ran the test.",
            prefix="SafeBreach.Test",
            output_type=str,
        ),
        OutputArgument(
            name="ran_from", description="where the user ran the test from.", prefix="SafeBreach.Test", output_type=str
        ),
        OutputArgument(
            name="enable_feedback_loop",
            description="feedback loop status of the test.",
            prefix="SafeBreach.Test",
            output_type=str,
        ),
        OutputArgument(name="test_id", description="test_id of the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(name="priority", description="priority of the test.", prefix="SafeBreach.Test", output_type=str),
        OutputArgument(
            name="retry_simulations", description="retry status of the test.", prefix="SafeBreach.Test", output_type=str
        ),
    ],
    description="This command puts given test data in queue for execution.",
)
def rerun_test(client):
    rerun_results = client.rerun_test_or_simulation()
    demisto.debug(f"Rerun test result is: {rerun_results}")

    flattened_simulations_data_for_table = client.extract_test_fields(rerun_results.get("data", {}))
    if flattened_simulations_data_for_table:
        human_readable = tableToMarkdown(
            name="test",
            headerTransform=tests_scenarios_transformer,
            t=flattened_simulations_data_for_table,
            headers=[
                "name",
                "originalScenarioId",
                "actions_list",
                "steps_order",
                "planId",
                "ranBy",
                "ranFrom",
                "planRunId",
                "priority",
                "retrySimulations",
            ],
        )
    else:
        human_readable = f"Unable to rerun test: {flattened_simulations_data_for_table}"
    outputs = rerun_results
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs, readable_output=human_readable)
    return result


@metadata_collector.command(
    command_name="safebreach-rerun-simulation",
    inputs_list=[
        InputArgument(
            name="simulation_ids",
            required=True,
            is_array=False,
            description="ids of simulation we want to queue,\
                          please give ids of simulations as comma separated numbers",
        ),
        InputArgument(
            name="test_name",
            required=True,
            is_array=False,
            description="test name for the given test",
        ),
    ],
    outputs_prefix="SafeBreach.Simulation",
    outputs_list=[
        OutputArgument(name="id", description="the Id of simulation.", prefix="SafeBreach.Simulation", output_type=str),
        OutputArgument(name="name", description="the name of the simulation.", prefix="SafeBreach.Simulation", output_type=str),
        OutputArgument(
            name="description",
            description="the description of the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="success_criteria",
            description="success criteria the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="original_scenario_id",
            description="original simulation id of simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="actions_list",
            description="actions list of the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="steps_order",
            description="the order of steps of the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="createdAt",
            description="the creation datetime of the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
        OutputArgument(
            name="updatedAt",
            description="the last updated time the simulation.",
            prefix="SafeBreach.Simulation",
            output_type=str,
        ),
    ],
    description="this commands puts given simulation ids into queue for running.",
)
def rerun_simulation(client):
    rerun_results = client.rerun_test_or_simulation()
    demisto.debug(f"Rerun Simulation result is: {rerun_results}")
    flattened_simulations_data_for_table = client.extract_custom_scenario_fields([rerun_results.get("data", {})])
    human_readable = tableToMarkdown(
        name="Scenarios",
        headerTransform=tests_scenarios_transformer,
        t=flattened_simulations_data_for_table,
        headers=[
            "name",
            "actions_list",
            "steps_order",
            "ranBy",
            "ranFrom",
            "planRunId",
            "priority",
            "retrySimulations",
        ],
    )
    outputs = rerun_results
    result = CommandResults(outputs_prefix="SafeBreach.Test", outputs=outputs, readable_output=human_readable)

    return result


@metadata_collector.command(
    command_name="safebreach-get-indicators",
    inputs_list=[
        InputArgument(name="test_id", required=True, is_array=False, description="Test ID of the insight"),
        InputArgument(
            name="limit",
            required=False,
            is_array=False,
            description="The maximum number of indicators to " + " generate. The default is 1000.",
        ),
        InputArgument(
            name="insightCategory",
            required=False,
            is_array=True,
            description="Multi-select option for the category of the insights to get remediation data "
            + " for:Network Access, Network Inspection, Endpoint, Email, Web, Data Leak",
        ),
        InputArgument(
            name="insightDataType",
            required=False,
            is_array=True,
            description="Multi-select option for the remediation data type to get: "
            + " Hash, Domain, URI, Command, Port, Protocol, Registry",
        ),
        InputArgument(
            name="behavioralReputation",
            required=False,
            is_array=False,
            description="Select option for the category of behavioral reputation",
        ),
        InputArgument(
            name="nonBehavioralReputation",
            required=False,
            is_array=False,
            description="Select option for the category of non-behavioral reputation",
        ),
    ],
    outputs_prefix="SafeBreach.Indicator",
    outputs_list=[
        OutputArgument(name="value", description="The value of the indicator", prefix="SafeBreach.Indicator", output_type=str),
        OutputArgument(name="type", description="The type of the indicator", prefix="SafeBreach.Indicator", output_type=str),
        OutputArgument(
            name="rawJSON.dataType",
            description="The data type of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=str,
        ),
        OutputArgument(
            name="rawJSON.insightTime",
            description="The time of the insight",
            prefix="SafeBreach.Indicator",
            output_type=str,
        ),
        OutputArgument(
            name="rawJSON.value",
            description="The data type value of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=str,
        ),
        OutputArgument(
            name="fields.description",
            description="The description of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=str,
        ),
        OutputArgument(
            name="fields.safebreachseverity",
            description="The severity of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=str,
        ),
        OutputArgument(
            name="fields.safebreachseverityscore",
            description="The severity score of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=str,
        ),
        OutputArgument(
            name="fields.safebreachisbehavioral",
            description="The behavioral of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=bool,
        ),
        OutputArgument(
            name="fields.safebreachattackids",
            description="The attack ids of the indicator",
            prefix="SafeBreach.Indicator",
            output_type=list,
        ),
        OutputArgument(
            name="fields.port", description="The port of the indicator", prefix="SafeBreach.Indicator", output_type=str
        ),
        OutputArgument(
            name="fields.tags", description="The tags of the indicator", prefix="SafeBreach.Indicator", output_type=str
        ),
        OutputArgument(name="score", description="The score of the indicator", prefix="SafeBreach.Indicator", output_type=int),
    ],
    description="This command fetches SafeBreach Insights from which indicators are extracted, "
    + " creating new indicators or updating existing indicators.",
)
def get_indicators_command(client):
    indicator_results = client.get_indicators_command()
    demisto.debug(f"List of Indicators: {indicator_results}")
    outputs = indicator_results

    entry_result = camelize(outputs)
    if entry_result:
        hr = tableToMarkdown("Indicators:", entry_result)
    else:
        hr = f"No indicators found: {outputs}"
    result = CommandResults(outputs_prefix="SafeBreach.Indicator", outputs=outputs, readable_output=hr)
    return result


@metadata_collector.command(
    command_name="safebreach-get-simulators-versions-list",
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=[
        OutputArgument(name="id", description="Simulator Id", prefix="SafeBreach.Simulator", output_type=str),
        OutputArgument(
            name="lastUpdateDate",
            description="Simulator last updated data",
            prefix="SafeBreach.Simulator",
            output_type=str,
        ),
        OutputArgument(
            name="lastUpdateStatus",
            description="Simulator last updated status",
            prefix="SafeBreach.Simulator",
            output_type=str,
        ),
        OutputArgument(
            name="currentStatus", description="Simulator current status", prefix="SafeBreach.Simulator", output_type=str
        ),
        OutputArgument(
            name="availableVersions",
            description="Simulator available versions",
            prefix="SafeBreach.Simulator",
            output_type=list,
        ),
    ],
    description="This command fetches the list of SafeBreach simulators",
)
def get_simulators_versions_list(client):
    simulator_results = client.get_simulators_versions_list()
    demisto.debug(f"List of simulators versions: {simulator_results}")
    outputs = simulator_results
    entry_result = camelize(outputs)
    if entry_result:
        hr = tableToMarkdown("Simulators:", entry_result)
    else:
        hr = f"No simulators versions found: {simulator_results}"
    result = CommandResults(outputs_prefix="SafeBreach.Simulator", outputs=outputs, readable_output=hr)
    return result


@metadata_collector.command(
    command_name="safebreach-upgrade-simulator",
    inputs_list=[
        InputArgument(name="simulator_id", required=True, is_array=False, description="Simulator ID"),
        InputArgument(
            name="simulator_version",
            required=True,
            is_array=False,
            description="The version should be in the format of the safebreach-get-simulators-versions-list "
            + " command and that 'latest' can be used. The default is the latest.",
        ),
    ],
    outputs_prefix="SafeBreach.Simulator",
    outputs_list=[
        OutputArgument(name="nodeId", description="Simulator ID", prefix="SafeBreach.Simulator", output_type=str),
        OutputArgument(name="status", description="Simulator status", prefix="SafeBreach.Simulator", output_type=str),
    ],
    description="This command updates the simulator using the Simulator ID and available version.",
)
def update_simulator_with_id(client):
    results = client.update_simulator_with_id()
    demisto.debug(f"Updated simulator result is: {results}")
    outputs = results
    entry_result = camelize(outputs)
    if entry_result:
        hr = tableToMarkdown("Updated Simulator:", entry_result)
    else:
        hr = f"Unable to update simulator with ID: {outputs}"
    result = CommandResults(outputs_prefix="SafeBreach.Simulator", outputs=outputs, readable_output=hr)
    return result


@metadata_collector.command(
    command_name="safebreach-get-simulator-download-links",
    outputs_prefix="SafeBreach.Installation",
    outputs_list=[
        OutputArgument(
            name="md5",
            description="The MD5 generated from the contents of the file",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="os",
            description="The operating system for which the update is intended",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="sha1",
            description="The sha1 generated from the contents of the file.",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="sha256",
            description="The sha256 generated from the contents of the file.",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="sha512",
            description="The sha512 generated from the contents of the file.",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="sha512",
            description="The sha512 generated from the contents of the file.",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="url",
            description="The URL from which update can be downloaded.",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
        OutputArgument(
            name="version",
            description="This indicates the simulator version.",
            prefix="SafeBreach.Installation",
            output_type=str,
        ),
    ],
    description="This command gets a list of links for download (item per operating system) for the latest available version.",
)
def get_installation_links(client):
    results = client.get_installation_links()
    demisto.debug(f"Installation links result is: {results}")
    outputs = results
    entry_result = camelize(outputs)
    if entry_result:
        hr = tableToMarkdown("Installation Links:", entry_result)
    else:
        hr = f"No installation links found: {outputs}"
    result = CommandResults(outputs_prefix="SafeBreach.Installation", outputs=outputs, readable_output=hr)
    return result


class CronString:
    def __init__(self, cron_string, time_zone):
        self.cron_string = cron_string
        self.final_string = ""
        self.time_zone = time_zone or "UTC"
        self.parse()

    def parse(self):
        comp_array = self.cron_string.split(" ")
        self.parse_hours_and_minutes(comp_array[0], comp_array[1])
        if comp_array[2] != "*":
            self.parse_days_of_month(comp_array[2])
        elif comp_array[-1] != "*":
            self.parse_day_of_week(comp_array[-1])
        else:
            self.final_string += "every day"

    def parse_hours_and_minutes(self, minutes, hours):
        self.final_string += f"At {hours} hours and {minutes} minutes "

    def parse_days_of_month(self, days_of_month):
        days = days_of_month.split(",")
        days_list = []
        for days_range in days:
            if "-" in days_range:
                days_present = days_range.split("-")
                days_list += list(map(str, range(int(days_present[0]), int(days_present[1]))))
            else:
                days_list.append(days_range)
        if days_list:
            self.final_string += f"on days {', '.join(days_list)} every month"

    def parse_day_of_week(self, days_of_week):
        days = days_of_week.split(",")
        days_list = []
        for days_range in days:
            if "-" in days_range:
                days_present = days_range.split("-")
                days_list += list(map(str, range(int(days_present[0]), int(days_present[1]))))
            else:
                days_list.append(days_range)
        if days_list:
            self.final_string += f"on days {', '.join(days_list)} every week"

    def __str__(self):
        return self.final_string + "."

    def to_string(self):
        return f"{self.final_string} on timezone {self.time_zone}."


def main() -> None:
    """
    Execution starts here
    """
    client = Client(
        api_key=demisto.params().get("credentials", {}).get("password"),
        account_id=demisto.params().get("account_id"),
        base_url=demisto.params().get("base_url"),
        verify=demisto.params().get("verify"),
        proxy=demisto.params().get("proxy"),
    )
    demisto.debug(f"Command being called is {demisto.command()}")
    try:
        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            result = client.get_all_users_for_test()
            return_results(result)

        elif demisto.command() == "safebreach-get-services-status":
            result = get_services_status(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-all-users":
            users = get_all_users(client=client)
            return_results(users)

        elif demisto.command() == "safebreach-get-user-with-matching-name-or-email":
            result = get_user_id_by_name_or_email(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-create-user":
            user = create_user(client=client)
            return_results(user)

        elif demisto.command() == "safebreach-delete-user":
            user = delete_user_with_details(client=client)
            return_results(user)

        elif demisto.command() == "safebreach-update-user":
            user = update_user_with_details(client=client)
            return_results(user)

        elif demisto.command() == "safebreach-list-deployments":
            deployment = get_deployments(client=client)
            return_results(deployment)

        elif demisto.command() == "safebreach-create-deployment":
            deployment = create_deployment(client=client)
            return_results(deployment)

        elif demisto.command() == "safebreach-update-deployment":
            deployment = update_deployment(client=client)
            return_results(deployment)

        elif demisto.command() == "safebreach-delete-deployment":
            deployment = delete_deployment(client=client)
            return_results(deployment)

        elif demisto.command() == "safebreach-generate-api-key":
            result = create_api_key(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-delete-api-key":
            result = delete_api_key(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-integration-issues":
            result = get_all_integration_error_logs(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-indicators":
            result = get_indicators_command(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-clear-integration-issues":
            result = delete_integration_error_logs(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-available-simulator-count":
            result = get_simulator_quota_with_table(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-available-simulator-details":
            result = get_all_simulator_details(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-simulator-with-id":
            result = get_simulator_with_name(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-delete-simulator":
            result = delete_simulator_with_given_name(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-update-simulator":
            result = update_simulator(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-verification-token":
            result = get_verification_token(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-rotate-verification-token":
            result = return_rotated_verification_token(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-tests":
            result = get_all_tests_summary(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-tests-with-scenario-id":
            result = get_all_tests_summary_with_scenario_id(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-delete-test-with-id":
            result = delete_test_result_of_test(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-running-tests":
            result = get_all_running_tests_summary(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-running-simulations":
            result = get_all_running_simulations_summary(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-pause/resume-simulations-tests":
            result = pause_resume_tests_and_simulations(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-scheduled-scenarios":
            result = get_schedules(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-delete-scheduled-scenarios":
            result = delete_schedules(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-prebuilt-scenarios":
            result = get_prebuilt_scenarios(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-custom-scenarios":
            result = get_custom_scenarios(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-approve-simulator":
            result = approve_simulator(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-rerun-test":
            result = rerun_test(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-rerun-simulation":
            result = rerun_simulation(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-simulations":
            result = get_simulations(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-simulators-versions-list":
            result = get_simulators_versions_list(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-upgrade-simulator":
            result = update_simulator_with_id(client=client)
            return_results(result)

        elif demisto.command() == "safebreach-get-simulator-download-links":
            result = get_installation_links(client=client)
            return_results(result)

    except Exception as error:
        return_error(f"Failed to execute {demisto.command()} command .\nError:\n{error}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()