#!/usr/bin/env bash
# shellcheck disable=SC2155,SC2211,SC2288,SC2317,SC2329

# --- Types ---

@type.port() {
  @is() {
    local value="$1"
    @int.is "$value"
    if (( value < 1 || value > 65535 )); then
      @throw "port-out-of-range" "port must be 1-65535, got $value"
    fi
    @return "$value"
  }

  @default() {
    @return 5432
  }
}

@type.endpoint() {
  @is() {
    local value="$1"
    local host="${value%%:*}"
    local port="${value##*:}"
    @url.is "$host"
    @port.is "$port"

    @out.url  host "$host"
    @out.port port "$port"

    @return.out
  }

  @default() {
    @out.url  host "localhost"
    @out.port port
    @return.out
  }
}

@type.database-config() {
  @is() {
    local value="$1"
    local primary fallback
    primary=$(jq -r '.primary' <<< "$value")
    fallback=$(jq -r '.fallback // empty' <<< "$value")

    @endpoint.is "$primary"
    if [[ -n "$fallback" ]]; then
      @endpoint.is "$fallback"
    fi
    @return "$value"
  }

  @default() {
    @out.endpoint primary "localhost:5432"
    @out.port[]   healthchecks 8080
    @return.out
  }
}

@type.datacenter() {
  @is() {
    case "$1" in
      alpha|beta|production) @return "\"$1\"" ;;
      *) @throw "invalid-datacenter" "'$1' is not a valid datacenter (alpha|beta|production)" ;;
    esac
  }

  @default() {
    @return '"alpha"'
  }
}

# --- Operation ---

@operation.infra.provision() {
  @in.string             name
  @in.datacenter         dc
  @in.database-config?   db-override

  @out.string           name
  @out.datacenter       dc
  @out.database-config  db
  @out.endpoint[]       replicas
  @out.string           provisioned-at

  @remote
  @dangerous
  @note "Provisions a full infra stack in the target datacenter"

  @do() {
    local name=$(@in name)
    local dc=$(@in dc)
    local db_override=$(@in db-override)

    @out.string name "$name"
    @out.datacenter dc "$dc"

    if [[ -n "$db_override" ]]; then
      @out.database-config db "$db_override"
    else
      @out.database-config db "$({
        @out.endpoint primary  "${dc}-db-primary.internal:5432"
        @out.endpoint fallback "${dc}-db-replica.internal:5432"
        @out.port[]   healthchecks 8080 9090
        @return.out
      })"
    fi

    @out.object replicas "$({
      @out.endpoint[] nodes \
        "${dc}-node-1.internal:6443" \
        "${dc}-node-2.internal:6443" \
        "${dc}-node-3.internal:6443"
      @return.out
    })"

    @out.time-iso provisioned-at

    @return.out
  }

  @undo() {
    local name=$(@in name)
    local dc=$(@in dc)
    @info "Tearing down $name in $dc"
  }

  @is-done() {
    local name=$(@in name)
    local dc=$(@in dc)
    curl -sf "https://${dc}-api.internal/status/${name}" > /dev/null
  }
}
