package graphql_test

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
)

// GraphQLRequest represents a GraphQL request
type GraphQLRequest struct {
	Query         string                 `json:"query"`
	OperationName string                 `json:"operationName,omitempty"`
	Variables     map[string]interface{} `json:"variables,omitempty"`
}

// GraphQLResponse represents a generic GraphQL response
type GraphQLResponse[T any] struct {
	Data   T                        `json:"data,omitempty"`
	Errors []map[string]interface{} `json:"errors,omitempty"`
}

// graphQLTestServer defines the interface for a test server that can execute GraphQL requests.
type graphQLTestServer interface {
	getURL() string
	getClient() *http.Client
}

// executeGraphQL executes a GraphQL query against a test server and decodes the response.
func executeGraphQL[T any](s graphQLTestServer, query string, variables map[string]interface{}, token *string, ctx ...context.Context) (*GraphQLResponse[T], error) {
	request := GraphQLRequest{
		Query:     query,
		Variables: variables,
	}

	requestBody, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	var reqCtx context.Context
	if len(ctx) > 0 {
		reqCtx = ctx[0]
	} else {
		reqCtx = context.Background()
	}

	req, err := http.NewRequestWithContext(reqCtx, "POST", s.getURL(), bytes.NewBuffer(requestBody))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	if token != nil {
		req.Header.Set("Authorization", "Bearer "+*token)
	}

	resp, err := s.getClient().Do(req)
	if err != nil {
		return nil, err
	}
	defer func() { _ = resp.Body.Close() }()

	var response GraphQLResponse[T]
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return &response, nil
}

// Implement the graphQLTestServer interface for GraphQLIntegrationSuite
func (s *GraphQLIntegrationSuite) getURL() string {
	return s.server.URL
}

func (s *GraphQLIntegrationSuite) getClient() *http.Client {
	return s.client
}

// MockGraphQLServer provides a mock server for unit tests that don't require the full integration suite.
type MockGraphQLServer struct {
	Server *httptest.Server
	Client *http.Client
}

func NewMockGraphQLServer(h http.Handler) *MockGraphQLServer {
	ts := httptest.NewServer(h)
	return &MockGraphQLServer{
		Server: ts,
		Client: ts.Client(),
	}
}

func (s *MockGraphQLServer) getURL() string {
	return s.Server.URL
}

func (s *MockGraphQLServer) getClient() *http.Client {
	return s.Client
}

func (s *MockGraphQLServer) Close() {
	s.Server.Close()
}
