package graphql_test

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	graph "tercul/internal/adapters/graphql"
	"tercul/internal/adapters/graphql/model"
	"tercul/internal/app/auth"
	"tercul/internal/app/author"
	"tercul/internal/app/bookmark"
	"tercul/internal/app/collection"
	"tercul/internal/app/comment"
	"tercul/internal/app/like"
	"tercul/internal/app/translation"
	"tercul/internal/domain"
	"tercul/internal/observability"
	platform_auth "tercul/internal/platform/auth"
	platform_config "tercul/internal/platform/config"
	"tercul/internal/testutil"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/suite"
)

// GraphQLIntegrationSuite is a test suite for GraphQL integration tests
type GraphQLIntegrationSuite struct {
	testutil.IntegrationTestSuite
	server *httptest.Server
	client *http.Client
}

func (s *GraphQLIntegrationSuite) CreateAuthenticatedUser(username, email string, role domain.UserRole) (*domain.User, string) {
	// Password can be fixed for tests
	password := "password123"

	// Register user
	registerInput := auth.RegisterInput{
		Username: username,
		Email:    email,
		Password: password,
	}
	authResponse, err := s.App.Auth.Commands.Register(context.Background(), registerInput)
	s.Require().NoError(err)
	s.Require().NotNil(authResponse)

	// Update user role if necessary
	user := authResponse.User
	token := authResponse.Token
	if user.Role != role {
		// This part is tricky. There is no UpdateUserRole command.
		// For a test, I can update the DB directly.
		s.DB.Model(&domain.User{}).Where("id = ?", user.ID).Update("role", role)
		user.Role = role

		// Re-generate token with the new role
		cfg, err := platform_config.LoadConfig()
		s.Require().NoError(err)
		jwtManager := platform_auth.NewJWTManager(cfg)
		newToken, err := jwtManager.GenerateToken(user)
		s.Require().NoError(err)
		token = newToken
	}

	return user, token
}

// SetupSuite sets up the test suite
func (s *GraphQLIntegrationSuite) SetupSuite() {
	s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())

	// Create GraphQL server with the test resolver
	resolver := &graph.Resolver{App: s.App}
	c := graph.Config{Resolvers: resolver}
	c.Directives.Binding = graph.Binding // Register the binding directive

	// Create the server with the default handler and custom error presenter
	//nolint:staticcheck // NewDefaultServer is deprecated but required for custom error presenter
	srv := handler.NewDefaultServer(graph.NewExecutableSchema(c))
	srv.SetErrorPresenter(graph.NewErrorPresenter())

	// Create JWT manager and middleware
	cfg, err := platform_config.LoadConfig()
	s.Require().NoError(err)
	jwtManager := platform_auth.NewJWTManager(cfg)
	reg := prometheus.NewRegistry()
	metrics := observability.NewMetrics(reg)

	// Create a middleware chain
	var chain http.Handler
	chain = srv
	chain = platform_auth.GraphQLAuthMiddleware(jwtManager)(chain)
	chain = metrics.PrometheusMiddleware(chain)
	chain = observability.TracingMiddleware(chain)
	chain = observability.RequestIDMiddleware(chain)

	s.server = httptest.NewServer(chain)
	s.client = s.server.Client()
}

// TearDownSuite tears down the test suite
func (s *GraphQLIntegrationSuite) TearDownSuite() {
	s.IntegrationTestSuite.TearDownSuite()
	s.server.Close()
}

// SetupTest sets up each test
func (s *GraphQLIntegrationSuite) SetupTest() {
	s.IntegrationTestSuite.SetupTest()
	s.DB.Exec("DELETE FROM trendings")
}

type GetWorkResponse struct {
	Work struct {
		ID       string `json:"id"`
		Name     string `json:"name"`
		Language string `json:"language"`
		Content  string `json:"content"`
	} `json:"work"`
}

// TestQueryWork tests the work query
func (s *GraphQLIntegrationSuite) TestQueryWork() {
	// Create a test work with content
	work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content for work")

	// Define the query
	query := `
		query GetWork($id: ID!) {
			work(id: $id) {
				id
				name
				language
				content
			}
		}
	`

	// Define the variables
	variables := map[string]interface{}{
		"id": fmt.Sprintf("%d", work.ID),
	}

	// Execute the query
	response, err := executeGraphQL[GetWorkResponse](s, query, variables, nil)
	s.Require().NoError(err)
	s.Require().NotNil(response)
	s.Require().Nil(response.Errors, "GraphQL query should not return errors")

	// Verify the response
	s.Equal("Test Work", response.Data.Work.Name, "Work name should match")
	s.Equal("Test content for work", response.Data.Work.Content, "Work content should match")
	s.Equal("en", response.Data.Work.Language, "Work language should match")
}

type GetWorksResponse struct {
	Works []struct {
		ID       string `json:"id"`
		Name     string `json:"name"`
		Language string `json:"language"`
		Content  string `json:"content"`
	} `json:"works"`
}

// TestQueryWorks tests the works query
func (s *GraphQLIntegrationSuite) TestQueryWorks() {
	// Create test works
	s.CreateTestWork(s.AdminCtx, "Test Work 1", "en", "Test content for work 1")
	s.CreateTestWork(s.AdminCtx, "Test Work 2", "en", "Test content for work 2")
	s.CreateTestWork(s.AdminCtx, "Test Work 3", "fr", "Test content for work 3")

	// Define the query
	query := `
		query GetWorks {
			works {
				id
				name
				language
				content
			}
		}
	`

	// Execute the query
	response, err := executeGraphQL[GetWorksResponse](s, query, nil, nil)
	s.Require().NoError(err)
	s.Require().NotNil(response)
	s.Require().Nil(response.Errors, "GraphQL query should not return errors")

	// Verify the response
	s.True(len(response.Data.Works) >= 3, "GraphQL response should contain at least 3 works")

	// Verify each work
	foundWork1 := false
	foundWork2 := false
	foundWork3 := false

	for _, work := range response.Data.Works {
		switch work.Name {
		case "Test Work 1":
			foundWork1 = true
			s.Equal("en", work.Language, "Work 1 language should match")
		case "Test Work 2":
			foundWork2 = true
			s.Equal("en", work.Language, "Work 2 language should match")
		case "Test Work 3":
			foundWork3 = true
			s.Equal("fr", work.Language, "Work 3 language should match")
		}
	}

	s.True(foundWork1, "GraphQL response should contain work 1")
	s.True(foundWork2, "GraphQL response should contain work 2")
	s.True(foundWork3, "GraphQL response should contain work 3")
}

type CreateWorkResponse struct {
	CreateWork struct {
		ID       string `json:"id"`
		Name     string `json:"name"`
		Language string `json:"language"`
		Content  string `json:"content"`
	} `json:"createWork"`
}

// TestCreateWork tests the createWork mutation
func (s *GraphQLIntegrationSuite) TestCreateWork() {
	// Define the mutation
	mutation := `
		mutation CreateWork($input: WorkInput!) {
			createWork(input: $input) {
				id
				name
				language
				content
			}
		}
	`

	// Define the variables
	variables := map[string]interface{}{
		"input": map[string]interface{}{
			"name":     "New Test Work",
			"language": "en",
			"content":  "New test content",
		},
	}

	// Execute the mutation
	response, err := executeGraphQL[CreateWorkResponse](s, mutation, variables, &s.AdminToken)
	s.Require().NoError(err)
	s.Require().NotNil(response)
	s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

	// Verify the response
	s.NotNil(response.Data.CreateWork.ID, "Work ID should not be nil")
	s.Equal("New Test Work", response.Data.CreateWork.Name, "Work name should match")
	s.Equal("en", response.Data.CreateWork.Language, "Work language should match")
	s.Equal("New test content", response.Data.CreateWork.Content, "Work content should match")

	// Verify that the work was created in the repository
	workID, err := strconv.ParseUint(response.Data.CreateWork.ID, 10, 64)
	s.Require().NoError(err)
	createdWork, err := s.App.Work.Queries.GetWorkByID(context.Background(), uint(workID))
	s.Require().NoError(err)
	s.Require().NotNil(createdWork)
	s.Equal("New Test Work", createdWork.Title)
	s.Equal("en", createdWork.Language)
	translations, err := s.App.Translation.Queries.TranslationsByWorkID(context.Background(), createdWork.ID)
	s.Require().NoError(err)
	s.Require().Len(translations, 1)
	s.Equal("New test content", translations[0].Content)
}

// TestGraphQLIntegrationSuite runs the test suite
func (s *GraphQLIntegrationSuite) TestRegisterValidation() {
	s.Run("should return error for invalid input", func() {
		// Define the mutation
		mutation := `
			mutation Register($input: RegisterInput!) {
				register(input: $input) {
					token
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"username":  "a", // Too short
				"email":     "invalid-email",
				"password":  "short",
				"firstName": "123",
				"lastName":  "456",
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, nil)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestLoginValidation() {
	s.Run("should return error for invalid input", func() {
		// Define the mutation
		mutation := `
			mutation Login($input: LoginInput!) {
				login(input: $input) {
					token
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"email":    "invalid-email",
				"password": "short",
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, nil)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestCreateWorkValidation() {
	s.Run("should return error for invalid input", func() {
		// Define the mutation
		mutation := `
			mutation CreateWork($input: WorkInput!) {
				createWork(input: $input) {
					id
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"name":     "a",     // Too short
				"language": "en-US", // Not 2 chars
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestUpdateWorkValidation() {
	s.Run("should return error for invalid input", func() {
		// Arrange
		work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content")

		// Define the mutation
		mutation := `
			mutation UpdateWork($id: ID!, $input: WorkInput!) {
				updateWork(id: $id, input: $input) {
					id
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", work.ID),
			"input": map[string]interface{}{
				"name":     "a",     // Too short
				"language": "en-US", // Not 2 chars
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestCreateAuthorValidation() {
	s.Run("should return error for invalid input", func() {
		// Define the mutation
		mutation := `
			mutation CreateAuthor($input: AuthorInput!) {
				createAuthor(input: $input) {
					id
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"name":     "a",     // Too short
				"language": "en-US", // Not 2 chars
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestUpdateAuthorValidation() {
	s.Run("should return error for invalid input", func() {
		// Arrange
		createdAuthor, err := s.App.Author.Commands.CreateAuthor(context.Background(), author.CreateAuthorInput{Name: "Test Author"})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation UpdateAuthor($id: ID!, $input: AuthorInput!) {
				updateAuthor(id: $id, input: $input) {
					id
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdAuthor.ID),
			"input": map[string]interface{}{
				"name":     "a",     // Too short
				"language": "en-US", // Not 2 chars
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestCreateTranslationValidation() {
	s.Run("should return error for invalid input", func() {
		// Arrange
		work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content")

		// Define the mutation
		mutation := `
			mutation CreateTranslation($input: TranslationInput!) {
				createTranslation(input: $input) {
					id
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"name":     "a",     // Too short
				"language": "en-US", // Not 2 chars
				"workId":   fmt.Sprintf("%d", work.ID),
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestUpdateTranslationValidation() {
	s.Run("should return error for invalid input", func() {
		// Arrange
		work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content")
		createdTranslation, err := s.App.Translation.Commands.CreateOrUpdateTranslation(s.AdminCtx, translation.CreateOrUpdateTranslationInput{
			Title:            "Test Translation",
			Language:         "en",
			Content:          "Test content",
			TranslatableID:   work.ID,
			TranslatableType: "works",
		})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation UpdateTranslation($id: ID!, $input: TranslationInput!) {
				updateTranslation(id: $id, input: $input) {
					id
				}
			}
		`

		// Define the variables with invalid input
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdTranslation.ID),
			"input": map[string]interface{}{
				"name":     "a",     // Too short
				"language": "en-US", // Not 2 chars
				"workId":   fmt.Sprintf("%d", work.ID),
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
		s.Len(response.Errors, 1)
	})
}

func (s *GraphQLIntegrationSuite) TestDeleteWork() {
	s.Run("should delete a work", func() {
		// Arrange
		work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content")

		// Define the mutation
		mutation := `
			mutation DeleteWork($id: ID!) {
				deleteWork(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", work.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
		s.Require().NotNil(response.Data)
		s.True(response.Data.(map[string]interface{})["deleteWork"].(bool))

		// Verify that the work was actually deleted from the database
		_, err = s.App.Work.Queries.GetWorkByID(context.Background(), work.ID)
		s.Require().Error(err)
	})
}

func (s *GraphQLIntegrationSuite) TestDeleteAuthor() {
	s.Run("should delete an author", func() {
		// Arrange
		createdAuthor, err := s.App.Author.Commands.CreateAuthor(context.Background(), author.CreateAuthorInput{Name: "Test Author"})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation DeleteAuthor($id: ID!) {
				deleteAuthor(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdAuthor.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
		s.Require().NotNil(response.Data)
		s.True(response.Data.(map[string]interface{})["deleteAuthor"].(bool))

		// Verify that the author was actually deleted from the database
		_, err = s.App.Author.Queries.Author(context.Background(), createdAuthor.ID)
		s.Require().Error(err)
	})
}

func (s *GraphQLIntegrationSuite) TestDeleteTranslation() {
	s.Run("should delete a translation", func() {
		// Arrange
		work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content")
		createdTranslation, err := s.App.Translation.Commands.CreateOrUpdateTranslation(s.AdminCtx, translation.CreateOrUpdateTranslationInput{
			Title:            "Test Translation",
			Language:         "en",
			Content:          "Test content",
			TranslatableID:   work.ID,
			TranslatableType: "works",
		})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation DeleteTranslation($id: ID!) {
				deleteTranslation(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdTranslation.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
		s.Require().NotNil(response.Data)
		s.True(response.Data.(map[string]interface{})["deleteTranslation"].(bool))

		// Verify that the translation was actually deleted from the database
		_, err = s.App.Translation.Queries.Translation(context.Background(), createdTranslation.ID)
		s.Require().Error(err)
	})
}

func TestGraphQLIntegrationSuite(t *testing.T) {
	testutil.SkipIfShort(t)
	suite.Run(t, new(GraphQLIntegrationSuite))
}

func (s *GraphQLIntegrationSuite) TestBookMutations() {
	// Create users for testing authorization
	_, readerToken := s.CreateAuthenticatedUser("bookreader", "bookreader@test.com", domain.UserRoleReader)

	var bookID string

	s.Run("a reader can create a book", func() {
		// Define the mutation
		mutation := `
			mutation CreateBook($input: BookInput!) {
				createBook(input: $input) {
					id
					name
					description
					language
					isbn
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"name":        "My New Book",
				"description": "A book about something.",
				"language":    "en",
				"isbn":        "978-3-16-148410-0",
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[CreateBookResponse](s, mutation, variables, &readerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		s.NotNil(response.Data.CreateBook.ID, "Book ID should not be nil")
		bookID = response.Data.CreateBook.ID
		s.Equal("My New Book", response.Data.CreateBook.Name)
		s.Equal("A book about something.", *response.Data.CreateBook.Description)
		s.Equal("en", response.Data.CreateBook.Language)
		s.Equal("978-3-16-148410-0", *response.Data.CreateBook.Isbn)
	})

	s.Run("a reader is forbidden from updating a book", func() {
		// Define the mutation
		mutation := `
			mutation UpdateBook($id: ID!, $input: BookInput!) {
				updateBook(id: $id, input: $input) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": bookID,
			"input": map[string]interface{}{
				"name":     "Updated Book Name",
				"language": "en",
			},
		}

		// Execute the mutation with the reader's token
		response, err := executeGraphQL[any](s, mutation, variables, &readerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response.Errors)
	})

	s.Run("an admin can update a book", func() {
		// Define the mutation
		mutation := `
			mutation UpdateBook($id: ID!, $input: BookInput!) {
				updateBook(id: $id, input: $input) {
					id
					name
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": bookID,
			"input": map[string]interface{}{
				"name":     "Updated Book Name by Admin",
				"language": "en",
			},
		}

		// Execute the mutation with the admin's token
		response, err := executeGraphQL[UpdateBookResponse](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
	})

	s.Run("a reader is forbidden from deleting a book", func() {
		// Define the mutation
		mutation := `
			mutation DeleteBook($id: ID!) {
				deleteBook(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": bookID,
		}

		// Execute the mutation with the reader's token
		response, err := executeGraphQL[any](s, mutation, variables, &readerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response.Errors)
	})

	s.Run("an admin can delete a book", func() {
		// Define the mutation
		mutation := `
			mutation DeleteBook($id: ID!) {
				deleteBook(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": bookID,
		}

		// Execute the mutation with the admin's token
		response, err := executeGraphQL[any](s, mutation, variables, &s.AdminToken)
		s.Require().NoError(err)
		s.Require().Nil(response.Errors)
		s.True(response.Data.(map[string]interface{})["deleteBook"].(bool))
	})
}

func (s *GraphQLIntegrationSuite) TestBookQueries() {
	// Create a book to query
	createMutation := `
		mutation CreateBook($input: BookInput!) {
			createBook(input: $input) {
				id
			}
		}
	`
	createVariables := map[string]interface{}{
		"input": map[string]interface{}{
			"name":        "Queryable Book",
			"description": "A book to be queried.",
			"language":    "en",
			"isbn":        "978-0-306-40615-7",
		},
	}
	createResponse, err := executeGraphQL[CreateBookResponse](s, createMutation, createVariables, &s.AdminToken)
	s.Require().NoError(err)
	bookID := createResponse.Data.CreateBook.ID

	s.Run("should get a book by ID", func() {
		// Define the query
		query := `
			query GetBook($id: ID!) {
				book(id: $id) {
					id
					name
					description
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": bookID,
		}

		// Execute the query
		response, err := executeGraphQL[GetBookResponse](s, query, variables, nil)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL query should not return errors")

		// Verify the response
		s.Equal(bookID, response.Data.Book.ID)
		s.Equal("Queryable Book", response.Data.Book.Name)
		s.Equal("A book to be queried.", *response.Data.Book.Description)
	})

	s.Run("should get a list of books", func() {
		// Define the query
		query := `
			query GetBooks {
				books {
					id
					name
				}
			}
		`

		// Execute the query
		response, err := executeGraphQL[GetBooksResponse](s, query, nil, nil)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL query should not return errors")

		// Verify the response
		s.True(len(response.Data.Books) >= 1)
		foundBook := false
		for _, book := range response.Data.Books {
			if book.ID == bookID {
				foundBook = true
				break
			}
		}
		s.True(foundBook, "The created book should be in the list")
	})
}

type CreateBookResponse struct {
	CreateBook model.Book `json:"createBook"`
}

type GetBookResponse struct {
	Book model.Book `json:"book"`
}

type GetBooksResponse struct {
	Books []model.Book `json:"books"`
}

type UpdateBookResponse struct {
	UpdateBook model.Book `json:"updateBook"`
}

type CreateCollectionResponse struct {
	CreateCollection struct {
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"createCollection"`
}

type UpdateCollectionResponse struct {
	UpdateCollection struct {
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"updateCollection"`
}

type AddWorkToCollectionResponse struct {
	AddWorkToCollection struct {
		ID string `json:"id"`
	} `json:"addWorkToCollection"`
}

type RemoveWorkFromCollectionResponse struct {
	RemoveWorkFromCollection struct {
		ID string `json:"id"`
	} `json:"removeWorkFromCollection"`
}

func (s *GraphQLIntegrationSuite) TestCommentMutations() {
	// Create users for testing authorization
	commenter, commenterToken := s.CreateAuthenticatedUser("commenter", "commenter@test.com", domain.UserRoleReader)
	otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
	_ = otherUser

	// Create a work to comment on
	work := s.CreateTestWork(s.AdminCtx, "Commentable Work", "en", "Some content")

	var commentID string

	s.Run("should create a comment on a work", func() {
		// Define the mutation
		mutation := `
			mutation CreateComment($input: CommentInput!) {
				createComment(input: $input) {
					id
					text
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"text":   "This is a test comment.",
				"workId": fmt.Sprintf("%d", work.ID),
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &commenterToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		commentData := response.Data.(map[string]interface{})["createComment"].(map[string]interface{})
		s.NotNil(commentData["id"], "Comment ID should not be nil")
		commentID = commentData["id"].(string)
		s.Equal("This is a test comment.", commentData["text"])
	})

	s.Run("should update a comment", func() {
		// Define the mutation
		mutation := `
			mutation UpdateComment($id: ID!, $input: CommentInput!) {
				updateComment(id: $id, input: $input) {
					id
					text
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": commentID,
			"input": map[string]interface{}{
				"text": "This is an updated comment.",
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &commenterToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		commentData := response.Data.(map[string]interface{})["updateComment"].(map[string]interface{})
		s.Equal("This is an updated comment.", commentData["text"])
	})

	s.Run("should not update a comment owned by another user", func() {
		// Define the mutation
		mutation := `
			mutation UpdateComment($id: ID!, $input: CommentInput!) {
				updateComment(id: $id, input: $input) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": commentID,
			"input": map[string]interface{}{
				"text": "Attempted Takeover",
			},
		}

		// Execute the mutation with the other user's token
		response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
		s.Require().NoError(err)
		s.Require().NotNil(response.Errors)
	})

	s.Run("should delete a comment", func() {
		// Create a new comment to delete
		createdComment, err := s.App.Comment.Commands.CreateComment(context.Background(), comment.CreateCommentInput{
			Text:   "to be deleted",
			UserID: commenter.ID,
			WorkID: &work.ID,
		})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation DeleteComment($id: ID!) {
				deleteComment(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdComment.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &commenterToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
		s.True(response.Data.(map[string]interface{})["deleteComment"].(bool))
	})
}

func (s *GraphQLIntegrationSuite) TestLikeMutations() {
	// Create users for testing authorization
	liker, likerToken := s.CreateAuthenticatedUser("liker", "liker@test.com", domain.UserRoleReader)
	otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
	_ = otherUser

	// Create a work to like
	work := s.CreateTestWork(s.AdminCtx, "Likeable Work", "en", "Some content")

	var likeID string

	s.Run("should create a like on a work", func() {
		// Define the mutation
		mutation := `
			mutation CreateLike($input: LikeInput!) {
				createLike(input: $input) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"workId": fmt.Sprintf("%d", work.ID),
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &likerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		likeData := response.Data.(map[string]interface{})["createLike"].(map[string]interface{})
		s.NotNil(likeData["id"], "Like ID should not be nil")
		likeID = likeData["id"].(string)
	})

	s.Run("should not delete a like owned by another user", func() {
		// Create a like by the original user
		createdLike, err := s.App.Like.Commands.CreateLike(context.Background(), like.CreateLikeInput{
			UserID: liker.ID,
			WorkID: &work.ID,
		})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation DeleteLike($id: ID!) {
				deleteLike(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdLike.ID),
		}

		// Execute the mutation with the other user's token
		response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
		s.Require().NoError(err)
		s.Require().NotNil(response.Errors)
	})

	s.Run("should delete a like", func() {
		// Use the likeID from the create test
		// Define the mutation
		mutation := `
			mutation DeleteLike($id: ID!) {
				deleteLike(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": likeID,
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &likerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
		s.True(response.Data.(map[string]interface{})["deleteLike"].(bool))
	})
}

func (s *GraphQLIntegrationSuite) TestBookmarkMutations() {
	// Create users for testing authorization
	bookmarker, bookmarkerToken := s.CreateAuthenticatedUser("bookmarker", "bookmarker@test.com", domain.UserRoleReader)
	otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
	_ = otherUser

	// Create a work to bookmark
	work := s.CreateTestWork(s.AdminCtx, "Bookmarkable Work", "en", "Some content")

	s.Run("should create a bookmark on a work", func() {
		// Define the mutation
		mutation := `
			mutation CreateBookmark($input: BookmarkInput!) {
				createBookmark(input: $input) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"workId": fmt.Sprintf("%d", work.ID),
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &bookmarkerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		bookmarkData := response.Data.(map[string]interface{})["createBookmark"].(map[string]interface{})
		s.NotNil(bookmarkData["id"], "Bookmark ID should not be nil")

		// Cleanup
		bookmarkID, err := strconv.ParseUint(bookmarkData["id"].(string), 10, 32)
		s.Require().NoError(err)
		err = s.App.Bookmark.Commands.DeleteBookmark(context.Background(), uint(bookmarkID))
		s.Require().NoError(err)
	})

	s.Run("should not delete a bookmark owned by another user", func() {
		// Create a bookmark by the original user
		createdBookmark, err := s.App.Bookmark.Commands.CreateBookmark(context.Background(), bookmark.CreateBookmarkInput{
			UserID: bookmarker.ID,
			WorkID: work.ID,
			Name:   "A Bookmark",
		})
		s.Require().NoError(err)
		s.T().Cleanup(func() {
			err := s.App.Bookmark.Commands.DeleteBookmark(context.Background(), createdBookmark.ID)
			s.Require().NoError(err)
		})

		// Define the mutation
		mutation := `
			mutation DeleteBookmark($id: ID!) {
				deleteBookmark(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdBookmark.ID),
		}

		// Execute the mutation with the other user's token
		response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
		s.Require().NoError(err)
		s.Require().NotNil(response.Errors)
	})

	s.Run("should delete a bookmark", func() {
		// Create a new bookmark to delete
		createdBookmark, err := s.App.Bookmark.Commands.CreateBookmark(context.Background(), bookmark.CreateBookmarkInput{
			UserID: bookmarker.ID,
			WorkID: work.ID,
			Name:   "To Be Deleted",
		})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation DeleteBookmark($id: ID!) {
				deleteBookmark(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": fmt.Sprintf("%d", createdBookmark.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &bookmarkerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
		s.True(response.Data.(map[string]interface{})["deleteBookmark"].(bool))
	})
}

type TrendingWorksResponse struct {
	TrendingWorks []struct {
		ID   string `json:"id"`
		Name string `json:"name"`
	} `json:"trendingWorks"`
}

func (s *GraphQLIntegrationSuite) TestTrendingWorksQuery() {
	s.Run("should return a list of trending works", func() {
		// Arrange
		work1 := s.CreateTestWork(s.AdminCtx, "Work 1", "en", "content")
		work2 := s.CreateTestWork(s.AdminCtx, "Work 2", "en", "content")
		s.DB.Create(&domain.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1})
		s.DB.Create(&domain.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10})
		s.Require().NoError(s.App.Analytics.UpdateTrending(context.Background()))

		// Act
		query := `
			query GetTrendingWorks {
				trendingWorks {
					id
					name
				}
			}
		`
		response, err := executeGraphQL[TrendingWorksResponse](s, query, nil, nil)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL query should not return errors")

		// Assert
		s.Len(response.Data.TrendingWorks, 2)
		s.Equal(fmt.Sprintf("%d", work2.ID), response.Data.TrendingWorks[0].ID)
	})
}

func (s *GraphQLIntegrationSuite) TestCollectionMutations() {
	// Create users for testing authorization
	owner, ownerToken := s.CreateAuthenticatedUser("collectionowner", "owner@test.com", domain.UserRoleReader)
	otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
	_ = otherUser

	var collectionID string

	s.Run("should create a collection", func() {
		// Define the mutation
		mutation := `
			mutation CreateCollection($input: CollectionInput!) {
				createCollection(input: $input) {
					id
					name
					description
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"input": map[string]interface{}{
				"name":        "My New Collection",
				"description": "A collection of my favorite works.",
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[CreateCollectionResponse](s, mutation, variables, &ownerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		s.NotNil(response.Data.CreateCollection.ID, "Collection ID should not be nil")
		collectionID = response.Data.CreateCollection.ID // Save for later tests
		s.Equal("My New Collection", response.Data.CreateCollection.Name, "Collection name should match")
		s.Equal("A collection of my favorite works.", response.Data.CreateCollection.Description, "Collection description should match")
	})

	s.Run("should update a collection", func() {
		// Define the mutation
		mutation := `
			mutation UpdateCollection($id: ID!, $input: CollectionInput!) {
				updateCollection(id: $id, input: $input) {
					id
					name
					description
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": collectionID,
			"input": map[string]interface{}{
				"name":        "My Updated Collection",
				"description": "An updated description.",
			},
		}

		// Execute the mutation
		response, err := executeGraphQL[UpdateCollectionResponse](s, mutation, variables, &ownerToken)
		s.Require().NoError(err)
		s.Require().NotNil(response)
		s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")

		// Verify the response
		s.Equal("My Updated Collection", response.Data.UpdateCollection.Name)
	})

	s.Run("should not update a collection owned by another user", func() {
		// Define the mutation
		mutation := `
			mutation UpdateCollection($id: ID!, $input: CollectionInput!) {
				updateCollection(id: $id, input: $input) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": collectionID,
			"input": map[string]interface{}{
				"name": "Attempted Takeover",
			},
		}

		// Execute the mutation with the other user's token
		response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
		s.Require().NoError(err)
		s.Require().NotNil(response.Errors)
	})

	s.Run("should add a work to a collection", func() {
		// Create a work
		work := s.CreateTestWork(s.AdminCtx, "Test Work", "en", "Test content")

		// Define the mutation
		mutation := `
			mutation AddWorkToCollection($collectionId: ID!, $workId: ID!) {
				addWorkToCollection(collectionId: $collectionId, workId: $workId) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"collectionId": collectionID,
			"workId":       fmt.Sprintf("%d", work.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[AddWorkToCollectionResponse](s, mutation, variables, &ownerToken)
		s.Require().NoError(err)
		s.Require().Nil(response.Errors)
	})

	s.Run("should remove a work from a collection", func() {
		// Create a work and add it to the collection first
		work := s.CreateTestWork(s.AdminCtx, "Another Work", "en", "Some content")
		collectionIDInt, err := strconv.ParseUint(collectionID, 10, 64)
		s.Require().NoError(err)
		err = s.App.Collection.Commands.AddWorkToCollection(context.Background(), collection.AddWorkToCollectionInput{
			CollectionID: uint(collectionIDInt),
			WorkID:       work.ID,
			UserID:       owner.ID,
		})
		s.Require().NoError(err)

		// Define the mutation
		mutation := `
			mutation RemoveWorkFromCollection($collectionId: ID!, $workId: ID!) {
				removeWorkFromCollection(collectionId: $collectionId, workId: $workId) {
					id
				}
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"collectionId": collectionID,
			"workId":       fmt.Sprintf("%d", work.ID),
		}

		// Execute the mutation
		response, err := executeGraphQL[RemoveWorkFromCollectionResponse](s, mutation, variables, &ownerToken)
		s.Require().NoError(err)
		s.Require().Nil(response.Errors)
	})

	s.Run("should delete a collection", func() {
		// Define the mutation
		mutation := `
			mutation DeleteCollection($id: ID!) {
				deleteCollection(id: $id)
			}
		`

		// Define the variables
		variables := map[string]interface{}{
			"id": collectionID,
		}

		// Execute the mutation
		response, err := executeGraphQL[any](s, mutation, variables, &ownerToken)
		s.Require().NoError(err)
		s.Require().Nil(response.Errors)
		s.True(response.Data.(map[string]interface{})["deleteCollection"].(bool))
	})
}
