(ns auto-ap.integration.graphql
  (:require [auto-ap.graphql :as sut]
            [venia.core :as v]
            [clojure.test :as t :refer [deftest is testing use-fixtures]]
            [datomic.api :as dc]
            [auto-ap.integration.util :refer [wrap-setup admin-token user-token setup-test-data test-transaction]]
            [auto-ap.datomic :refer [conn]]))


(defn new-client [args]
  (merge {:client/name "Test client"
          :client/code (.toString (java.util.UUID/randomUUID))
          :client/locations ["AB"]}
         args))

(defn new-transaction [args]
  (merge {:transaction/amount 100.0
          :transaction/date #inst "2021-01-01"
          :transaction/id (.toString (java.util.UUID/randomUUID))}
         args))

(defn new-invoice [args]
  (merge {:invoice/total 100.0
          :invoice/date #inst "2021-01-01"
          :invoice/invoice-number (.toString (java.util.UUID/randomUUID))}
         args))

(use-fixtures :each wrap-setup)
(deftest transaction-page
  (testing "transaction page"
    (let [{:strs [test-client-id]} (setup-test-data [(test-transaction :transaction/description-original "hi")])]
      
      (testing "It should find all transactions"
        (let [result (:transaction-page (:data (sut/query (admin-token) "{ transaction_page(filters: {}) { count, start, data { id } }}" {:clients [{:db/id test-client-id}]})))]
          (is (= 1 (:count result)))
          (is (= 0 (:start result)))
          (is (= 1 (count (:data result))))))

      (testing "Users should not see transactions they don't own"
        (let [result (:transaction-page (:data (sut/query (user-token) "{ transaction_page(filters: {}) { count, start, data { id } }}" {:clients []})))]
          (is (= 0 (:count result)))
          (is (= 0 (:start result)))
          (is (= 0 (count (:data result)))))))))


(deftest invoice-page
  (testing "invoice page"
    @(dc/transact conn
                 [(new-client {:db/id "client"})
                  (new-invoice {:invoice/client "client"
                                :invoice/status :invoice-status/paid})])
    (testing "It should find all invoices"
      (let [result (first (:invoice-page (:data (sut/query (admin-token) "{ invoice_page(filters: { status:paid}) { count, start, invoices { id } }}"))))]
        (is (= 1 (:count result)))
        (is (= 0 (:start result)))
        (is (= 1 (count (:invoices result))))))

    (testing "Users should not see transactions they don't own"
      (let [result (first (:invoice-page (:data (sut/query (user-token) "{ invoice_page(filters: {}) { count, start, invoices { id } }}"))))]
        (is (= 0 (:count result)))
        (is (= 0 (:start result)))
        (is (= 0 (count (:data result))))))))

(deftest ledger-page
  (testing "ledger"
    (testing "it should find ledger entries"
      (let [result (:ledger-page (:data (sut/query (admin-token) "{ ledger_page(filters: {client_id: null}) { count, start, journal_entries { id } }}")))]
        (is (int? (:count result)))
        (is (int? (:start result)))
        (is (seqable? (:journal-entries result)))))))


(deftest vendors
  (testing "vendors"
    (testing "it should find vendors"
      (let [result (:ledger-page (:data (sut/query (admin-token) "{ ledger_page(filters: {client_id: null}) { count, start, journal_entries { id } }}")))]
        (is (int? (:count result)))
        (is (int? (:start result)))
        (is (seqable? (:journal-entries result)))))))

(deftest transaction-rule-page
  (testing "it should find rules"
    (let [result (-> (sut/query (admin-token) "{ transaction_rule_page(client_id: null) { count, start, transaction_rules { id } }}")
                     :data
                     :transaction-rule-page)]
      (is (int? (:count result)))
      (is (int? (:start result)))
      (is (seqable? (:transaction-rules result))))))

(deftest upsert-transaction-rule
  (let [{:strs [vendor-id account-id yodlee-merchant-id]} (-> 
                                           @(dc/transact
                                             conn
                                             [{:vendor/name "Bryce's Meat Co"
                                               :db/id       "vendor-id"}
                                              {:account/name "hello"
                                               :db/id        "account-id"}
                                              {:yodlee-merchant/name "yodlee"
                                               :db/id                "yodlee-merchant-id"}])
                                           
                                           :tempids)]
    (testing "it should reject rules that don't add up to 100%"
      (let [q (v/graphql-query {:venia/operation {:operation/type :mutation
                                                  :operation/name "UpsertTransactionRule"}
                                :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule
                                                                             {:transaction-rule  {:accounts [{:account-id account-id
                                                                                                              :percentage "0.25"
                                                                                                              :location "Shared"}]}}
                                                                             [:id ]])}]})]
        (is (thrown? clojure.lang.ExceptionInfo (sut/query (admin-token) q)))))


    (testing "It should reject rules that are missing both description and merchant"
      (let [q (v/graphql-query {:venia/operation {:operation/type :mutation
                                                  :operation/name "UpsertTransactionRule"}
                                :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule
                                                                             {:transaction-rule  {:accounts [{:account-id account-id
                                                                                                              :percentage "1.0"
                                                                                                              :location "Shared"}]}}
                                                                             [:id ]])}]})]
        (is (thrown? clojure.lang.ExceptionInfo (sut/query (admin-token) q)))))
    (testing "it should add rules"
      (let [q (v/graphql-query {:venia/operation {:operation/type :mutation
                                                  :operation/name "UpsertTransactionRule"}
                                :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule
                                                                             {:transaction-rule  {:description "123"
                                                                                                  :yodlee-merchant-id yodlee-merchant-id
                                                                                                  :vendor-id vendor-id
                                                                                                  :transaction-approval-status :approved
                                                                                                  :accounts [{:account-id account-id
                                                                                                              :percentage "0.5"
                                                                                                              :location "Shared"}
                                                                                                             {:account-id account-id
                                                                                                              :percentage "0.5"
                                                                                                              :location "Shared"}]}}
                                                                             [:id :description
                                                                              :transaction-approval-status
                                                                              [:vendor [:name]]
                                                                              [:yodlee-merchant [:name]]
                                                                              [:accounts [:id :percentage [:account [:name]]]]]])}]})
            result (-> (sut/query (admin-token) q)
                       :data
                       :upsert-transaction-rule)]
        
        (is (= "123" (:description result)))
        (is (= "Bryce's Meat Co" (-> result :vendor :name)))
        (is (= "yodlee" (-> result :yodlee-merchant :name)))
        (is (= :approved (:transaction-approval-status result)))
        (is (= "hello" (-> result :accounts (get 0) :account :name )))
        (is (:id result))

        (testing "it should unset removed fields"
          (let [q (v/graphql-query {:venia/operation {:operation/type :mutation
                                                      :operation/name "UpsertTransactionRule"}
                                    :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule
                                                                                 {:transaction-rule  {:id (:id result)
                                                                                                      :description "123"
                                                                                                      :vendor-id nil
                                                                                                      :accounts [{:id (-> result :accounts (get 0) :id)
                                                                                                                  :account-id account-id
                                                                                                                  :percentage "1.0"
                                                                                                                  :location "Shared"}]}}
                                                                                 [[:vendor [:name]]]])}]})
                result (-> (sut/query (admin-token) q)
                           :data
                           :upsert-transaction-rule)]

            (is (nil? (:vendor result)))))

        (testing "it should delete removed rules"
          (let [q (v/graphql-query {:venia/operation {:operation/type :mutation
                                                      :operation/name "UpsertTransactionRule"}
                                    :venia/queries [{:query/data (sut/->graphql [:upsert-transaction-rule
                                                                                 {:transaction-rule  {:id (:id result)
                                                                                                      :description "123"
                                                                                                      :vendor-id vendor-id
                                                                                                      :accounts [{:id (-> result :accounts (get 0) :id)
                                                                                                                  :account-id account-id
                                                                                                                  :percentage "1.0"
                                                                                                                  :location "Shared"}]}}
                                                                                 [[:accounts [:id :percentage [:account [:name]]]]]])}]})
                result (-> (sut/query (admin-token) q)
                           :data
                           :upsert-transaction-rule)]

            (is (= 1 (count (:accounts result))))))))))


(deftest test-transaction-rule
  (testing "it should match rules"
    (let [matching-transaction @(dc/transact conn
                                            [{:transaction/description-original "matching-desc"
                                              :transaction/date                 #inst "2019-01-05T00:00:00.000-08:00"
                                              :transaction/client               {:client/name "1"
                                                                                 :db/id       "client-1"}
                                              :transaction/bank-account         {:db/id             "bank-account-1"
                                                                                 :bank-account/name "1"}

                                              :transaction/amount 1.00
                                              :transaction/id     "2019-01-05 matching-desc 1"
                                              :db/id              "a"}

                                             {:transaction/description-original "nonmatching-desc"
                                              :transaction/client               {:client/name "2"
                                                                                 :db/id       "client-2"}
                                              :transaction/bank-account         {:db/id             "bank-account-2"
                                                                                 :bank-account/name "2"}
                                              :transaction/date                 #inst "2019-01-15T23:23:00.000-08:00"
                                              :transaction/amount               2.00
                                              :transaction/id                   "2019-01-15 nonmatching-desc 2"
                                              :db/id                            "b"}])
          {:strs [a b client-1 client-2 bank-account-1 bank-account-2]} (get-in matching-transaction [:tempids])
          a (str a)
          b (str b)

          rule-test  (fn [rule]
                       (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :query
                                                                              :operation/name "TestTransactionRule"}
                                                            :venia/queries [{:query/data (sut/->graphql [:test-transaction-rule
                                                                                                         {:transaction-rule  rule}
                                                                                                         [:id]])}]}))
                           :data
                           :test-transaction-rule))]
      (testing "based on date "
        (is (= [{:id b}] (rule-test {:dom-gte 14 :dom-lte 16})))
        (is (= [{:id b}] (rule-test {:dom-gte 14})))
        (is (= (set [{:id a} {:id b}]) (set (rule-test {:dom-lte 15}))))
        (is (= [{:id b}] (rule-test {:dom-gte 15})))
        (is (= [{:id b}] (rule-test {:dom-gte 15 :dom-lte 15}))))

      (testing "based on description"
        (is (= [{:id a}] (rule-test {:description "^match"}))))

      (testing "based on amount"
        (is (= [{:id a}] (rule-test {:amount-gte 1.0 :amount-lte 1.0})))
        (is (= (set [{:id a} {:id b}]) (set (rule-test {:amount-gte 1.0 }))) )
        (is (= (set [{:id a} {:id b}]) (set (rule-test {:amount-lte 2.0 }))) ))

      (testing "based on client"
        (is (= [{:id a}] (rule-test {:client-id (str client-1)})))
        (is (= [{:id b}] (rule-test {:client-id (str client-2)}))))

      (testing "based on bank account"
        (is (= [{:id a}] (rule-test {:bank-account-id (str bank-account-1)})))
        (is (= [{:id b}] (rule-test {:bank-account-id (str bank-account-2)})))))))

(deftest test-match-transaction-rule
  (testing "it should apply a  rules"
    (let [{:strs [transaction-id transaction-rule-id uneven-transaction-rule-id]} (-> @(dc/transact conn
                                                                                                   [{:transaction/description-original "matching-desc"
                                                                                                     :transaction/date                 #inst "2019-01-05T00:00:00.000-08:00"
                                                                                                     :transaction/client               {:client/name "1"
                                                                                                                                        :db/id       "client-1"}
                                                                                                     :transaction/bank-account         {:db/id             "bank-account-1"
                                                                                                                                        :bank-account/name "1"}
                                                                                                     :transaction/amount               1.00
                                                                                                     :db/id                            "transaction-id"}

                                                                                                    {:db/id                        "transaction-rule-id"
                                                                                                     :transaction-rule/note        "transaction rule note"
                                                                                                     :transaction-rule/description "matching-desc"
                                                                                                     :transaction-rule/accounts    [{:transaction-rule-account/location   "A"
                                                                                                                                     :transaction-rule-account/account    {:account/numeric-code 123 :db/id "123"}
                                                                                                                                     :transaction-rule-account/percentage 1.0}]}
                                                                                                    {:db/id                        "uneven-transaction-rule-id"
                                                                                                     :transaction-rule/note        "transaction rule note"
                                                                                                     :transaction-rule/description "matching-desc"
                                                                                                     :transaction-rule/accounts    [{:transaction-rule-account/location   "A"
                                                                                                                                     :transaction-rule-account/account    {:account/numeric-code 123 :db/id "123"}
                                                                                                                                     :transaction-rule-account/percentage 0.3333333}
                                                                                                                                    {:transaction-rule-account/location   "B"
                                                                                                                                     :transaction-rule-account/account    {:account/numeric-code 123 :db/id "123"}
                                                                                                                                     :transaction-rule-account/percentage 0.33333333}
                                                                                                                                    {:transaction-rule-account/location   "c"
                                                                                                                                     :transaction-rule-account/account    {:account/numeric-code 123 :db/id "123"}
                                                                                                                                     :transaction-rule-account/percentage 0.333333}]}])
                                                           :tempids)
          rule-test  (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :mutation
                                                                                      :operation/name "MatchTransactionRules"}
                                                                    :venia/queries [{:query/data (sut/->graphql [:match-transaction-rules
                                                                                                                 {:transaction-rule-id  transaction-rule-id
                                                                                                                  :transaction-ids [transaction-id]}
                                                                                                                 [[:matched-rule [:id :note]] [:accounts [:id]] ]])}]}))
                         :data
                         :match-transaction-rules)]

      (is (= "transaction rule note" (-> rule-test first :matched-rule :note)))
      (is (= 1 (-> rule-test first :accounts count)))

      (testing "Should replace accounts when matching a second time"
        (let [rule-test  (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :mutation
                                                                                          :operation/name "MatchTransactionRules"}
                                                                        :venia/queries [{:query/data (sut/->graphql [:match-transaction-rules
                                                                                                                     {:transaction-rule-id  transaction-rule-id
                                                                                                                      :transaction-ids [transaction-id]}
                                                                                                                     [[:matched-rule [:id :note]] [:accounts [:id]] ]])}]}))
                             :data
                             :match-transaction-rules)]
          (is (= 1 (-> rule-test first :accounts count)))))

      (testing "Should round when the transaction can't be divided eventy"
        (let [rule-test  (-> (sut/query (admin-token) (v/graphql-query {:venia/operation {:operation/type :mutation
                                                                                          :operation/name "MatchTransactionRules"}
                                                                        :venia/queries [{:query/data (sut/->graphql [:match-transaction-rules
                                                                                                                     {:transaction-rule-id  uneven-transaction-rule-id
                                                                                                                      :transaction-ids [transaction-id]}
                                                                                                                     [[:matched-rule [:id :note]] [:accounts [:id :amount]] ]])}]}))
                             :data
                             :match-transaction-rules)]
          (is (= 3 (-> rule-test first :accounts count)))
          (is (= "0.33" (-> rule-test first :accounts (nth 0) :amount)))
          (is (= "0.33" (-> rule-test first :accounts (nth 1) :amount)))
          (is (= "0.34" (-> rule-test first :accounts (nth 2) :amount))))))))


