#!/usr/bin/env crystal
require "./cb"
require "./ext/option_parser"
require "raven"

Log.setup do |c|
  c.bind("raven.*", Log::Severity::None, Log::IOBackend.new)
  c.bind "*", :info, Raven::LogBackend.new(record_breadcrumbs: true)
end

PROG = CB::Program.new

macro set_action(cl)
  action = CB::{{cl}}.new PROG.client, PROG.input, PROG.output
end

macro positional_args(*action_setters)
  parser.unknown_args do |args|
    unless args.size == {{action_setters.size}}
      STDERR.puts parser
      exit 1
    end
    {% for setter, idx in action_setters %}
      {{setter}} = args[{{idx}}]
    {% end %}
  end
end

def show_deprecated(msg : String)
  printf "%s: %s\n\n", "Deprecated".colorize.t_warn, msg
end

Raven.configure do |config|
  {% if flag?(:release) %}
    config.dsn = String.new(Bytes[
      104, 116, 116, 112, 115, 58, 47, 47, 99, 56, 101, 102, 56, 99, 57, 57,
      48, 100, 52, 57, 52, 48, 102, 50, 97, 49, 53, 55, 48, 52, 102, 101, 100,
      52, 100, 51, 53, 52, 55, 56, 64, 111, 52, 51, 51, 52, 53, 49, 46, 105,
      110, 103, 101, 115, 116, 46, 115, 101, 110, 116, 114, 121, 46, 105, 111,
      47, 53, 56, 51, 49, 51, 51, 49,
    ])
  {% end %}
  config.release = CB::VERSION
  config.server_name = CB::HOST
end

action = nil
op = OptionParser.new do |parser|
  parser.banner = "cb <command>"

  parser.on("--_completion CMDSTRING") do |cmdstring|
    client = PROG.client rescue nil # in case not logged in
    CB::Completion.parse(client, cmdstring).each { |opt| puts opt }
    exit
  end

  parser.on("login", "Store API key") do
    parser.banner = "cb login"
    action = CB::Login.new
  end

  parser.on("logout", "Remove stored API key and tokens") do
    parser.banner = "cb logout"
    action = CB::Logout.new
  end

  parser.on("list", "List clusters") do
    parser.banner = "cb list"
    list = set_action List

    parser.on("--team ID", "Choose team") { |arg| list.team_id = arg }
    parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| list.format = arg }
    parser.on("--no-header", "Do not display table header") { list.no_header = true }

    parser.examples = <<-EXAMPLES
    List all clusters. Output: table (default)
    $ cb list

    $ cb list --format=table

    List all clusters. Output: tree
    $ cb list --format=tree

    List all clusters for a single team.
    $ cb list --team <ID>
    EXAMPLES
  end

  parser.on("info", "Detailed cluster information") do
    parser.banner = "cb info <cluster id>"
    info = set_action ClusterInfo
    positional_args info.cluster_id
  end

  # Opens the Bridge dashboard.
  #
  # This is hidden from help because it's largely used for internal use.
  parser.on("open") do
    parser.banner = "cb open"
    set_action Open
  end

  parser.on("rename", "Change cluster name") do
    parser.banner = "cb rename <cluster id> <new name>"
    rename = set_action ClusterRename

    positional_args rename.cluster_id, rename.new_name
  end

  parser.on("uri", "Display connection URI for a cluster") do
    parser.banner = "cb uri <cluster id> [--role]"
    uri = set_action ClusterURI

    parser.on("--database DATABASE", "Database name (default: postgres)") { |arg| uri.database = arg }
    parser.on("--port PORT", "Port number (default: 5432)") { |arg| uri.port = arg }
    parser.on("--role NAME", "Role name (default: user)") { |arg| uri.role = CB::Role.new arg }
    positional_args uri.cluster_id
  end

  parser.on("psql", "Connect to the database using `psql`") do
    parser.banner = "cb psql <cluster id> [--database] [-- [args for psql such as -c or -f]]"
    psql = set_action Psql

    parser.on("--database NAME", "Database name (default: postgres)") { |arg| psql.database = arg }
    parser.on("--role NAME", "Role name (default: default)") { |arg| psql.role = arg }
    positional_args psql.cluster_id
  end

  parser.on("firewall") do
    show_deprecated("Prefer use of #{"cb network".colorize.bold} instead")
    manage = set_action ManageFirewall
    parser.banner = "cb firewall <--cluster> [--add] [--remove]"

    parser.on("--cluster ID", "Choose cluster") { |arg| manage.cluster_id = arg }
    parser.on("--add CIDR", "Add a firewall rule") { |arg| manage.add arg }
    parser.on("--remove CIDR", "Remove a firewall rule") { |arg| manage.remove arg }
  end

  parser.on("create", "Create a new cluster") do
    create = set_action ClusterCreate
    parser.banner = <<-EOB
      cb create <--platform|-p> <--region|-r> <--plan> <--team|-t> [--storage|-s] [--name|-n] [--version|-v] [--ha] [--network]
          cb create --fork ID [--at] [--platform|-p] [--region|-r] [--plan] [--storage|-s] [--name|-n] [--ha] [--network]
          cb create --replica ID [--platform|-p] [--region|-r] [--plan] [--name|-n] [--network]
    EOB

    parser.on("--ha <true|false>", "High Availability (default: false)") { |arg| create.ha = arg }
    parser.on("--plan NAME", "Plan (server vCPU+memory)") { |arg| create.plan = arg }
    parser.on("-n NAME", "--name NAME", "Cluster name (default: Cluster date+time)") { |arg| create.name = arg }
    parser.on("-p NAME", "--platform NAME", "Cloud provider") { |arg| create.platform = arg }
    parser.on("-r NAME", "--region NAME", "Region/Location") { |arg| create.region = arg }
    parser.on("-v VERSION", "--version VERSION", "Postgres major version") { |arg| create.postgres_version = arg }
    parser.on("-s GiB", "--storage GiB", "Storage size (default: 100GiB, or same as source)") { |arg| create.storage = arg }
    parser.on("-t ID", "--team ID", "Team") { |arg| create.team = arg }
    parser.on("--network network", "Network") { |arg| create.network = arg }

    parser.on("--replica ID", "Choose source cluster for read-replica") { |arg| create.replica = arg }
    parser.on("--fork ID", "Choose source cluster for fork") { |arg| create.fork = arg }
    parser.on("--at TIME", "Recovery point-in-time in RFC3339 (default: now)") { |arg| create.at = arg }

    parser.examples = <<-EXAMPLES
      Create a new cluster.
      $ cb create --name <NAME> --platform <NAME> --region <NAME> --plan <NAME>

      Create a fork.
      $ cb create --fork <ID>

      Create a read-replica.
      $ cb create --replica <ID>

      Create a cross platform fork/replica.
      $ cb create --fork <ID> --platform <NAME> --region <NAME> --plan <NAME>
      $ cb create --replica <ID> --platform <NAME> --region <NAME> --plan <NAME>
    EXAMPLES
  end

  #
  # Custom Configuration Parameter Management
  #

  parser.on("config-param", "Manage configuration parameters") do
    parser.banner = "cb config-param <command>"

    # TODO (abrightwell): would be really cool to implement this kind of
    # support.
    #
    # parser.on("edit", "interactively edit config values") do
    # parser.on("--cluster ID", "Choose cluster")
    # end

    parser.on("get", "display one or all configuration parameters for a cluster") do
      parser.banner = "cb config-param get <--cluster> [NAME]"

      get = set_action ConfigurationParameterGet
      parser.on("--cluster ID", "Choose cluster") { |arg| get.cluster_id = arg }
      parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| get.format = arg }
      parser.on("--no-header", "Do not display table header") { get.no_header = true }

      parser.unknown_args { |args| get.args = args }

      parser.examples = <<-EXAMPLES
        Get a single configuration parameter:
        $ cb config-params get --cluster <ID> postgres:max_connections

        Get all configuration parameters:
        $ cb config-params get --cluster <ID>
      EXAMPLES
    end

    parser.on("list-supported", "display supported configuration parameters") do
      parser.banner = "cb config-param list-supported"

      supported = set_action ConfigurationParameterListSupported
      parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| supported.format = arg }
      parser.on("--no-header", "Do not display table header") { supported.no_header = true }

      parser.unknown_args { |args| supported.args = args }

      parser.examples = <<-EXAMPLES
        List all supported configuration parameters:
        $ cb config-param list-supported

        List all supported configuration parameters by component:
        $ cb config-param list-supported postgres
      EXAMPLES
    end

    parser.on("set", "set one or more configuration parameter values") do
      parser.banner = "cb config-param set <--cluster> <NAME=VALUE> [...]"

      set = set_action ConfigurationParameterSet
      parser.on("--cluster ID", "Choose cluster") { |arg| set.cluster_id = arg }
      parser.on("--allow-restart <true|false>", "Allow restarting the cluster (default: false).") { |arg| set.allow_restart = arg }

      parser.unknown_args { |args| set.args = args }

      parser.examples = <<-EXAMPLES
        Set a single configuration parameter:
        $ cb config-params set --cluster <ID> postgres:max_connections=100

        Set multiple configuration parameters:
        $ cb config-params set --cluster <ID> postgres:max_connections=100 postgres:hot_standby=off
      EXAMPLES
    end

    parser.on("reset", "reset one or more configuration parameter values to default") do
      parser.banner = "cb config-params reset <--cluster> <NAME [...]>"

      reset = set_action ConfigurationParameterReset
      parser.on("--cluster ID", "Choose cluster") { |arg| reset.cluster_id = arg }
      parser.on("--allow-restart <true|false>", "Allow restarting the cluster (default: false).") { |arg| reset.allow_restart = arg }

      parser.unknown_args { |args| reset.args = args }

      parser.examples = <<-EXAMPLES
        Reset a single configuration parameter:
        $ cb config-param reset --cluster <ID> postgres:max_connections

        Reset multiple configuration parameters:
        $ cb config-param reset --cluster <ID> postgres:max_connections postgres:hot_standby
      EXAMPLES
    end
  end

  # Cluster Upgrade
  parser.on("upgrade", "Manage a cluster upgrades") do
    parser.banner = "cb upgrade <start|status|cancel>"

    parser.on("start", "Start a cluster upgrade") do
      upgrade = set_action UpgradeStart
      parser.banner = "cb upgrade start <--cluster>"

      parser.on("--cluster ID", "Choose cluster") { |arg| upgrade.cluster_id = arg }
      parser.on("--ha <true|false>", "High Availability") { |arg| upgrade.ha = arg }
      parser.on("-v VERSION", "--version VERSION", "Postgres major version") { |arg| upgrade.postgres_version = arg }
      parser.on("--plan NAME", "Plan (server vCPU+memory)") { |arg| upgrade.plan = arg }
      parser.on("-s GiB", "--storage GiB", "Storage size") { |arg| upgrade.storage = arg }
      parser.on("--starting-from START", "Starting time of upgrade. (RFC3339 format)") { |arg| upgrade.starting_from = arg }
      parser.on("--now", "Start the upgrade now") { |_| upgrade.now = true }
      parser.on("--confirm", "Confirm cluster upgrade") do
        upgrade.confirmed = true
      end

      parser.examples = <<-EXAMPLES
        Upgrade to high availability.
        $ cb upgrade start --cluster <ID> --ha true

        Upgrade storage and failover during the cluster maintenance window.
        $ cb upgrade start --cluster <ID> --storage 200

        Upgrade plan and failover as soon as possible.
        $ cb upgrade start --cluster <ID> --plan memory-128 --now

        Upgrade postgres major version and storage and failover at the given time.
        $ cb upgrade start --cluster <ID> --version 15 --storage 800 --starting-from 2022-01-01T00:00:00Z
      EXAMPLES
    end

    parser.on("cancel", "Cancel a cluster upgrade") do
      upgrade = set_action UpgradeCancel
      parser.banner = "cb upgrade cancel <--cluster>"

      parser.on("--cluster ID", "Choose cluster") { |arg| upgrade.cluster_id = arg }
    end

    parser.on("status", "Show cluster upgrade status") do
      upgrade = set_action UpgradeStatus
      parser.banner = "cb upgrade status <--cluster>"

      parser.on("--cluster ID", "Choose cluster") { |arg| upgrade.cluster_id = arg }
    end

    parser.on("update", "Update a pending cluster upgrade") do
      update = set_action UpgradeUpdate
      parser.banner = "cb maintenance update <--cluster> [--starting-from] [--now]"
      parser.on("--cluster ID", "Choose cluster") { |arg| update.cluster_id = arg }
      parser.on("--starting-from START", "Starting time to update a maintenance. (RFC3339 format)") { |arg| update.starting_from = arg }
      parser.on("--now", "Start a maintenance now") { |_| update.now = true }
      parser.on("--use-cluster-maintenance-window", "Use cluster maintenance window") { |arg| update.use_cluster_maintenance_window = true }
      parser.on("--confirm", "Confirm maintenance creation.") { |_| update.confirmed = true }
      parser.on("-v VERSION", "--version VERSION", "Postgres major version") { |arg| update.postgres_version = arg }
      parser.on("--plan NAME", "Plan (server vCPU+memory)") { |arg| update.plan = arg }
      parser.on("-s GiB", "--storage GiB", "Storage size") { |arg| update.storage = arg }

      parser.examples = <<-EXAMPLES
        Update a pending upgrade to a new storage size.
        $ cb upgrade update --cluster <ID> --storage 800

        Update a pending upgrade to a new plan.
        $ cb upgrade update --cluster <ID> --plan memory-128

        Update a pending upgrade to failover as soon as possible.
        $ cb upgrade update --cluster <ID> --now

        Update a upgrade to failover starting from a given time.
        $ cb upgrade update --cluster <ID> --starting-from 2022-01-01T00:00:00Z
      EXAMPLES
    end

    parser
  end

  parser.on("destroy", "Destroy a cluster") do
    parser.banner = "cb destroy <cluster id> [--confirm]"
    destroy = set_action ClusterDestroy

    parser.on("--confirm", "Confirm cluster destroy") { destroy.confirmed = true }

    positional_args destroy.cluster_id
  end

  parser.on("detach", "Detach a cluster") do
    detach = set_action Detach
    parser.banner = "cb detach <cluster id>"

    parser.on("--confirm", "Confirm cluster detach") do
      detach.confirmed = true
    end

    positional_args detach.cluster_id
  end

  parser.on("restart", "Restart a cluster") do
    restart = set_action Restart
    parser.banner = "cb restart <cluster id> [--confirm] [--full]"

    parser.on("--confirm", "Confirm cluster restart") { restart.confirmed = true }
    parser.on("--full", "Full restart of server") { restart.full = true }

    positional_args restart.cluster_id
  end

  parser.on("scope", "Run diagnostic queries on a cluster") do
    parser.banner = "cb scope <--cluster> [--(check),...]"
    scope = set_action Scope
    parser.on("--cluster ID", "Choose cluster") { |arg| scope.cluster_id = arg }
    parser.on("--suite <all|quick>", "Run suite of scopes (default: quick)") { |arg| scope.suite = arg }
    parser.on("--database NAME", "Database name (default: postgres)") { |arg| scope.database = arg }
    Scope::Check.all.each do |ck|
      parser.on(ck.flag, ck.desc) { scope.checks << ck.type }
    end
  end

  parser.on("logs", "View live cluster logs") do
    parser.banner = "cb logs <cluster>"
    logs = set_action Logs

    positional_args logs.cluster_id
  end

  parser.on("logdest", "Manage log destinations") do
    parser.banner = "cb logdest <list|add|destroy>"

    parser.on("list", "List log destinations for a cluster") do
      list = set_action LogDestinationList
      parser.banner = "cb logdest list <--cluster>"
      parser.on("--cluster ID", "Choose cluster") { |arg| list.cluster_id = arg }
    end

    parser.on("add", "Add a new log destination to a cluster") do
      add = set_action LogDestinationAdd
      parser.banner = "cb logdest add <--cluster> <--host> <--port> <--template> [--desc]"
      parser.on("--cluster ID", "Choose cluster") { |arg| add.cluster_id = arg }
      parser.on("--host HOST", "Hostname") { |arg| add.host = arg }
      parser.on("--port PORT", "Port number") { |arg| add.port = arg }
      parser.on("--template STR", "Log template") { |arg| add.template = arg }
      parser.on("--desc STR", "Description") { |arg| add.description = arg }
    end

    parser.on("destroy", "Remove an existing log destination from a cluster") do
      destroy = set_action LogDestinationDestroy
      parser.banner = "cb logdest destroy <--cluster> <--logdest>"
      parser.on("--cluster ID", "Choose cluster") { |arg| destroy.cluster_id = arg }
      parser.on("--logdest ID", "Choose log destination") { |arg| destroy.logdest_id = arg }
    end
  end

  #
  # Cluster maintenance Management
  #

  parser.on("maintenance", "Manage cluster maintenance") do
    parser.banner = "cb maintenance <info|set|cancel|create|update>"

    parser.on("info", "Display cluster maintenance information") do
      upgrade = set_action UpgradeStatus
      upgrade.maintenance_only = true

      parser.banner = "cb maintenance info <--cluster>"
      parser.on("--cluster ID", "Choose cluster") { |arg| upgrade.cluster_id = arg }
    end

    parser.on("cancel", "Cancel a cluster maintenance") do
      cancel = set_action MaintenanceCancel
      parser.banner = "cb maintenance cancel <--cluster>"

      parser.on("--cluster ID", "Choose cluster") { |arg| cancel.cluster_id = arg }
    end

    parser.on("set", "Update the cluster default maintenance window") do
      update = set_action MaintenanceWindowUpdate
      parser.banner = "cb maintenance set <--cluster> [--window-start] [--unset]"
      parser.on("--cluster ID", "Choose cluster") { |arg| update.cluster_id = arg }
      parser.on("--window-start START", "Hour maintenance window start (UTC)") { |arg| update.window_start = arg }
      parser.on("--unset", "Unset mainetnance window") { |_| update.unset = true }
    end

    parser.on("create", "Create a cluster maintenance") do
      create = set_action MaintenanceCreate
      parser.banner = "cb maintenance create <--cluster> [--starting-from] [--now]"
      parser.on("--cluster ID", "Choose cluster") { |arg| create.cluster_id = arg }
      parser.on("--starting-from START", "Starting time to create a maintenance. (RFC3339 format)") { |arg| create.starting_from = arg }
      parser.on("--now", "Start a maintenance now") { |_| create.now = true }
      parser.on("--confirm", "Confirm maintenance creation.") { |_| create.confirmed = true }

      parser.examples = <<-EXAMPLES
        Create a maintenance that will failover during the cluster maintenance window
        $ cb maintenance create --cluster <ID>

        Create a maintenance that will failover as soon as possible
        $ cb maintenance create --cluster <ID> --now

        Create a maintenance that will failover starting from a given time
        $ cb maintenance create --cluster <ID> --starting-from 2022-01-01T00:00:00Z
      EXAMPLES
    end

    parser.on("update", "Update a pending cluster maintenance") do
      update = set_action MaintenanceUpdate
      parser.banner = "cb maintenance update <--cluster> [--starting-from] [--now]"
      parser.on("--cluster ID", "Choose cluster") { |arg| update.cluster_id = arg }
      parser.on("--starting-from START", "Starting time to update a maintenance. (RFC3339 format)") { |arg| update.starting_from = arg }
      parser.on("--now", "Start a maintenance now") { |_| update.now = true }
      parser.on("--use-cluster-maintenance-window", "Use cluster maintenance window") { |arg| update.use_cluster_maintenance_window = true }
      parser.on("--confirm", "Confirm maintenance creation.") { |_| update.confirmed = true }

      parser.examples = <<-EXAMPLES
        Update a pending maintenance to failover during the cluster maintenance window.
        $ cb maintenance update --cluster <ID> --use-cluster-maintenance-window

        Update a pending maintenance to failover as soon as possible.
        $ cb maintenance update --cluster <ID> --now

        Update a maintenance to failover starting from a given time.
        $ cb maintenance update --cluster <ID> --starting-from 2022-01-01T00:00:00Z
      EXAMPLES
    end
  end

  #
  # Network Management
  #

  parser.on("network", "Manage networks") do
    parser.banner = "cb network"

    parser.on("add-firewall-rule", "Add a firewall rule to a network") do
      add = set_action FirewallRuleAdd

      parser.banner = "cb network add-firewall-rule <--network> <--rule>"

      parser.on("--description DESC", "A description for the rule") { |arg| add.description = arg }
      parser.on("--format FORMAT", "Output format (default: table)") { |arg| add.format = arg }
      parser.on("--network ID", "The target network for the rule") { |arg| add.network_id = arg }
      parser.on("--no-header", "Do not display table header") { add.no_header = true }
      parser.on("--rule CIDR", "A firewall rule") { |arg| add.rule = arg }

      parser.examples = <<-EXAMPLES
      Add a firewall rule. Output: table
      $ cb network add-firewall-rule --network <ID> --rule <CIDR>

      Add a firewall rule. Output: table without header
      $ cb network add-firewall-rule --network <ID> --rule <CIDR> --no-header

      Add a firewall rule with a description. Output: table
      $ cb network add-firewall-rule --network <ID> --rule <CIDR> --description <DESC>

      Add a firewall rule. Output: json
      $ cb network add-firewall-rule --network <ID> --rule <CIDR> --format json
      EXAMPLES
    end

    parser.on("create-peering", "Create a new peering for a network") do
      create = set_action PeeringCreate

      parser.banner = "cb network create-peering <--network> <--platform>"

      parser.on("--platform NAME", "Cloud provider") { |arg| create.platform = arg }

      # AWS peering options.
      parser.on("--aws-account-id ID", "The AWS account id") { |arg| create.aws_account_id = arg }
      parser.on("--aws-vpc-id ID", "The AWS VPC id") { |arg| create.aws_vpc_id = arg }

      # GCP peering options.
      parser.on("--gcp-project-id ID", "The GCP project id") { |arg| create.gcp_project_id = arg }
      parser.on("--gcp-vpc-name NAME", "The GCP VPC name") { |arg| create.gcp_vpc_name = arg }

      parser.on("--format FORMAT", "Output format (default: table)") { |arg| create.format = arg }
      parser.on("--network ID", "The target network") { |arg| create.network_id = arg }
      parser.on("--no-header", "Do not display table header") { create.no_header = true }

      parser.examples = <<-EXAMPLES
      Create a new peering with AWS VPC.
      $ cb network create-peering --platform aws --network <ID> --aws-account-id <ID> --aws-vpc-id <ID>

      Create a new peering with GCP VPC.
      $ cb network create-peering --platform gcp --network <ID> --gcp-project-id <ID> --gcp-vpc-name <NAME>
      EXAMPLES
    end

    parser.on("delete-peering", "Delete an existing peering for a network") do
      delete = set_action PeeringDelete

      parser.banner = "cb network delete-peering <--network> <--peering>"

      parser.on("--format FORMAT", "Output format (default: table)") { |arg| delete.format = arg }
      parser.on("--network ID", "The target network") { |arg| delete.network_id = arg }
      parser.on("--no-header", "Do not display table header") { delete.no_header = true }
      parser.on("--peering ID", "The ID of the peering") { |arg| delete.peering_id = arg }

      parser.examples = <<-EXAMPLES
      Delete an existing peering.
      $ cb network delete-peering --network <ID> --peering <ID>
      EXAMPLES
    end

    parser.on("get-peering", "Get an existing peering for a network") do
      get = set_action PeeringGet

      parser.banner = "cb network get-peering <--network> <--peering>"

      parser.on("--format FORMAT", "Output format (default: table)") { |arg| get.format = arg }
      parser.on("--network ID", "The target network") { |arg| get.network_id = arg }
      parser.on("--no-header", "Do not display table header") { get.no_header = true }
      parser.on("--peering ID", "The ID of the peering") { |arg| get.peering_id = arg }

      parser.examples = <<-EXAMPLES
      Get an existing peering. Output: table
      $ cb network get-peering --network <ID> --peering <ID>

      Get an existing peering. Output: table without header
      $ cb network get-peering --network <ID> --peering <ID> --no-header

      Get an existing peering. Output: list
      $ cb network get-peering --network <ID> --peering <ID> --format list

      Get an existing peering. Output: json
      $ cb network get-peering --network <ID> --peering <ID> --format json
      EXAMPLES
    end

    parser.on("list-peerings", "List existing peerings for a network") do
      list = set_action PeeringList

      parser.banner = "cb network list-peerings <--network>"

      parser.on("--format FORMAT", "Output format (default: table)") { |arg| list.format = arg }
      parser.on("--network ID", "The target network") { |arg| list.network_id = arg }
      parser.on("--no-header", "Do not display table header") { list.no_header = true }

      parser.examples = <<-EXAMPLES
      List existing peerings. Output: table
      $ cb network list-peerings --network <ID>

      List existing peerings. Output: table without header
      $ cb network list-peerings --network <ID> --no-header

      List existing peerings. Output: list
      $ cb network list-peerings --network <ID> --format list

      List existing peerings. Output: json
      $ cb network list-peerings --network <ID> --format json
      EXAMPLES
    end

    parser.on("list-firewall-rules", "List all firewall rules for a network") do
      list = set_action FirewallRuleList

      parser.on("--format FORMAT", "Output format (default: table)") { |arg| list.format = arg }
      parser.on("--network ID", "The target network") { |arg| list.network_id = arg }
      parser.on("--no-header", "Do not display table header") { list.no_header = true }

      parser.examples = <<-EXAMPLES
      List all firewall rules. Output: table
      $ cb network list-firewall-rules --network <ID>

      List all firewall rules. Output: table without header
      $ cb network list-firewall-rules --network <ID> --no-header

      List all firewall rules. Output: json
      $ cb network list-firewall-rules --network <ID> --format json
      EXAMPLES
    end

    parser.on("remove-firewall-rule", "Remove a firewall rule from a network") do
      remove = set_action FirewallRuleRemove

      parser.banner = "cb network remove-firewall-rule <--network> <--firewall-rule>"

      parser.on("--firewall-rule ID", "The id of the rule to remove") { |arg| remove.firewall_rule_id = arg }
      parser.on("--format FORMAT", "Output format (default: table)") { |arg| remove.format = arg }
      parser.on("--network ID", "The target network") { |arg| remove.network_id = arg }

      parser.examples = <<-EXAMPLES
      Remove firewall rule. Output: table
      $ cb network remove-firewall-rule --network <ID> --firewall-rule <ID>

      Remove firewall rule. Output: table without header
      $ cb network remove-firewall-rule --network <ID> --firewall-rule <ID> --no-header

      Remove firewall rule. Ouptut: json
      $ cb network remove-firewall-rule --network <ID> --firewall-rule <ID> --format json
      EXAMPLES
    end

    parser.on("update-firewall-rule", "Update a network firewall rule") do
      update = set_action FirewallRuleUpdate

      parser.banner = "cb network update-firewall-rule <--network> <--firewall-rule>"

      parser.on("--description DESC", "The description for the rule") { |arg| update.description = arg }
      parser.on("--firewall-rule ID", "The id of the rule to remove") { |arg| update.firewall_rule_id = arg }
      parser.on("--format FORMAT", "Output format (default: table)") { |arg| update.format = arg }
      parser.on("--network ID", "The target network") { |arg| update.network_id = arg }
      parser.on("--rule CIDR", "The firewall rule") { |arg| update.rule = arg }

      parser.examples = <<-EXAMPLES
      Update rule.
      $ cb network update-firewall-rule --network <ID> --firewall-rule <ID> --rule <CIDR>

      Update description.
      $ cb network update-firewall-rule --network <ID> --firewall-rule <ID> --description <DESC>
      EXAMPLES
    end

    parser.on("info", "Detailed network information") do
      info = set_action NetworkInfo

      parser.banner = "cb network info <--network>"

      parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| info.format = arg }
      parser.on("--network ID", "Choose network") { |arg| info.network_id = arg }
      parser.on("--no-header", "Do not display table header") { info.no_header = true }

      parser.examples = <<-EXAMPLES
      Get network details. Ouptut: table
      $ cb network info --network <ID>

      Get network details. Output: table without header
      $ cb network info --network <ID> --no-header

      Get network details. Output: json
      $ cb network info --network <ID> --format=json
      EXAMPLES
    end

    parser.on("list", "List all networks") do
      list = set_action NetworkList

      parser.banner = "cb network list [--team]"

      parser.on("--team ID", "Choose team") { |arg| list.team_id = arg }
      parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| list.format = arg }
      parser.on("--no-header", "Do not display table header") { list.no_header = true }

      parser.examples = <<-EXAMPLES
      List all networks for personal team (or preferred team if using SSO). Output: table
      $ cb network list

      List all networks for a specific team. Output: table
      $ cb network list --team <ID>

      List all networks for a specific team. Ouptut: table without header
      $ cb network list --team <ID> --no-header

      List all networks for a specific team. Output: json
      $ cb network list --team <ID> --format=json
      EXAMPLES
    end
  end

  #
  # Cluster Tailscale Management
  #

  parser.on("tailscale", "Manage Tailscale") do
    parser.banner = "cb tailscale <connect|disconnect>"

    parser.on("connect", "Add a cluster to Tailscale") do
      connect = set_action TailscaleConnect
      parser.banner = "cb tailscale connect <--cluster> <--authkey>"
      parser.on("--cluster ID", "Choose cluster") { |arg| connect.cluster_id = arg }
      parser.on("--authkey KEY", "Pre-authentication key") { |arg| connect.auth_key = arg }
    end

    parser.on("disconnect", "Remove a cluster from Tailscale") do
      disconnect = set_action TailscaleDisconnect
      parser.banner = "cb tailscale disconnect <--cluster>"
      parser.on("--cluster ID", "Choose cluster") { |arg| disconnect.cluster_id = arg }
    end
  end

  #
  # Cluster Role Management
  #

  parser.on("role", "Manage cluster roles") do
    parser.banner = "cb role <create|update|destroy>"

    parser.on("create", "Create new role for a cluster") do
      create = set_action RoleCreate
      parser.banner = "cb role create <--cluster>"
      parser.on("--cluster ID", "Choose cluster") { |arg| create.cluster_id = arg }
    end

    parser.on("destroy", "Destroy a cluster role") do
      destroy = set_action RoleDelete
      parser.banner = "cb role destroy <--cluster> <--name>"
      parser.on("--cluster ID", "Choose cluster") { |arg| destroy.cluster_id = arg }
      parser.on("--name NAME", "Role name") { |arg| destroy.role = arg }
    end

    parser.on("list", "List cluster roles") do
      list = set_action RoleList
      parser.banner = "cb role list <--cluster>"
      parser.on("--cluster ID", "Choose cluster") { |arg| list.cluster_id = arg }
      parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| list.format = arg }
      parser.on("--no-header", "Do not display table header") { |arg| list.no_header = true }

      parser.examples = <<-EXAMPLES
        Get roles for a cluster. Output: table
        $ cb role list --cluster <ID>

        Get roles for a cluster. Output: table without header
        $ cb role list --cluster <ID> --no-header

        Get roles for a cluster. Output: json
        $ cb role list --cluster <ID> --format=json
      EXAMPLES
    end

    parser.on("update", "Update a cluster role") do
      update = set_action RoleUpdate
      parser.banner = "cb role update <--cluster> <--name> [--mode] [--rotate-password]"
      parser.on("--cluster ID", "Choose cluster") { |arg| update.cluster_id = arg }
      parser.on("--name NAME", "Role name") { |arg| update.role = arg }
      parser.on("--read-only <true|false>", "Read-only") { |arg| update.read_only = arg }
      parser.on("--rotate-password <true|false>", "Rotate password") { |arg| update.rotate_password = arg }
    end
  end

  #
  # Team Management
  #
  parser.on("team", "Manage teams") do
    parser.banner = "cb team <command>"

    parser.on("create", "Create a new team.") do
      create = set_action TeamCreate
      parser.banner = "cb team create <--name>"

      parser.on("--name NAME", "Team name") { |arg| create.name = arg }
    end

    parser.on("list", "List available teams.") do
      set_action TeamList
      parser.banner = "cb team list"
    end

    parser.on("info", "Show a teams details.") do
      info = set_action TeamInfo
      parser.banner = "cb team info <team id>"

      positional_args info.team_id
    end

    parser.on("update", "Update a team.") do
      update = set_action TeamUpdate
      parser.banner = "cb team update <team id> [options]"

      parser.on("--billing-email EMAIL", "Team billing email address.") { |arg| update.billing_email = arg }
      parser.on("--enforce-sso <true|false>", "Enforce SSO access to team.") { |arg| update.enforce_sso = arg }
      parser.on("--name NAME", "Name of the team.") { |arg| update.name = arg }
      parser.on("--confirm", "Confirm team update.") { |_| update.confirmed = true }
      positional_args update.team_id
    end

    parser.on("destroy", "Delete a team.") do
      destroy = set_action TeamDestroy
      parser.banner = "cb team destroy <team id> [options]"

      parser.on("--confirm", "Confirm team deletion.") { destroy.confirmed = true }
      positional_args destroy.team_id
    end
  end

  #
  # Team Member Management
  #

  parser.on("team-member", "Manage team members") do
    parser.banner = "cb team-member <command>"

    parser.on("add", "Add a team member") do
      add = set_action TeamMemberAdd
      parser.banner = "cb team-member add <--team> <--email> [--role]"

      parser.on("--team ID", "Team ID") { |arg| add.team_id = arg }
      parser.on("--email EMAIL", "User email address") { |arg| add.email = arg }
      parser.on("--role ROLE", "Team member role (default: member)") { |arg| add.role = arg }
    end

    parser.on("list", "List members of a team") do
      list = set_action TeamMemberList
      parser.banner = "cb team-member list <--team>"

      parser.on("--team ID", "Team ID") { |arg| list.team_id = arg }
    end

    parser.on("info", "Show team member details") do
      info = set_action TeamMemberInfo
      parser.banner = "cb team-member info <--team> <--account | --email>"

      parser.on("--team ID", "Team ID") { |arg| info.team_id = arg }
      parser.on("--account ID", "Account ID") { |arg| info.account_id = arg }
      parser.on("--email EMAIL", "User email address") { |arg| info.email = arg }
    end

    parser.on("update", "Update a member of a team") do
      update = set_action TeamMemberUpdate
      parser.banner = "cb team-member update <--team> <--account | --email> [options]"

      parser.on("--team ID", "Team ID") { |arg| update.team_id = arg }
      parser.on("--account ID", "Account ID") { |arg| update.account_id = arg }
      parser.on("--email EMAIL", "User email address") { |arg| update.email = arg }
      parser.on("--role ROLE", "Team member role") { |arg| update.role = arg }
    end

    parser.on("remove", "Remove a member from a team") do
      remove = set_action TeamMemberRemove
      parser.banner = "cb team-member remove <--team> <--account | --email>"

      parser.on("--team ID", "Team ID") { |arg| remove.team_id = arg }
      parser.on("--account ID", "Account ID") { |arg| remove.account_id = arg }
      parser.on("--email EMAIL", "User email address") { |arg| remove.email = arg }
    end
  end

  #
  # Backup Management
  #
  parser.on("backup", "Manage backups") do
    parser.banner = "cb backup <capture|list|token>"

    parser.on("capture", "Start capturing a new backup for a cluster") do
      capture = set_action BackupCapture
      parser.banner = "cb backup capture <cluster id>"
      positional_args capture.cluster_id
    end

    parser.on("list", "List backups for a cluster") do
      list = set_action BackupList
      parser.banner = "cb backup list <cluster id>"
      positional_args list.cluster_id
    end

    parser.on("token", "Create backup token") do
      token = set_action BackupToken
      parser.on("--format=FORMAT", "<default|pgbackrest>") { |arg| token.format = arg }
      parser.banner = "cb backup token <cluster id>"
      positional_args token.cluster_id
    end
  end

  parser.on("suspend", "Temporarily turn off a cluster") do
    parser.banner = "cb suspend <cluster id>"
    suspend = set_action ClusterSuspend
    positional_args suspend.cluster_id
  end

  parser.on("resume", "Turn on a previously suspended cluster") do
    parser.banner = "cb resume <cluster id>"
    resume = set_action ClusterResume
    positional_args resume.cluster_id
  end

  parser.on("teamcert", "Show public TLS cert for a team") do
    parser.banner = "cb teamcert <team id>"
    teamcert = set_action TeamCert
    positional_args teamcert.team_id
  end

  parser.on("whoami", "Information on current user") do
    set_action WhoAmI
  end

  parser.on("token", "Return a bearer token for use in the api") do
    parser.banner = "cb token [-H]"
    client = CB::Client.new host: CB::HOST
    token = action = CB::Token.new(client, PROG.input, PROG.output)

    parser.on("-H", "Authorization header format") { token.with_header = true }
    parser.on("--format=FORMAT", "Choose output format") { |arg| token.format = arg }
  end

  parser.on("version", "Show the version") do
    parser.banner = "cb version"
    puts CB::VERSION_STR
    exit
  end

  parser.on("-h", "--help", "Show this help") do
    puts parser
    exit
  end

  parser.invalid_option do |flag|
    STDERR << "error".colorize.t_warn << ": " << flag.colorize.t_name << " is not a valid option.\n"
    STDERR.puts parser
    exit 1
  end

  parser.missing_option do |flag|
    STDERR << "error".colorize.t_warn << ": " << flag.colorize.t_name << " requires a value.\n"
    STDERR.puts parser
    exit 1
  end
end

def capture_error(e)
  return if e.class == Raven::Error
  # TODO (abrightwell): Update this before next release. Need to pull the
  # user_id from the credentials maybe? Should we maybe store that along with
  # the credential metadata in `Dirs::CONFIG`? Edge case to consider is if the
  # API key is used as this doesn't inherently have the user id embedded or
  # readily available.
  #
  # begin
  # make sure error gets annotated with user id, but donâ€™t fail to capture
  # if some error in this process
  #
  # t = PROG.token
  # Raven.user_context id: t.user_id rescue end

  cap = Raven.capture e
  if cap.class == Raven::Event
    cap = cap.as(Raven::Event)
    STDERR.puts "#{"error".colorize.red.bold}: #{e.message}"
    STDERR.puts "   #{"id".colorize.red.bold}: #{cap.id.colorize.t_id}"
  else
    raise e
  end
end

begin
  op.parse
  if a = action
    a.call
  else
    puts op
    exit
  end
rescue e : CB::Program::Error
  STDERR.puts "#{"error".colorize.red.bold}: #{e.message}"
  STDERR.puts op if e.show_usage

  exit 1
rescue e : CB::Client::Error
  STDERR << "error".colorize.red.bold << ": #{e.message}\n"
  exit 2 unless e.unauthorized?
  CB::Login.new.run
rescue e
  capture_error e
  exit 3
end
