(ns testbench.core
  "Testbench Namespace"
  (:require [contajners.core :as ctr])
  (:require [yaml.core :as yaml]))

(def testbench-netname "testbench-network-1")
(def test-network {:op :NetworkCreate
                   :data {:Name testbench-netname
                          :CheckDuplicate true
                          :Driver "overlay"}})

(def envoy {:op :ServiceCreate
            :data {:Name "envoy"
                   :TaskTemplate {:ContainerSpec {:Image "FIXME"
                                                  :Env ["ENVOY_WAF_WORKER_THREADS=3"
                                                        "cmdoptions='--concurrency 10'"]
                                                  :Networks testbench-netname
                                                  :Mounts   [{:Source "/tmp" :Target "/tmp"}
                                                             {:Source "/tmp/envoy-test.yaml"
                                                              :Target "/etc/envoy/envoy.yaml"}]}}
                   :Networks  [{:Target testbench-netname}]
                   :EndpointSpec  {:Ports  [{:TargetPort 9900 :PublishedPort 9900}
                                            {:TargetPort 8500 :PublishedPort 8500}
                                            {:TargetPort 8601 :PublishedPort 8601}
                                            {:TargetPort 9999 :PublishedPort 9999}]}}})

(def otel {:op :ServiceCreate
           :data {:Name "opentelemetry"
                  :TaskTemplate {:ContainerSpec {:Image "jaegertracing/all-in-one:latest"
                                                 :Env ["COLLECTOR_OTLP_ENABLED=true"
                                                       "LOG_LEVEL=debug"]
                                                 :Networks testbench-netname}}
                  :Networks  [{:Target testbench-netname}]
                  :EndpointSpec  {:Ports [{:TargetPort 16686 :PublishedPort 16686}
                                          {:TargetPort 14268 :PublishedPort 14268}
                                          {:TargetPort  4317 :PublishedPort  4317}]}}})

(def echo {:op :ServiceCreate
           :data {:Name "echo"
                  :TaskTemplate {:ContainerSpec {:Image "hashicorp/http-echo:latest"
                                                 :Args ["-text='hello world'"]
                                                 :Networks testbench-netname}}
                  :Networks  [{:Target testbench-netname}]
                  :EndpointSpec  {:Ports [{:TargetPort 5678 :PublishedPort 5678}]}}})

(def svc-client (ctr/client {:engine   :docker
                             :version  "v1.43"
                             :category :services
                             :conn     {:uri "unix:///var/run/docker.sock"}}))

(def net-client (ctr/client {:engine   :docker
                             :version  "v1.43"
                             :category :networks
                             :conn     {:uri "unix:///var/run/docker.sock"}}))

(defn mk-broom
  "creates a cleanup callback for a given id"
  [id client op]
  (fn []
    (println "[+] Destroying: " id)
    (ctr/invoke client {:op op
                        :params {:id id}})))

(defn mk-basic-cluster
  "fills a boilerplate document with envoy cluster config"
  [name port]
  {:name name
   :type "STRICT_DNS"
   :lb_policy "ROUND_ROBIN"
   :typed_extension_protocol_options {:envoy.extensions.upstreams.http.v3.HttpProtocolOptions
                                      {"@type" "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions"
                                       :explicit_http_config {:http2_protocol_options {}}}}
   :load_assignment {:cluster_name name
                     :endpoints [{:lb_endpoints [{:endpoint {:address {:socket_address {:address name :port_value port}}}}]}]}})


(defn mk-basic-listener
  "fills a boilerplate document with envoy listener config"
  [cluster-name port]
  {:name (str cluster-name "-listener")
   :address {:socket_address {:protocol   "TCP"
                              :address    "0.0.0.0"
                              :port_value port}}
   :filter_chains [{:filters [{:name "envoy.filters.network.http_connection_manager"
                               :typed_config
                                 {"@type"
                                  "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
                                  :stat_prefix "ingress_http"
                                  :route_config {:name "local_route"
                                                 :virtual_hosts [{:name "local_service"
                                                                  :domains ["*"]
                                                                  :routes [{:match {:prefix "/"}
                                                                            :route
                                                                            {:host_rewrite_literal "rewritten.net"
                                                                             :cluster cluster-name}}]}]}
                                  :tracing {:verbose true
                                            :provider {:name "opentelemetry"
                                                       :typed_config
                                                         {"@type" "type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig"
                                                          :grpc_service {:envoy_grpc {:cluster_name "opentelemetry"}}
                                                          :service_name "envoy"}}}
                                  :http_filters [{:name "envoy.filters.http.router"
                                                  :typed_config
                                                  {"@type" "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"}}
                                                 ]}}]}]})

(defn -main
  "Create, and then destroy all objects in dispatch table"
  [& args]
  (let [envoy-image (first args)
        envoy (assoc-in envoy [:data :TaskTemplate :ContainerSpec :Image] envoy-image)
        [net-broom net-id] (let [res (ctr/invoke net-client test-network)
                                 id  (:Id res)
                                 broom (mk-broom id net-client :NetworkDelete)]
                             (if (some? id)
                               (do ;; then
                                 (println "[+] Network " id " created")
                                 [broom id])
                               (do ;; else
                                 (println "[-] Failed to create network: " (:message res))
                                 [(fn [] (println "[-] skipped deleting network")) nil])))
        svc-table [{:svc      echo
                    :cluster  5678}
                   {:svc      otel
                    :cluster  4317}
                   {:svc      envoy
                    :listener {:port 8500
                               :dst  "echo"}}]]

    ;; TODO fail if net-id blank
    ;; TODO print net info
    (println "[+] Envoy image: " envoy-image)
    ;; generate envoy configuration
    (let [clusters  (reduce (fn [set svc]
                              (let [cls (:cluster svc)]
                                (if (some? cls)
                                  (conj set (mk-basic-cluster (:Name (:data (:svc svc))) cls))
                                  set)))
                            [] svc-table)
          listeners (reduce (fn [set svc]
                              (let [lsn (:listener svc)]
                                (if (some? lsn)
                                  (conj set (mk-basic-listener (:dst lsn) (:port lsn)))
                                  set)))
                            [] svc-table)
          envoy-config {:admin {:address {:socket_address {:address "0.0.0.0"
                                                      :port_value 9999}}}
                   :node  {:id "testbench-node"
                           :cluster "idunno"}
                   :static_resources {:clusters  clusters
                                      :listeners listeners}}]
      (spit "/tmp/envoy-test.yaml" (yaml/generate-string envoy-config))
      (println "[+] wrote envoy config to /tmp/envoy-test.yaml"))

    ;; setup services
    (let [svc-brooms (reduce (fn [set svc]
                               (let [call (:svc svc)
                                     res (ctr/invoke svc-client call)
                                     id (:ID res)
                                     broom (mk-broom id svc-client :ServiceDelete)]
                                 (if (some? id)
                                   (do (println "[+] Created " (:Name (:data call)) " " id)
                                       (conj set broom))
                                   (do (println "[-] Failed to create "
                                                (:Name (:data call))
                                                " " (:message res))
                                       set))))
                             [] svc-table)]
      ;; WAIT FOR ENTER
      (println "[+] press enter to tear down ....")
      (read-line)
      ;; TEARDOWN
      (net-broom)
      (doseq [broom svc-brooms]
        (broom)))))
