//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import XCTest
@testable import AWSAPIPlugin
@testable import Amplify
#if os(watchOS)
@testable import APIWatchApp
#else
@testable import APIHostApp
#endif

/*
 ```
 # 6 - Blog Post Comment
 type Blog6 @model {
   id: ID!
   name: String!
   posts: [Post6] @connection(keyName: "byBlog", fields: ["id"])
 }

 type Post6 @model @key(name: "byBlog", fields: ["blogID"]) {
   id: ID!
   title: String!
   blogID: ID!
   blog: Blog6 @connection(fields: ["blogID"])
   comments: [Comment6] @connection(keyName: "byPost", fields: ["id"])
 }

 type Comment6 @model @key(name: "byPost", fields: ["postID", "content"]) {
   id: ID!
   postID: ID!
   post: Post6 @connection(fields: ["postID"])
   content: String!
 }
 ```
 */
class GraphQLConnectionScenario6Tests: XCTestCase {

    override func setUp() {
        do {
            Amplify.Logging.logLevel = .verbose
            try Amplify.add(plugin: AWSAPIPlugin())

            let amplifyConfig = try TestConfigHelper.retrieveAmplifyConfiguration(
                forResource: GraphQLModelBasedTests.amplifyConfiguration)
            try Amplify.configure(amplifyConfig)

            ModelRegistry.register(modelType: Blog6.self)
            ModelRegistry.register(modelType: Post6.self)
            ModelRegistry.register(modelType: Comment6.self)

        } catch {
            XCTFail("Error during setup: \(error)")
        }
    }

    override func tearDown() async throws {
        await Amplify.reset()
    }

    func testGetBlogThenFetchPostsThenFetchComments() async throws {
        guard let blog = try await createBlog(name: "name"),
              let post1 = try await createPost(title: "title", blog: blog),
              try await createPost(title: "title", blog: blog) != nil,
              let comment1post1 = try await createComment(post: post1, content: "content"),
              let comment2post1 = try await createComment(post: post1, content: "content") else {
            XCTFail("Could not create blog, posts, and comments")
            return
        }
        let getBlogCompleted = expectation(description: "get blog complete")
        let fetchPostCompleted = expectation(description: "fetch post complete")
        var resultPosts: List<Post6>?
        let response = try await Amplify.API.query(request: .get(Blog6.self, byId: blog.id))
        switch response {
        case .success(let queriedBlogOptional):
            guard let queriedBlog = queriedBlogOptional else {
                XCTFail("Could not get blog")
                return
            }
            XCTAssertEqual(queriedBlog.id, blog.id)
            getBlogCompleted.fulfill()
            guard let posts = queriedBlog.posts else {
                XCTFail("Could not get comments")
                return
            }
            try await posts.fetch()
            resultPosts = posts
            fetchPostCompleted.fulfill()
        case .failure(let response): XCTFail("Failed with: \(response)")
        }
        wait(for: [getBlogCompleted, fetchPostCompleted], timeout: TestCommonConstants.networkTimeout)

        let allPosts = try await getAll(list: resultPosts)
        XCTAssertEqual(allPosts.count, 2)
        guard let fetchedPost = allPosts.first(where: { (post) -> Bool in
            post.id == post1.id
        }), let comments = fetchedPost.comments else {
            XCTFail("Could not set up - failed to get a post and its comments")
            return
        }

        let fetchCommentsCompleted = expectation(description: "fetch post complete")
        var resultComments: List<Comment6>?
        try await comments.fetch()
        resultComments = comments
        fetchCommentsCompleted.fulfill()
        wait(for: [fetchCommentsCompleted], timeout: TestCommonConstants.networkTimeout)
        let allComments = try await getAll(list: resultComments)
        XCTAssertEqual(allComments.count, 2)
        XCTAssertTrue(allComments.contains(where: { (comment) -> Bool in
            comment.id == comment1post1.id
        }))
        XCTAssertTrue(allComments.contains(where: { (comment) -> Bool in
            comment.id == comment2post1.id
        }))
    }

    func getAll<M>(list: List<M>?) async throws -> [M] {
        guard var list = list else {
            return []
        }
        var results = [M]()
        while list.hasNextPage() {
            let nextList = try await list.getNextPage()
            list = nextList
            results.append(contentsOf: nextList.elements)
        }
        return list.elements
    }

    func createBlog(id: String = UUID().uuidString, name: String) async throws -> Blog6? {
        let blog = Blog6(id: id, name: name)
        let data = try await Amplify.API.mutate(request: .create(blog))
        switch data {
        case .success(let post):
            return post
        case .failure(let error):
            throw error
        }
    }

    func createPost(id: String = UUID().uuidString, title: String, blog: Blog6) async throws -> Post6? {
        let post = Post6(id: id, title: title, blog: blog)
        let data = try await Amplify.API.mutate(request: .create(post))
        switch data {
        case .success(let post):
            return post
        case .failure(let error):
            throw error
        }
    }

    func createComment(id: String = UUID().uuidString, post: Post6, content: String) async throws -> Comment6? {
        let comment = Comment6(id: id, post: post, content: content)
        let data = try await Amplify.API.mutate(request: .create(comment))
        switch data {
        case .success(let comment):
            return comment
        case .failure(let error):
            throw error
        }
    }
}