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.

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jordanknott/taskcafe/internal/db"
	"github.com/jordanknott/taskcafe/internal/logger"
	"github.com/jordanknott/taskcafe/internal/utils"
	log "github.com/sirupsen/logrus"
	"github.com/vektah/gqlparser/v2/gqlerror"
)

func (r *labelColorResolver) ID(ctx context.Context, obj *db.LabelColor) (uuid.UUID, error) {
	return obj.LabelColorID, nil
}

func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*db.ProjectLabel, error) {
	createdAt := time.Now().UTC()

	var name sql.NullString
	if input.Name != nil {
		name = sql.NullString{
			*input.Name,
			true,
		}
	} else {
		name = sql.NullString{
			"",
			false,
		}
	}
	projectLabel, err := r.Repository.CreateProjectLabel(ctx, db.CreateProjectLabelParams{input.ProjectID, input.LabelColorID, createdAt, name})
	return &projectLabel, err
}

func (r *mutationResolver) DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*db.ProjectLabel, error) {
	label, err := r.Repository.GetProjectLabelByID(ctx, input.ProjectLabelID)
	if err != nil {
		return &db.ProjectLabel{}, err
	}
	err = r.Repository.DeleteProjectLabelByID(ctx, input.ProjectLabelID)
	return &label, err
}

func (r *mutationResolver) UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*db.ProjectLabel, error) {
	label, err := r.Repository.UpdateProjectLabel(ctx, db.UpdateProjectLabelParams{ProjectLabelID: input.ProjectLabelID, LabelColorID: input.LabelColorID, Name: sql.NullString{String: input.Name, Valid: true}})
	return &label, err
}

func (r *mutationResolver) UpdateProjectLabelName(ctx context.Context, input UpdateProjectLabelName) (*db.ProjectLabel, error) {
	label, err := r.Repository.UpdateProjectLabelName(ctx, db.UpdateProjectLabelNameParams{ProjectLabelID: input.ProjectLabelID, Name: sql.NullString{String: input.Name, Valid: true}})
	return &label, err
}

func (r *mutationResolver) UpdateProjectLabelColor(ctx context.Context, input UpdateProjectLabelColor) (*db.ProjectLabel, error) {
	label, err := r.Repository.UpdateProjectLabelColor(ctx, db.UpdateProjectLabelColorParams{ProjectLabelID: input.ProjectLabelID, LabelColorID: input.LabelColorID})
	return &label, err
}

func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input InviteProjectMembers) (*InviteProjectMembersPayload, error) {
	members := []Member{}
	invitedMembers := []InvitedMember{}
	for _, invitedMember := range input.Members {
		if invitedMember.Email != nil && invitedMember.UserID != nil {
			return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
				Message: "Both email and userID can not be used to invite a project member",
				Extensions: map[string]interface{}{
					"code": "403",
				},
			}
		} else if invitedMember.Email == nil && invitedMember.UserID == nil {
			return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
				Message: "Either email or userID must be set to invite a project member",
				Extensions: map[string]interface{}{
					"code": "403",
				},
			}
		}
		if invitedMember.UserID != nil {
			// Invite by user ID
			addedAt := time.Now().UTC()
			_, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: *invitedMember.UserID, AddedAt: addedAt, RoleCode: "member"})
			if err != nil {
				return &InviteProjectMembersPayload{Ok: false}, err
			}
			user, err := r.Repository.GetUserAccountByID(ctx, *invitedMember.UserID)
			if err != nil && err != sql.ErrNoRows {
				return &InviteProjectMembersPayload{Ok: false}, err

			}
			var url *string
			if user.ProfileAvatarUrl.Valid {
				url = &user.ProfileAvatarUrl.String
			}
			profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}

			role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: *invitedMember.UserID, ProjectID: input.ProjectID})
			if err != nil {
				return &InviteProjectMembersPayload{Ok: false}, err
			}
			members = append(members, Member{
				ID:          *invitedMember.UserID,
				FullName:    user.FullName,
				Username:    user.Username,
				ProfileIcon: profileIcon,
				Role:        &db.Role{Code: role.Code, Name: role.Name},
			})
		} else {
			// Invite by email

			// if invited user does not exist, create entry
			invitedUser, err := r.Repository.GetInvitedUserByEmail(ctx, *invitedMember.Email)
			now := time.Now().UTC()
			if err != nil {
				if err == sql.ErrNoRows {
					invitedUser, err = r.Repository.CreateInvitedUser(ctx, *invitedMember.Email)
					if err != nil {
						return &InviteProjectMembersPayload{Ok: false}, err
					}
					confirmToken, err := r.Repository.CreateConfirmToken(ctx, *invitedMember.Email)
					if err != nil {
						return &InviteProjectMembersPayload{Ok: false}, err
					}
					invite := utils.EmailInvite{To: *invitedMember.Email, FullName: *invitedMember.Email, ConfirmToken: confirmToken.ConfirmTokenID.String()}
					err = utils.SendEmailInvite(r.AppConfig.Email, invite)
					if err != nil {
						logger.New(ctx).WithError(err).Error("issue sending email")
						return &InviteProjectMembersPayload{Ok: false}, err
					}
				} else {
					return &InviteProjectMembersPayload{Ok: false}, err
				}
			}

			_, err = r.Repository.CreateInvitedProjectMember(ctx, db.CreateInvitedProjectMemberParams{
				ProjectID:            input.ProjectID,
				UserAccountInvitedID: invitedUser.UserAccountInvitedID,
			})
			if err != nil {
				return &InviteProjectMembersPayload{Ok: false}, err
			}
			logger.New(ctx).Info("adding invited member")
			invitedMembers = append(invitedMembers, InvitedMember{Email: *invitedMember.Email, InvitedOn: now})

		}
	}
	return &InviteProjectMembersPayload{Ok: false, ProjectID: input.ProjectID, Members: members, InvitedMembers: invitedMembers}, nil
}

func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error) {
	user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
	if err != nil {
		return &DeleteProjectMemberPayload{Ok: false}, err
	}
	var url *string
	if user.ProfileAvatarUrl.Valid {
		url = &user.ProfileAvatarUrl.String
	}
	profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
	role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: input.UserID, ProjectID: input.ProjectID})
	if err != nil {
		return &DeleteProjectMemberPayload{Ok: false}, err
	}
	err = r.Repository.DeleteProjectMember(ctx, db.DeleteProjectMemberParams{UserID: input.UserID, ProjectID: input.ProjectID})
	if err != nil {
		return &DeleteProjectMemberPayload{Ok: false}, err
	}
	return &DeleteProjectMemberPayload{Ok: true, Member: &Member{
		ID:          input.UserID,
		FullName:    user.FullName,
		ProfileIcon: profileIcon,
		Role:        &db.Role{Code: role.Code, Name: role.Name},
	}, ProjectID: input.ProjectID}, nil
}

func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error) {
	user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
	if err != nil {
		logger.New(ctx).WithError(err).Error("get user account")
		return &UpdateProjectMemberRolePayload{Ok: false}, err
	}
	_, err = r.Repository.UpdateProjectMemberRole(ctx, db.UpdateProjectMemberRoleParams{ProjectID: input.ProjectID,
		UserID: input.UserID, RoleCode: input.RoleCode.String()})
	if err != nil {
		logger.New(ctx).WithError(err).Error("update project member role")
		return &UpdateProjectMemberRolePayload{Ok: false}, err
	}
	role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: input.ProjectID})
	if err != nil {
		logger.New(ctx).WithError(err).Error("get role for project member")
		return &UpdateProjectMemberRolePayload{Ok: false}, err
	}
	var url *string
	if user.ProfileAvatarUrl.Valid {
		url = &user.ProfileAvatarUrl.String
	}
	profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
	if user.ProfileAvatarUrl.Valid {
		url = &user.ProfileAvatarUrl.String
	}
	member := Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
		Role: &db.Role{Code: role.Code, Name: role.Name},
	}
	return &UpdateProjectMemberRolePayload{Ok: true, Member: &member}, err
}

func (r *mutationResolver) DeleteInvitedProjectMember(ctx context.Context, input DeleteInvitedProjectMember) (*DeleteInvitedProjectMemberPayload, error) {
	member, err := r.Repository.GetProjectMemberInvitedIDByEmail(ctx, input.Email)
	if err != nil {
		return &DeleteInvitedProjectMemberPayload{}, err
	}
	err = r.Repository.DeleteInvitedProjectMemberByID(ctx, member.ProjectMemberInvitedID)
	if err != nil {
		return &DeleteInvitedProjectMemberPayload{}, err
	}
	return &DeleteInvitedProjectMemberPayload{
		InvitedMember: &InvitedMember{Email: member.Email, InvitedOn: member.InvitedOn},
	}, nil
}

func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) (*db.Project, error) {
	userID, ok := GetUserID(ctx)
	if !ok {
		return &db.Project{}, errors.New("user id is missing")
	}
	createdAt := time.Now().UTC()
	logger.New(ctx).WithFields(log.Fields{"name": input.Name, "teamID": input.TeamID}).Info("creating new project")
	var project db.Project
	var err error
	if input.TeamID == nil {
		project, err = r.Repository.CreatePersonalProject(ctx, db.CreatePersonalProjectParams{
			CreatedAt: createdAt,
			Name:      input.Name,
		})
		if err != nil {
			logger.New(ctx).WithError(err).Error("error while creating project")
			return &db.Project{}, err
		}
		logger.New(ctx).WithField("projectID", project.ProjectID).Info("creating personal project link")
	} else {
		project, err = r.Repository.CreateTeamProject(ctx, db.CreateTeamProjectParams{
			CreatedAt: createdAt,
			Name:      input.Name,
			TeamID:    *input.TeamID,
		})
		if err != nil {
			logger.New(ctx).WithError(err).Error("error while creating project")
			return &db.Project{}, err
		}
	}
	_, err = r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: project.ProjectID, UserID: userID, AddedAt: createdAt, RoleCode: "admin"})
	if err != nil {
		logger.New(ctx).WithError(err).Error("error while creating initial project member")
		return &db.Project{}, err
	}
	return &project, nil
}

func (r *mutationResolver) DeleteProject(ctx context.Context, input DeleteProject) (*DeleteProjectPayload, error) {
	project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
	if err != nil {
		return &DeleteProjectPayload{Ok: false}, err
	}
	err = r.Repository.DeleteProjectByID(ctx, input.ProjectID)
	if err != nil {
		return &DeleteProjectPayload{Ok: false}, err
	}
	return &DeleteProjectPayload{Project: &project, Ok: true}, err
}

func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*db.Project, error) {
	project, err := r.Repository.UpdateProjectNameByID(ctx, db.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name})
	if err != nil {
		return &db.Project{}, err
	}
	return &project, nil
}

func (r *mutationResolver) ToggleProjectVisibility(ctx context.Context, input ToggleProjectVisibility) (*ToggleProjectVisibilityPayload, error) {
	if input.IsPublic {
		project, err := r.Repository.SetPublicOn(ctx, db.SetPublicOnParams{ProjectID: input.ProjectID, PublicOn: sql.NullTime{Valid: true, Time: time.Now().UTC()}})
		return &ToggleProjectVisibilityPayload{Project: &project}, err
	}
	project, err := r.Repository.SetPublicOn(ctx, db.SetPublicOnParams{ProjectID: input.ProjectID, PublicOn: sql.NullTime{Valid: false, Time: time.Time{}}})
	return &ToggleProjectVisibilityPayload{Project: &project}, err
}

func (r *projectResolver) ID(ctx context.Context, obj *db.Project) (uuid.UUID, error) {
	return obj.ProjectID, nil
}

func (r *projectResolver) Team(ctx context.Context, obj *db.Project) (*db.Team, error) {
	team, err := r.Repository.GetTeamByID(ctx, obj.TeamID)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		logger.New(ctx).WithFields(log.Fields{"teamID": obj.TeamID, "projectID": obj.ProjectID}).WithError(err).Error("issue while getting team for project")
		return &team, err
	}
	return &team, nil
}

func (r *projectResolver) TaskGroups(ctx context.Context, obj *db.Project) ([]db.TaskGroup, error) {
	return r.Repository.GetTaskGroupsForProject(ctx, obj.ProjectID)
}

func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Member, error) {
	members := []Member{}
	projectMembers, err := r.Repository.GetProjectMembersForProjectID(ctx, obj.ProjectID)
	if err != nil {
		logger.New(ctx).WithError(err).Error("get project members for project id")
		return members, err
	}

	for _, projectMember := range projectMembers {
		user, err := r.Repository.GetUserAccountByID(ctx, projectMember.UserID)
		if err != nil {
			logger.New(ctx).WithError(err).Error("get user account by ID")
			return members, err
		}
		var url *string
		if user.ProfileAvatarUrl.Valid {
			url = &user.ProfileAvatarUrl.String
		}
		role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: obj.ProjectID})
		if err != nil {
			logger.New(ctx).WithError(err).Error("get role for projet member by user ID")
			return members, err
		}
		profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
		members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
			Username: user.Username, Role: &db.Role{Code: role.Code, Name: role.Name},
		})
	}
	return members, nil
}

func (r *projectResolver) InvitedMembers(ctx context.Context, obj *db.Project) ([]InvitedMember, error) {
	members, err := r.Repository.GetInvitedMembersForProjectID(ctx, obj.ProjectID)
	if err != nil && err == sql.ErrNoRows {
		return []InvitedMember{}, nil
	}
	invited := []InvitedMember{}
	for _, member := range members {
		invited = append(invited, InvitedMember{Email: member.Email, InvitedOn: member.InvitedOn})
	}
	return invited, err
}

func (r *projectResolver) PublicOn(ctx context.Context, obj *db.Project) (*time.Time, error) {
	if obj.PublicOn.Valid {
		return &obj.PublicOn.Time, nil
	}
	return nil, nil
}

func (r *projectResolver) Permission(ctx context.Context, obj *db.Project) (*ProjectPermission, error) {
	panic(fmt.Errorf("not implemented"))
}

func (r *projectResolver) Labels(ctx context.Context, obj *db.Project) ([]db.ProjectLabel, error) {
	labels, err := r.Repository.GetProjectLabelsForProject(ctx, obj.ProjectID)
	return labels, err
}

func (r *projectLabelResolver) ID(ctx context.Context, obj *db.ProjectLabel) (uuid.UUID, error) {
	return obj.ProjectLabelID, nil
}

func (r *projectLabelResolver) LabelColor(ctx context.Context, obj *db.ProjectLabel) (*db.LabelColor, error) {
	labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID)
	if err != nil {
		return &db.LabelColor{}, err
	}
	return &labelColor, nil
}

func (r *projectLabelResolver) Name(ctx context.Context, obj *db.ProjectLabel) (*string, error) {
	var name *string
	if obj.Name.Valid {
		name = &obj.Name.String
	}
	return name, nil
}

// LabelColor returns LabelColorResolver implementation.
func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{r} }

// Project returns ProjectResolver implementation.
func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} }

// ProjectLabel returns ProjectLabelResolver implementation.
func (r *Resolver) ProjectLabel() ProjectLabelResolver { return &projectLabelResolver{r} }

type labelColorResolver struct{ *Resolver }
type projectResolver struct{ *Resolver }
type projectLabelResolver struct{ *Resolver }
