(ns wanijo.schema.db
  (:require [wanijo.infrastructure.neo4j :as neo4j]
            [wanijo.schema.domain :as domain]
            [clojure.spec.alpha :as spec]))

(neo4j/defquery all-created-by
  "MATCH (s:schema)-[:created_by]->(u:user)
   WHERE u.uuid = {uuid}
   RETURN s
   ORDER BY s.name")
(defn all-created-by! [user-uuid]
  (map :s
       (neo4j/exec-query!
         all-created-by
         {:uuid user-uuid})))

(neo4j/defquery all
  "MATCH (s:schema)
   RETURN s
   ORDER BY s.name")
(defn all! []
  (map :s
       (neo4j/exec-query! all {})))

(neo4j/defquery create-new
  "MATCH (u:user)
   WHERE u.uuid = {u_uuid}
   CREATE (s:schema)-[:created_by]->(u)
   SET s.name = {name}
   SET s.uuid = {s_uuid}
   SET s.created_at = {created_at}
   RETURN s")
(defn create-new! [schema-name user-uuid]
  (->>
    (neo4j/exec-query!
      create-new
      {:u_uuid user-uuid
       :name schema-name
       :s_uuid (neo4j/uuid)
       :created_at (neo4j/now-str)})
    first
    :uuid))

(neo4j/defquery find-by-uuid
  "MATCH (s:schema)
   WHERE s.uuid = {uuid}
   OPTIONAL MATCH
         (s)<-[:of]-(a:attribute)
   WHERE a.required = 1
   RETURN s, a
   ORDER BY a.name")
(defn find-by-uuid! [uuid]
  {:post [(spec/assert ::domain/schema-with-req-attrs %)]}
  (let [result (neo4j/exec-query!
                 find-by-uuid
                 {:uuid uuid})
        schema (:s (first result))]
    (when schema
      (assoc schema
             :req-attrs (map :a result)))))

(neo4j/defquery schema-permissions
  "MATCH (s:schema {uuid:{schema_uuid}})
   RETURN
     EXISTS((:user {uuid:{user_uuid}})
            -[:permission {type:{type}}]-
            (s)) AS user_has_permission,
     NOT EXISTS((:user)
                -[:permission {type:{type}}]-
                (s)) AS is_public")
(defn has-user-permission? [perm-type schema-uuid user-uuid]
  (let [perms (first
                (neo4j/exec-query! schema-permissions
                                   {:schema_uuid schema-uuid
                                    :user_uuid user-uuid
                                    :type perm-type}))]
    (domain/has-user-permission? perms)))
(defn has-user-write-permissions? [schema-uuid user-uuid]
  (has-user-permission? "write" schema-uuid user-uuid))
(defn has-user-read-permissions? [schema-uuid user-uuid]
  (has-user-permission? "read" schema-uuid user-uuid))

(neo4j/defquery accessible-schemas
  "MATCH (s:schema),
         (u:user {uuid:{user_uuid}})
   WHERE EXISTS((u)-[:permission {type:'read'}]->(s))
      OR NOT EXISTS((:user)-[:permission {type:'read'}]->(s))
   RETURN s
   ORDER BY s.name")
(defn accessible-schemas! [user-uuid]
  (map :s
       (neo4j/exec-query! accessible-schemas
                          {:user_uuid user-uuid})))

(neo4j/defquery delete
  "MATCH (s:schema {uuid:{uuid}})
   WITH s
   OPTIONAL MATCH (s)--(a:attribute)-[cb:created_by]-(:user)
   DELETE cb, a
   WITH s
   MATCH (s)-[c]-()
   DELETE c, s")
(defn delete! [uuid]
  (neo4j/exec-query!
    delete
    {:uuid uuid}))

(neo4j/defquery edit
  "MATCH (s:schema)
   WHERE s.uuid = {uuid}
     SET s.name = {name}")
(defn edit! [schema]
  (neo4j/exec-query!
    edit
    schema))

(neo4j/defquery assigned-users
  "MATCH (s:schema)-[p:permission]-(u:user)
   WHERE s.uuid = {uuid}
   RETURN u, p
   ORDER BY u.ident")
(defn assigned-users! [uuid]
  (neo4j/exec-query!
    assigned-users
    {:uuid uuid}))

(neo4j/defquery assigned-schemas
  "MATCH (s1:schema)-[p:permission]-(s2:schema)
   WHERE s1.uuid = {uuid}
   RETURN s2
   ORDER BY s2.name")
(defn assigned-schemas! [uuid]
  (neo4j/exec-query!
    assigned-schemas
    {:uuid uuid}))

(defn find-with-assigned-entities! [uuid]
  (let [users (reduce #(case (-> %2 :p :type)
                         "write" (update %1 :write conj (-> %2 :u :uuid))
                         "read" (update %1 :read conj (-> %2 :u :uuid))
                         %1)
                      {:write []
                       :read []}
                      (assigned-users! uuid))
        schemas (map #(-> % :s2 :uuid) (assigned-schemas! uuid))]
    (assoc (find-by-uuid! uuid)
           :assigned-read-users (:read users)
           :assigned-write-users (:write users)
           :assigned-schemas schemas)))

(neo4j/defquery remove-assignments
  "MATCH (s:schema)-[c:permission]-(:user)
   WHERE s.uuid = {uuid}
     AND c.type = {permtype}
   DELETE c")
(neo4j/defquery create-assignments
  "MATCH (s:schema), (u:user)
   WHERE s.uuid = {uuid}
     AND u.uuid IN {users}
   CREATE (s)<-[:permission{type:{permtype}}]-(u)")
(defn assign-users! [uuid users permission]
  (neo4j/exec-queries!
    [remove-assignments
     {:uuid uuid
      :permtype permission}]
    [create-assignments
     {:uuid uuid
      :users users
      :permtype permission}]))

(neo4j/defquery remove-schema-assignments
  "MATCH (s1:schema)-[p:permission]-(s2:schema)
   WHERE s1.uuid = {uuid}
   DELETE p")
(neo4j/defquery create-schema-assignments
  "MATCH (s1:schema), (s2:schema)
   WHERE s1.uuid = {uuid}
     AND s2.uuid IN {schemas}
   CREATE (s1)-[:permission]->(s2)")
(defn assign-schemas! [uuid schemas]
  (neo4j/exec-queries!
    [remove-schema-assignments
     {:uuid uuid}]
    [create-schema-assignments
     {:uuid uuid :schemas schemas}]))

(neo4j/defquery find-by-instance
  "MATCH (i:instance {uuid:{uuid}})-[:of]->(s:schema)
   RETURN s")
(defn find-by-instance! [uuid]
  (-> find-by-instance
      (neo4j/exec-query! {:uuid uuid})
      first
      :s))
