/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package grails.test.app

import org.grails.gorm.graphql.plugin.testing.GraphQLSpec
import grails.testing.mixin.integration.Integration
import grails.testing.spock.OnceBefore
import org.grails.web.json.JSONArray
import org.grails.web.json.JSONObject
import org.spockframework.util.StringMessagePrintStream
import spock.lang.Shared
import spock.lang.Specification

@Integration
class TagIntegrationSpec extends Specification implements GraphQLSpec {

    @Shared Long grailsId

    @OnceBefore
    void createPosts() {
        def resp = graphQL.graphql("""
            mutation {
              postCreate(post: {
                title: "Grails 3.3 Release",
                tags: [
                  {name: "Grails"},
                  {name: "Groovy"},
                  {name: "Java"}
                ]
              }) {
                id
                tags {
                  id
                  name
                }
              }
            }
        """)
        List obj = resp.body.data.postCreate.tags
        def grails = obj.find { it.name == 'Grails' }.id
        grailsId = grails
        def groovy = obj.find { it.name == 'Groovy' }.id

        resp = graphQL.graphql("""
            mutation {
              postCreate(post: {
                title: "Grails 3.4 Release",
                tags: [
                  {id: ${grails}},
                  {id: ${groovy}},
                  {name: "Kotlin Ken"}
                ]
              }) {
                id
                tags {
                  id 
                  name
                }
              }
            }
        """)
        assert resp.body.data.postCreate.tags.size() == 3
    }

    void "test getting the count"() {
        when:
        def resp = graphQL.graphql("""
            {
              tagCount
            }
        """)
        def obj = resp.body.data.tagCount

        then:
        obj == 4
    }

    void "test a custom property can reference a domain"() {
        when:
        def resp = graphQL.graphql("""
            {
              tagList(sort: "id") {
                id
                name
                posts {
                  id
                }
              }
            }
        """)
        List obj = resp.body.data.tagList

        then:
        obj.size() == 4
        obj.find { it.name == 'Grails' }.posts.size() == 2
        obj.find { it.name == 'Groovy' }.posts.size() == 2
        obj.find { it.name == 'Java' }.posts.size() == 1
        obj.find { it.name == 'Kotlin Ken' }.posts.size() == 1
    }

    void "test a custom property can reference a domain with using joins"() {
        given:
        PrintStream originalOut = System.out
        List<String> queries = []
        System.setOut(new StringMessagePrintStream() {
            @Override
            protected void printed(String message) {
                queries.add(message)
            }
        })

        when:
        def resp = graphQL.graphql("""
            {
              tag(id: ${grailsId}) {
                id
                name
                posts {
                  id  
                  tags {
                    name
                  }
                }
              }
            }
        """)
        Map obj = resp.body.data.tag

        then:
        //queries.size() == 2 ignored due to GORM issue https://github.com/apache/grails-data-mapping/issues/989
        queries[0] ==~ 'Hibernate: select this_.id as id[0-9]+_[0-9]+_[0-9]+_, this_.version as version[0-9]+_[0-9]+_[0-9]+_, this_.name as name[0-9]+_[0-9]+_[0-9]+_ from tag this_ where this_.id=\\? limit \\?\n'
        queries[1] ==~ 'Hibernate: select this_.id as id[0-9]+_[0-9]+_[0-9]+_, this_.version as version[0-9]+_[0-9]+_[0-9]+_, this_.title as title[0-9]+_[0-9]+_[0-9]+_, this_.date_created as date_cre[0-9]+_[0-9]+_[0-9]+_, this_.last_updated as last_upd[0-9]+_[0-9]+_[0-9]+_, tags3_.post_tags_id as post_tag[0-9]+_[0-9]+_, tags_alias1_.id as tag_id[0-9]+_[0-9]+_, tags_alias1_.id as id[0-9]+_[0-9]+_[0-9]+_, tags_alias1_.version as version[0-9]+_[0-9]+_[0-9]+_, tags_alias1_.name as name[0-9]+_[0-9]+_[0-9]+_ from post this_ inner join post_tag tags3_ on this_.id=tags3_.post_tags_id inner join tag tags_alias1_ on tags3_.tag_id=tags_alias1_.id where tags_alias1_.id=\\?\n'

        cleanup:
        System.setOut(originalOut)
    }

    void "test optimistic locking"() {
        when:
        def resp = graphQL.graphql("""
            mutation {
              tagUpdate(id: ${grailsId}, tag: {version: -1, name: "Grails 3"}) {
                id
                name
                errors {
                  field 
                  message
                }  
              }
            }
        """)
        Map obj = resp.body.data.tagUpdate

        then:
        obj.id == grailsId
        obj.name == "Grails" //Updated data not bound
        obj.errors.size() == 1
        obj.errors[0].field == "version"
        obj.errors[0].message == "Another user has updated this Tag while you were editing"
    }

    void cleanupSpec() {
        // Cleanup is best-effort: integration tests share the H2 database with other
        // specs (e.g. PostIntegrationSpec) so a stricter assertion on counts would
        // make this spec depend on test execution order.
        def resp = graphQL.graphql("""
            { 
              postList {
                id
              }
            }
        """)
        resp.body.data.postList.each {
            graphQL.graphql("""
              mutation {
                postDelete(id: ${it.id}) {
                  success
                }
              }
            """)
        }
        resp = graphQL.graphql("""
            { 
              tagList {
                id
              }
            }
        """)
        resp.body.data.tagList.each {
            graphQL.graphql("""
              mutation {
                tagDelete(id: ${it.id}) {
                  success
                }
              }
            """)
        }
    }
}
