(ns wanijo.public.db
  (:require [wanijo.infrastructure.neo4j :as neo4j]
            [wanijo.specs :as specs]
            [clojure.set :refer [map-invert]]
            [clojure.spec.alpha :as spec]))

(def schema-uuids
  {:coc (or (System/getenv "SCHEMA_COC")
            "4f7b561c-51d2-4332-895f-cd583946dcab")
   :role (or (System/getenv "SCHEMA_ROLE")
             "f87b5810-b8c2-4420-91ae-87f0e02af88a")
   :level (or (System/getenv "SCHEMA_LEVEL")
              "4ce60c8f-3864-4528-9e63-7c862cb8644a")
   :module (or (System/getenv "SCHEMA_MODULE")
               "574b1813-5366-4e48-9034-9c1bce52d455")
   :component (or (System/getenv "SCHEMA_COMPONENT")
                  "439856be-85be-49c1-b58b-4594741f37b3")})

(def attribute-uuids
  {:module-desc (or (System/getenv "ATTR_MODULE_DESC")
                    "0924be5a-9680-44e3-986f-54b5b59631ba")
   :role-desc (or (System/getenv "ATTR_ROLE_DESC")
                  "39e467a5-9ed2-496b-bc98-a14e3b52a8d1")
   :comp-desc (or (System/getenv "ATTR_COMP_DESC")
                  "35646a07-5b48-4711-9211-54df9f13e815")})

(def coc-instance-uuids
  {"dev" (or (System/getenv "INST_DEV")
             "6348f1ab-1771-4bef-b101-eb76ec236646")
   "req" (or (System/getenv "INST_REQ")
             "44b02979-7e78-491c-8631-fc49a47b799d")
   "prk" (or (System/getenv "INST_PRK")
             "318ee72d-8a8b-42c5-aa90-d5efc1787d22")})

(neo4j/defquery roles-with-levels-of-coc
  "MATCH (coc:instance {uuid:{coc_uuid}}),
         (rschema:schema {uuid:{roleschema_uuid}}),
         (role:instance)-[:of]->(rschema),
         (coc)-[:link]->(role)
   OPTIONAL MATCH
         (lschema:schema {uuid:{levelschema_uuid}}),
         (level:instance)-[:of]->(lschema),
         (role)-[:link]->(level)
   RETURN role, level")
(defn roles-with-levels-of-coc! [coc-key]
  (->> (neo4j/exec-query! roles-with-levels-of-coc
                          {:coc_uuid (get coc-instance-uuids coc-key)
                           :roleschema_uuid (:role schema-uuids)
                           :levelschema_uuid (:level schema-uuids)})
       (group-by :role)
       (map (fn [[role rows]]
              {:role role
               :levels (->> (map :level rows)
                            (filter some?)
                            (sort-by :name))}))
       (sort-by #(-> % :role :name))))

(comment
  (roles-with-levels-of-coc! "dev")
  (neo4j/exec-query! roles-with-levels-of-coc!
                     {:coc_uuid (get coc-instance-uuids "dev")
                      :roleschema_uuid (:role schema-uuids)
                      :levelschema_uuid (:level schema-uuids)}))

(neo4j/defquery coc
  "MATCH (coc:instance {uuid:{uuid}})
   RETURN coc")
(defn coc! [coc-key]
  (-> (neo4j/exec-query! coc
                         {:uuid (get coc-instance-uuids coc-key)})
      first
      :coc
      (assoc :key coc-key)))

(neo4j/defquery instance
  "MATCH (i:instance {uuid:{uuid}})
   OPTIONAL MATCH
         (p:property)-[:of]->(i),
         (a:attribute)<-[:of]-(p)
   RETURN i, p, a")
(defn instance! [uuid]
  (let [res (neo4j/exec-query! instance
                               {:uuid uuid})
        instance (-> res
                     first
                     :i
                     (assoc :properties []))]
    (reduce (fn [instance row]
              (if-let [prop (:p row)]
                (update instance
                        :properties
                        conj (assoc prop
                                    :attr (:a row)))
                instance))
            instance
            res)))
(comment
  (instance! "0b30521a-0727-4a6b-b69e-6f3c554a81b2")
  (instance! "fbe4375b-6518-45bb-a692-e58dccd8f658"))

(neo4j/defquery modules-with-comps
  "MATCH (level:instance {uuid:{level_uuid}}),
         (mschema:schema {uuid:{moduleschema_uuid}}),
         (module:instance)-[:of]->(mschema),
         (level)-[:link]->(module)
   OPTIONAL MATCH
         (cschema:schema {uuid:{compschema_uuid}}),
         (comp:instance)-[:of]->(cschema),
         (module)-[:link]-(comp)
   RETURN module, comp")
(defn modules-with-comps! [level-uuid]
  (->> (neo4j/exec-query! modules-with-comps
                          {:level_uuid level-uuid
                           :moduleschema_uuid (:module schema-uuids)
                           :compschema_uuid (:component schema-uuids)})
       (group-by :module)
       (map (fn [[module rows]]
              {:module module
               :comps (->> (map :comp rows)
                           (filter some?)
                           (sort-by :name))}))
       (sort-by #(-> % :module :name))))

(comment
  (modules-with-comps! "140962fb-f55e-45ab-87fc-680c012bd8dc")
  (neo4j/exec-query! modules-with-comps
                     {:level_uuid "140962fb-f55e-45ab-87fc-680c012bd8dc"
                      :moduleschema_uuid (:module schema-uuids)
                      :compschema_uuid (:component schema-uuids)}))

(neo4j/defquery modules-and-levels
  "MATCH (role:instance {uuid:{role_uuid}}),
         (lschema:schema {uuid:{levelschema_uuid}}),
         (level:instance)-[:of]->(lschema),
         (role)-[:link]->(level),
         (mschema:schema {uuid:{moduleschema_uuid}}),
         (module:instance)-[:of]->(mschema),
         (level)-[:link]->(module)
   RETURN module, level
   ORDER BY CASE
              WHEN level.name =~ \".*Foundation.*\" THEN
                1
              WHEN level.name =~ \".*Professional.*\" THEN
                2
              ELSE
                3
            END,
            module.name")
(defn modules-and-levels! [role-uuid]
  (neo4j/exec-query! modules-and-levels
                     {:role_uuid role-uuid
                      :moduleschema_uuid (:module schema-uuids)
                      :levelschema_uuid (:level schema-uuids)}))
(comment
  (modules-and-levels! "d97628f4-477d-49d7-a1c1-2fd643a0ee65")
  (instance! "d97628f4-477d-49d7-a1c1-2fd643a0ee65"))

(defn propery-of-attribute [{:keys [properties]} attr-uuid]
  (->> properties
       (filter #(= attr-uuid (-> % :attr :uuid)))
       first
       :value))

(neo4j/defquery components-of-module
  "MATCH (module:instance {uuid:{uuid}}),
         (cschema:schema {uuid:{compschema_uuid}}),
         (comp:instance)-[:of]->(cschema),
         (module)-[:link]->(comp)
   RETURN comp
   ORDER BY comp.name")
(defn module! [uuid]
  (let [i (instance! uuid)
        comps (->> {:uuid uuid
                    :compschema_uuid (:component schema-uuids)}
                   (neo4j/exec-query! components-of-module)
                   (map :comp))]

    (assoc i
           :desc (propery-of-attribute i (:module-desc attribute-uuids))
           :components comps)))
(comment
  (module! "0b30521a-0727-4a6b-b69e-6f3c554a81b2"))

(defn role! [uuid]
  (let [i (instance! uuid)]
    (assoc i
           :desc (propery-of-attribute i (:role-desc attribute-uuids)))))
(comment
  (role! "2c507f67-600d-4add-b648-2ea62d5a4ffc"))

(defn comp! [uuid]
  (let [i (instance! uuid)]
    (assoc i
           :desc (propery-of-attribute i (:comp-desc attribute-uuids)))))

(neo4j/defquery search
  "MATCH (i:instance)-[:of]->(s:schema)
   OPTIONAL MATCH (i)<-[:of]-(p:property)
   WITH i, p, s
   WHERE toLower(i.name) =~ toLower('(?s).*' + {term} + '.*')
      OR toLower(p.name) =~ toLower('(?s).*' + {term} + '.*')
      OR toLower(p.value) =~ toLower('(?s).*' + {term} + '.*')
   MATCH (cocs:instance)
   WHERE cocs.uuid IN {coc_uuids}
   WITH i, p, s,
        shortestPath((i)<-[:link*0..5]-(cocs)) AS path
   WHERE path IS NOT NULL
   UNWIND nodes(path) AS segment
   MATCH (segment)-[:of]->(segment_schema:schema)
   RETURN i, p, s,
          segment,
          segment_schema,
          CASE
            WHEN toLower(i.name) =~ toLower('(?s).*' + {term} + '.*') THEN
              i.name
            WHEN toLower(p.name) =~ toLower('(?s).*' + {term} + '.*') THEN
              p.name
            ELSE
              p.value
          END AS hit")
(defn uuid->coc-key [instance-uuid]
  (-> coc-instance-uuids
      map-invert
      (get instance-uuid)
      keyword))
(comment
  (uuid->coc-key "73f69c1b-5106-44bb-b5e1-90c4f3847037")
  (uuid->coc-key "2c507f67-600d-4add-b648-2ea62d5a4ffc"))
(defn build-path [segments segment-schemas]
  (let [merged (map (fn merge-segment-schema [seg schema]
                      (assoc seg :schema schema))
                    segments segment-schemas)]
    (map (fn assoc-coc-key [row]
           (if (= :coc (-> row :schema :which))
             (assoc row :key
                    (uuid->coc-key (:uuid row)))
             row))
         merged)))
(comment
  (build-path [{:uuid "73f69c1b-5106-44bb-b5e1-90c4f3847037"}
               {:uuid "bla bla"}]
              [{:which :coc}
               {:which :comp}]))
(defn group-search-result [[instance row]]
  (let [segments (map :segment row)
        segment-schemas (->> row
                             (map :segment_schema)
                             (map #(assoc % :which
                                          (get (map-invert schema-uuids)
                                               (:uuid %)))))]
    (assoc instance
           :props (->> row
                       (map :p)
                       (filter some?)
                       distinct)
           :match (-> row first :hit)
           :schema (-> row first :s)
           :path-to-coc (build-path segments segment-schemas))))
(spec/def ::props
  (spec/coll-of
    (spec/keys :req-un [::specs/created_at
                        ::specs/updated_at
                        ::specs/uuid
                        ::specs/value])))
(spec/def ::which (set (keys schema-uuids)))
(spec/def ::key (set (map keyword (keys coc-instance-uuids))))
(spec/def ::schema
  (spec/keys :req-un [::specs/name
                      ::specs/created_at
                      ::specs/uuid
                      ::which]))
(spec/def ::match (spec/and string?
                            not-empty))
(spec/def ::path-to-coc
  (spec/coll-of
    (spec/keys :req-un [::specs/name
                        ::specs/uuid
                        ::schema]
               :opt-un [::key])))
(spec/def ::search-result-list
  (spec/coll-of
    (spec/keys :req-un [::specs/name
                        ::specs/created_at
                        ::specs/updated_at
                        ::specs/uuid
                        ::props
                        ::match
                        ::schema
                        ::path-to-coc
                        ::which])))
(defn search! [term]
  {:post [(spec/assert ::search-result-list %)]}
  (let [results (->> (neo4j/exec-query!
                       search
                       {:term term
                        :coc_uuids (vals coc-instance-uuids)})
                     (group-by :i)
                     (map group-search-result)
                     (sort-by :updated_at #(compare %2 %1)))]
    (map (fn [finding]
           (assoc-in finding
                     [:schema :which]
                     (get (map-invert schema-uuids)
                          (-> finding :schema :uuid))))
         results)))
(comment
  (search! "Builtools")
  (search! "Arschitektur")
  (search! "Angular")
  (neo4j/exec-query!
    search
    {:term "Angular"
     :coc_uuids (vals coc-instance-uuids)})
  (neo4j/defquery path-query
    "match
      (coc:instance)-[:of]->(:schema {uuid:'f0dae806-c82e-4ced-a39e-c7aef5442579'}),
      (i:instance {uuid:'0b30521a-0727-4a6b-b69e-6f3c554a81b2'})
     with i,
          shortestPath((i)<-[:link*..4]-(coc)) as path
     where path is not null
     return i,
            nodes(path) as path")
  (neo4j/exec-query! path-query {}))
