package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.66

import (
	"context"
	"errors"

	"github.com/google/uuid"
	"github.com/gqlgen/_examples/mini-habr-with-subscriptions/graph/model"
	internalmodel "github.com/gqlgen/_examples/mini-habr-with-subscriptions/internal/model"
	"github.com/gqlgen/_examples/mini-habr-with-subscriptions/internal/pkg/cursor"
	"github.com/gqlgen/_examples/mini-habr-with-subscriptions/internal/pkg/errs"
	"github.com/rs/zerolog/log"
)

// Replies is the resolver for the replies field.
func (r *commentResolver) Replies(ctx context.Context, obj *model.Comment, first *int32, after *string) (*model.CommentConnection, error) {
	const op = "graph.Replies()"

	log.Debug().Msgf("%s start", op)

	var path string
	var err error
	if after != nil {
		err = cursor.ValidateAfter(after)
		if err != nil {
			return nil, errors.New("invalid after")
		}

		path, err = cursor.GetPath(after)
		if err != nil {
			return nil, errors.New("invalid after")
		}
	} else {
		path, err = r.CommentQuery.GetPathToComments(obj.ID)
		if err != nil {
			if err != errs.ErrCommentsNotExist {
				err = errors.New("internal server error")
			}

			return nil, err
		}
	}

	internalCommentsBranch, err := r.CommentQuery.GetCommentsBranchToPost(obj.PostID, path)
	if err != nil {
		if err != errs.ErrCommentsNotExist {
			if err == errs.ErrPathNotExist {
				return nil, nil // it works when there are no replies to the comment.
			}
			err = errors.New("internal server error")
		}
		return nil, err

	}

	commentBranch, err := paginateInternalBranch(internalCommentsBranch, first, after)

	log.Debug().Msgf("%s end", op)

	return commentBranch, nil
}

// AddPost is the resolver for the addPost field.
func (r *mutationResolver) AddPost(ctx context.Context, postInput model.NewPost) (*model.Post, error) {
	const op = "graph.AddPost()"

	log.Debug().Msgf("%s start", op)

	newPost := newPostToInternalModel(&postInput)
	post, err := r.PostMutation.AddPost(newPost)

	if err != nil {
		log.Error().Err(err).Msgf("%s end with error", op)

		return nil, errors.New("internal server error")
	}

	log.Debug().Msgf("%s end", op)
	return postFromInternalModel(post), err
}

func newPostToInternalModel(post *model.NewPost) *internalmodel.NewPost {
	return &internalmodel.NewPost{
		AuthorID:        post.AuthorID,
		Title:           post.Title,
		Text:            post.Text,
		CommentsEnabled: post.CommentsEnabled,
	}
}

func postFromInternalModel(internalPost *internalmodel.Post) *model.Post {
	return &model.Post{
		ID:              internalPost.ID,
		AuthorID:        internalPost.AuthorID,
		Title:           internalPost.Title,
		Text:            internalPost.Text,
		CommentsEnabled: internalPost.CommentsEnabled,
		CreateDate:      internalPost.CreateDate,
	}
}

// AddComment is the resolver for the addComment field.
func (r *mutationResolver) AddComment(ctx context.Context, commentInput model.NewComment) (*model.Comment, error) {
	const op = "graph.AddComment()"

	log.Debug().Msgf("%s start", op)

	newInternalComment := newCommentToInternalModel(&commentInput)
	internalComment, err := r.CommentMutation.AddComment(newInternalComment.PostID, newInternalComment)
	if err != nil {
		if err != errs.ErrPostNotExist && err != errs.ErrParentCommentNotExist && err != errs.ErrCommentsNotEnabled {
			err = errors.New("internal server error")
		}
		return nil, err
	}

	comment := commentFromInternalModel(internalComment)
	r.Subscribers.Pub(commentInput.PostID, comment)
	log.Debug().Msgf("%s end", op)
	return comment, nil
}

func newCommentToInternalModel(newComment *model.NewComment) *internalmodel.NewComment {
	return &internalmodel.NewComment{
		AuthorID: newComment.AuthorID,
		PostID:   newComment.PostID,
		ParentID: newComment.ParentID,
		Text:     newComment.Text,
	}
}

// UpdateEnableComment is the resolver for the updateEnableComment field.
func (r *mutationResolver) UpdateEnableComment(ctx context.Context, postID int64, authorID uuid.UUID, commentsEnabled bool) (*model.Post, error) {
	const op = "graph.UpdateEnableComment()"

	log.Debug().Msgf("%s start", op)

	post, err := r.PostMutation.UpdateEnableCommentToPost(postID, authorID, commentsEnabled)

	if err != nil {
		log.Error().Err(err).Msgf("%s end with error", op)
		if err != errs.ErrPostNotExist && err != errs.ErrUnauthorizedAccess {
			err = errors.New("internal server error")
		}
		return nil, err
	}

	log.Debug().Msgf("%s end", op)
	return postFromInternalModel(post), err
}

// Comments is the resolver for the comments field.
func (r *postResolver) Comments(ctx context.Context, obj *model.Post, first *int32, after *string) (*model.CommentConnection, error) {
	const op = "graph.Comments()"

	log.Debug().Msgf("%s start", op)

	var path string
	if after != nil {
		err := cursor.ValidateAfter(after)
		if err != nil {
			return nil, errors.New("invalid after")
		}

		path, err = cursor.GetPath(after)
		if err != nil {
			return nil, errors.New("invalid after")
		}
	}

	internalCommentsBranch, err := r.CommentQuery.GetCommentsBranchToPost(obj.ID, path)
	if err != nil {
		if err != errs.ErrCommentsNotExist && err != errs.ErrPathNotExist {
			err = errors.New("internal server error")
		}
		return nil, err

	}

	commentBranch, err := paginateInternalBranch(internalCommentsBranch, first, after)

	log.Debug().Msgf("%s end", op)

	return commentBranch, nil
}

const defaultFirst int = 5

func paginateInternalBranch(internalComments []*internalmodel.Comment, firstInput *int32, after *string) (*model.CommentConnection, error) {

	var first int
	if firstInput == nil {
		first = defaultFirst
	} else {
		first = int(*firstInput)
	}

	var edges = make([]*model.CommentEdge, 0, first)

	start := false
	var commentID int64
	var err error
	if after != nil {
		commentID, err = cursor.GetCommentID(after)
		if err != nil {
			return nil, err
		}
	} else {
		start = true
	}

	var hasNextPage = false
	var counter = 0
	for i, v := range internalComments {
		if v.ID == commentID && start == false {
			start = true
			continue
		}

		if start == true {
			edges = append(edges, &model.CommentEdge{
				Node:   commentFromInternalModel(v),
				Cursor: cursor.CreateCursorFromComment(v),
			})
			counter += 1
		}

		if counter == first {
			if i+1 < len(internalComments) {
				hasNextPage = true
			}
			break
		}
	}
	if start == false { // not found comment with commentID
		return &model.CommentConnection{
			Edges:    []*model.CommentEdge{},
			PageInfo: &model.PageInfo{HasNextPage: false},
		}, nil
	}
	var endCursor *string
	if len(edges) > 0 {
		endCursor = &edges[len(edges)-1].Cursor
	}

	pageInfo := &model.PageInfo{
		EndCursor:   endCursor,
		HasNextPage: hasNextPage,
	}

	return &model.CommentConnection{
		Edges:    edges,
		PageInfo: pageInfo,
	}, nil
}

func commentFromInternalModel(internalComment *internalmodel.Comment) *model.Comment {
	return &model.Comment{
		ID:         internalComment.ID,
		AuthorID:   internalComment.AuthorID,
		PostID:     internalComment.PostID,
		ParentID:   internalComment.ParentID,
		Text:       internalComment.Text,
		CreateDate: internalComment.CreateDate,
	}
}

// Posts is the resolver for the posts field.
func (r *queryResolver) Posts(ctx context.Context) ([]*model.Post, error) {
	const op = "graph.Posts()"

	log.Debug().Msgf("%s start", op)

	internalPosts, err := r.PostQuery.GetAllPosts()

	if err != nil {
		log.Error().Err(err).Msgf("%s end with error", op)
		if err != errs.ErrPostsNotExist {
			err = errors.New("internal server error")
		}
		return nil, err
	}
	posts := make([]*model.Post, len(internalPosts))
	for i, v := range internalPosts {
		posts[i] = postFromInternalModel(v)
	}

	log.Debug().Msgf("%s end", op)
	return posts, err
}

// Post is the resolver for the post field.
func (r *queryResolver) Post(ctx context.Context, postID int64) (*model.Post, error) {
	const op = "graph.Post()"

	log.Debug().Msgf("%s start", op)

	postInternal, err := r.PostQuery.GetPost(postID)

	if err != nil {
		log.Error().Err(err).Msgf("%s end with error", op)
		if err != errs.ErrPostNotExist {
			err = errors.New("internal server error")
		}
		return nil, err
	}

	post := postFromInternalModel(postInternal)
	log.Debug().Msgf("%s end", op)
	return post, err
}

// CommentAdded is the resolver for the commentAdded field.
func (r *subscriptionResolver) CommentAdded(ctx context.Context, postID int64) (<-chan *model.Comment, error) {
	const op = "graph.CommentAdded()"
	log.Debug().Msgf("%s subscription init", op)
	ch := make(chan *model.Comment, 10)

	go func() {
		defer r.Subscribers.CloseSub(postID, ch)

		r.Subscribers.Sub(postID, ch)
		for {
			select {
			case <-ctx.Done():
				log.Debug().Msgf("%s subscription closed", op)
				return
			}
		}
	}()
	return ch, nil

}

// Comment returns CommentResolver implementation.
func (r *Resolver) Comment() CommentResolver { return &commentResolver{r} }

// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }

// Post returns PostResolver implementation.
func (r *Resolver) Post() PostResolver { return &postResolver{r} }

// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }

// Subscription returns SubscriptionResolver implementation.
func (r *Resolver) Subscription() SubscriptionResolver { return &subscriptionResolver{r} }

type commentResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type postResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type subscriptionResolver struct{ *Resolver }
