package main

import (
	"context"
	"encoding/json"
	"fmt"

	gql "github.com/graph-gophers/graphql-go"
	"github.com/graph-gophers/graphql-go/directives"
)

// Made up schema: you can ask for a course, and if you are logged in, then you
// can ask if you are enrolled in that course.
var s = `
	directive @isAuthenticated on FIELD_DEFINITION

	type Query {
		course(name: String!): Course
	}
	type Course {
		name: String!
		enrolled: Boolean! @isAuthenticated
	}
`

type RootResolver struct{}
type QueryResolver struct{ user *User }
type CourseResolver struct {
	user            *User
	requestedCourse string
}

// NOTE THAT THIS DIRECTIVE THING CHANGED COMPARED WITH WHAT WE HAVE IN OUR REPO
// you will have to update it with `go get github.com/graph-gophers/graphql-go@master`
// and then you will have to change the directive to look like this,
// and change teh way we pass it in the shema options to match what is in the main fn
type IsAuthenticatedDirective struct{}

func (h *IsAuthenticatedDirective) Resolve(ctx context.Context, args interface{}, next directives.Resolver) (output any, err error) {
	_, ok := ctx.Value("currentUser").(*User)
	if !ok {
		return nil, fmt.Errorf("Unauthorized")
	}
	return next.Resolve(ctx, args)
}
func (h *IsAuthenticatedDirective) ImplementsDirective() string {
	return "isAuthenticated"
}

// users will just have a "set" of courses they're enrolled in
type User struct{ courses map[string]bool }

// This doesn't work for some reason, I want to grab the user from the context in the root resolver
// https://github.com/graph-gophers/graphql-go/blob/224841f523b3b94f1d75a0d428719a77b9bfc8a8/internal/exec/resolvable/resolvable.go#L198
//
//	func (r *RootResolver) Query(ctx context.Context) *QueryResolver {
//	  user, _ := ctx.Value("currentUser").(*User)
//	  return &QueryResolver{user: user}
//	}
func (r *RootResolver) Query() *QueryResolver {
	return &QueryResolver{user: nil}
}
func (r *QueryResolver) Course(args *struct{ Name string }) *CourseResolver {
	return &CourseResolver{user: r.user, requestedCourse: args.Name}
}
func (r *CourseResolver) Name() string {
	return r.requestedCourse
}

// Really stupid, it has to receive the context b/c the query resolver couldn't
// https://github.com/graph-gophers/graphql-go/blob/224841f523b3b94f1d75a0d428719a77b9bfc8a8/internal/exec/resolvable/resolvable.go#L198
// So now every authenticated field will have to pull the current user out of the
// context instead of being able to do that at the root query object.
func (r *CourseResolver) Enrolled(ctx context.Context) bool {
	// _, isEnrolled := r.user.courses[r.requestedCourse] // <-- what we want to do, we know the user is present b/c this field is authenticated
	user := ctx.Value("currentUser").(*User) // <-- what we have to do instead
	_, isEnrolled := user.courses[r.requestedCourse]
	return isEnrolled
}

func main() {
	opts := []gql.SchemaOpt{
		gql.UseFieldResolvers(),
		gql.Directives(&IsAuthenticatedDirective{}),
	}
	schema := gql.MustParseSchema(s, &RootResolver{}, opts...)

	run := func(user *User, query string) {
		ctx := context.Background()
		if user != nil {
			ctx = context.WithValue(ctx, "currentUser", user) // in the app, this comes from a middleware
		}
		response := schema.Exec(ctx, query, "", map[string]any{})
		json, _ := json.Marshal(response)
		fmt.Printf("response: %s\n", json)
	}

	u := &User{courses: map[string]bool{"course2": true}}
	run(u, `query { course(name: "course1") { name enrolled } }`)
	run(u, `query { course(name: "course2") { name enrolled } }`)
	run(u, `query { course(name: "course3") { name enrolled } }`)
	run(nil, `query { course(name: "course4") { name enrolled } }`)
	run(nil, `query { course(name: "course5") { name } }`)
}