package api

// 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.28

import (
	"context"
	"errors"
	"fmt"
	"mime"
	"strconv"
	"strings"
	"time"

	"entgo.io/ent/dialect/sql"
	apigen "github.com/42atomys/stud42/internal/api/generated"
	typesgen "github.com/42atomys/stud42/internal/api/generated/types"
	"github.com/42atomys/stud42/internal/discord"
	"github.com/42atomys/stud42/internal/models/generated"
	"github.com/42atomys/stud42/internal/models/generated/account"
	"github.com/42atomys/stud42/internal/models/generated/campus"
	"github.com/42atomys/stud42/internal/models/generated/follow"
	"github.com/42atomys/stud42/internal/models/generated/followsgroup"
	"github.com/42atomys/stud42/internal/models/generated/location"
	"github.com/42atomys/stud42/internal/models/generated/notice"
	"github.com/42atomys/stud42/internal/models/generated/noticesuser"
	"github.com/42atomys/stud42/internal/models/generated/user"
	"github.com/42atomys/stud42/internal/models/gotype"
	"github.com/42atomys/stud42/internal/pkg/s3"
	"github.com/42atomys/stud42/internal/pkg/searchengine"
	"github.com/42atomys/stud42/pkg/cache"
	"github.com/42atomys/stud42/pkg/duoapi"
	"github.com/42atomys/stud42/pkg/utils"
	"github.com/99designs/gqlgen/graphql"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
)

// IsSwimmer is the resolver for the isSwimmer field.
func (r *meResolver) IsSwimmer(ctx context.Context, obj *generated.User) (bool, error) {
	return r.User().IsSwimmer(ctx, obj)
}

// ActivesNotices is the resolver for the activesNotices field.
func (r *meResolver) ActivesNotices(ctx context.Context, obj *generated.User) ([]*generated.Notice, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	return r.client.Notice.Query().Modify(func(s *sql.Selector) {
		t := sql.Table(notice.Table)
		s.From(t).
			LeftJoin(sql.Table(noticesuser.Table).As(noticesuser.Table)).
			OnP(
				sql.And(
					sql.ColumnsEQ(t.C(notice.FieldID), sql.Table(noticesuser.Table).C(noticesuser.FieldNoticeID)),
					sql.EQ(sql.Table(noticesuser.Table).C(noticesuser.FieldUserID), cu.ID),
				),
			).
			Where(
				sql.And(
					sql.EQ(t.C(notice.FieldIsActive), true),
					sql.IsNull(sql.Table(noticesuser.Table).C(noticesuser.FieldID)),
				),
			)
		s.OrderBy(t.C(notice.FieldCreatedAt))
	}).All(ctx)
}

// CreateFriendship is the resolver for the createFriendship field.
func (r *mutationResolver) CreateFriendship(ctx context.Context, userID uuid.UUID) (bool, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	if userID == cu.ID {
		return false, fmt.Errorf("cannot befriend yourself")
	}

	if _, err := r.client.Follow.Create().
		SetUser(cu).
		SetFollowID(userID).
		Save(ctx); err != nil {
		return false, err
	}
	return true, nil
}

// DeleteFriendship is the resolver for the deleteFriendship field.
func (r *mutationResolver) DeleteFriendship(ctx context.Context, userID uuid.UUID) (bool, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	if _, err := r.client.Follow.Delete().
		Where(follow.UserID(cu.ID), follow.FollowID(userID)).Exec(ctx); err != nil {
		return false, err
	}
	return true, nil
}

// CreateOrUpdateFollowsGroup is the resolver for the createOrUpdateFollowsGroup field.
func (r *mutationResolver) CreateOrUpdateFollowsGroup(ctx context.Context, input typesgen.FollowsGroupInput) (*generated.FollowsGroup, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	var fg *generated.FollowsGroup
	// If the input contains an ID, try to update the follows group with that ID.
	if input.ID != nil {
		// Update an existing FollowsGroup
		fg, err = r.client.FollowsGroup.Query().Where(followsgroup.UserID(cu.ID), followsgroup.ID(*input.ID)).First(ctx)
		if generated.IsNotFound(err) {
			// If we can't find the FollowsGroup, create a new one
			goto CREATE
		} else if err != nil {
			return nil, err
		}

		fg, err = fg.Update().
			SetName(input.Name).
			SetNillableColor(input.Color).
			Save(ctx)
		if err != nil {
			return nil, err
		}
		return fg, nil
	}

	// If it does not exist, create a new follows group.
CREATE:
	fg, err = r.client.FollowsGroup.Create().
		SetUserID(cu.ID).
		SetName(input.Name).
		SetNillableColor(input.Color).
		Save(ctx)
	if err != nil {
		return nil, err
	}
	return fg, nil
}

// DeleteFollowsGroup is the resolver for the deleteFollowsGroup field.
func (r *mutationResolver) DeleteFollowsGroup(ctx context.Context, id uuid.UUID) (bool, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	// Delete the relationship between the current user and the group.
	if _, err := r.client.FollowsGroup.Delete().
		Where(followsgroup.UserID(cu.ID), followsgroup.ID(id)).Exec(ctx); err != nil {
		return false, err
	}

	return true, nil
}

// ReadNotice is the resolver for the readNotice field.
func (r *mutationResolver) ReadNotice(ctx context.Context, noticeID uuid.UUID) (bool, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	if err := r.client.User.UpdateOneID(cu.ID).AddReadedNoticeIDs(noticeID).Exec(ctx); err != nil {
		return false, err
	}

	return true, nil
}

// AssignFollowsGroupToUser is the resolver for the assignFollowsGroupToUser field.
func (r *mutationResolver) AssignFollowsGroupToUser(ctx context.Context, userID uuid.UUID, followsGroupID uuid.UUID, assign bool) (bool, error) {
	// Get the current user from the context
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	// If the current user ID is the same as the user ID, return an error
	if cu.ID == userID {
		return false, errors.New("cannot assign a follows group to yourself")
	}

	// Count the number of follows groups that the current user owns that match the follows group ID
	if r.client.FollowsGroup.Query().Where(followsgroup.UserID(cu.ID), followsgroup.ID(followsGroupID)).CountX(ctx) == 0 {
		return false, errors.New("you don't own this follows group or it doesn't exist")
	}

	// Get the follow relationship between the current user and the user ID
	follow, err := r.client.Follow.Query().Where(follow.UserID(cu.ID), follow.FollowID(userID)).First(ctx)
	if err != nil {
		if generated.IsNotFound(err) {
			return false, errors.New("you are not following this user")
		}
		return false, err
	}

	// If assign is true, add the follows group to the user; otherwise, remove it
	if assign {
		if _, err := follow.Update().AddFollowGroupIDs(followsGroupID).Save(ctx); err != nil {
			return false, err
		}
	} else {
		if _, err := follow.Update().RemoveFollowGroupIDs(followsGroupID).Save(ctx); err != nil {
			return false, err
		}
	}

	return true, nil
}

// UpdateSettings is the resolver for the updateSettings field.
func (r *mutationResolver) UpdateSettings(ctx context.Context, input typesgen.SettingsInput) (*gotype.Settings, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	updatedUser, err := r.client.User.UpdateOne(cu).SetSettings(gotype.Settings(input)).Save(ctx)
	if err != nil {
		return nil, err
	}
	return &updatedUser.Settings, nil
}

// UpdateMe is the resolver for the updateMe field.
func (r *mutationResolver) UpdateMe(ctx context.Context, input typesgen.UpdateMeInput) (*generated.User, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	s3Client, err := s3.NewS3Client(s3.UsersBucketConfigKey)
	if err != nil {
		return nil, graphql.ErrorOnPath(ctx, errors.New("failed to connect to storage"))
	}

	if input.AvatarURL != nil && *input.AvatarURL != "" && !strings.HasPrefix(*input.AvatarURL, s3Client.GetBaseURL()) {
		return nil, graphql.ErrorOnPath(ctx, errors.New("invalid avatar url"))
	}

	if input.CoverURL != nil && *input.CoverURL != "" && !strings.HasPrefix(*input.CoverURL, s3Client.GetBaseURL()) {
		return nil, graphql.ErrorOnPath(ctx, errors.New("invalid cover url"))
	}

	// For the moment, only sponsors can change their nickname.
	// This will change in the future.
	if input.Nickname != nil && !utils.Contains(cu.Flags, gotype.UserFlagSponsor) {
		return nil, graphql.ErrorOnPath(ctx, errors.New("you cannot change your nickname, you are not a sponsor"))
	}

	if input.Nickname != nil && len(*input.Nickname) > 20 {
		return nil, graphql.ErrorOnPath(ctx, errors.New("your nickname cannot be longer than 20 characters"))
	}

	updatedUser, err := r.client.User.UpdateOne(cu).
		SetNillableAvatarURL(input.AvatarURL).
		SetNillableCoverURL(input.CoverURL).
		SetNillableNickname(input.Nickname).
		SetNillablePronoun(input.Pronoun).
		Save(ctx)
	if err != nil {
		return nil, err
	}
	return updatedUser, nil
}

// UpdateAccountVisibility is the resolver for the updateAccountVisibility field.
func (r *mutationResolver) UpdateAccountVisibility(ctx context.Context, input typesgen.UpdateAccountVisibilityInput) (*generated.Account, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	if cu.QueryAccounts().Where(account.ID(input.ID)).CountX(ctx) == 0 {
		return nil, errors.New("you don't own this account or it doesn't exist")
	}

	return r.client.Account.UpdateOneID(input.ID).
		SetPublic(input.Public).
		Save(ctx)
}

// DeleteAccount is the resolver for the deleteAccount field.
func (r *mutationResolver) DeleteAccount(ctx context.Context, id uuid.UUID) (bool, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	_, err = r.client.Account.Delete().
		Where(account.ID(id), account.UserID(cu.ID)).
		Exec(ctx)
	if err != nil {
		return false, err
	}

	return true, nil
}

// InternalCreateUser is the resolver for the internalCreateUser field.
func (r *mutationResolver) InternalCreateUser(ctx context.Context, input typesgen.CreateUserInput) (uuid.UUID, error) {
	campusID, err := r.client.Campus.Query().Where(campus.DuoID(input.CurrentDuoCampusID)).FirstID(ctx)
	if err != nil {
		log.Error().Err(err).Msg("cannot find campus")
		return uuid.Nil, err
	}

	return r.client.User.Create().
		SetEmail(input.Email).
		SetDuoLogin(input.DuoLogin).
		SetDuoID(input.DuoID).
		SetFirstName(input.FirstName).
		SetNillableUsualFirstName(input.UsualFirstName).
		SetLastName(input.LastName).
		SetNillablePoolYear(input.PoolYear).
		SetNillablePoolMonth(input.PoolMonth).
		SetNillableDuoAvatarURL(input.DuoAvatarURL).
		SetNillableDuoAvatarSmallURL(input.DuoAvatarSmallURL).
		SetIsStaff(input.IsStaff).
		SetNillableCurrentCampusID(&campusID).
		SetIsAUser(true).
		OnConflictColumns(user.FieldDuoID).
		UpdateNewValues().
		ID(ctx)
}

// InternalLinkAccount is the resolver for the internalLinkAccount field.
func (r *mutationResolver) InternalLinkAccount(ctx context.Context, input typesgen.LinkAccountInput) (*generated.Account, error) {
	id, err := r.client.Account.Create().
		SetProvider(input.Provider).
		SetProviderAccountID(input.ProviderAccountID).
		SetUsername(input.Username).
		SetType(input.Type).
		SetAccessToken(input.AccessToken).
		SetNillableRefreshToken(input.RefreshToken).
		SetTokenType(input.TokenType).
		SetNillableExpiresAt(input.ExpiresAt).
		SetScope(input.Scope).
		SetUserID(uuid.MustParse(input.UserID)).
		OnConflictColumns(account.FieldProvider, account.FieldProviderAccountID).
		UpdateNewValues().
		ID(ctx)

	if err != nil {
		return nil, err
	}

	account, err := r.client.Account.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	go accountLinkCallback(ctx, r.client, account)

	return account, nil
}

// InviteOnDiscord is the resolver for the inviteOnDiscord field.
func (r *mutationResolver) InviteOnDiscord(ctx context.Context) (bool, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return false, err
	}

	acc, err := r.client.Account.Query().Where(account.UserID(cu.ID), account.ProviderEQ(gotype.AccountProviderDiscord)).Only(ctx)
	if err != nil {
		return false, err
	}

	err = discord.DefaultClient().InviteOnOurDiscord(ctx, acc.AccessToken, acc.ProviderAccountID)
	if err != nil {
		return false, err
	}
	return true, nil
}

// Me is the resolver for the me field.
func (r *queryResolver) Me(ctx context.Context) (*generated.User, error) {
	return CurrentUserFromContext(ctx)
}

// SearchUser is the resolver for the searchUser field.
func (r *queryResolver) SearchUser(ctx context.Context, query string, onlyOnline *bool) ([]*generated.User, error) {
	usersID, err := searchengine.NewClient().SearchUser(query, *onlyOnline)
	if err != nil {
		return nil, err
	}

	return r.client.User.Query().Modify(func(s *sql.Selector) {
		s.
			Select("users.*").
			SetDistinct(false).
			FromExpr(sql.Expr("unnest($1::uuid[]) WITH ORDINALITY AS x(id, order_nr)", "{"+strings.Join(utils.StringifySlice(usersID), ", ")+"}")).
			Join(sql.Table(user.Table).As(user.Table)).
			On(
				sql.Table(user.Table).C(user.FieldID),
				sql.Table("x").C(user.FieldID),
			).
			OrderExpr(sql.Expr("x.order_nr"))
	}).All(ctx)
}

// Campus is the resolver for the campus field.
func (r *queryResolver) Campus(ctx context.Context, id uuid.UUID) (*generated.Campus, error) {
	return r.client.Campus.Query().Where(campus.ID(id)).Only(ctx)
}

// User is the resolver for the user field.
func (r *queryResolver) User(ctx context.Context, id uuid.UUID) (*generated.User, error) {
	return r.client.User.Query().Where(user.ID(id)).Only(ctx)
}

// Location is the resolver for the location field.
func (r *queryResolver) Location(ctx context.Context, id uuid.UUID) (*generated.Location, error) {
	return r.client.Location.Query().Where(location.ID(id)).Only(ctx)
}

// Locations is the resolver for the locations field.
func (r *queryResolver) Locations(ctx context.Context, page typesgen.PageInput, campusID uuid.UUID) (*generated.LocationConnection, error) {
	return r.client.Location.Query().
		Where(location.CampusID(campusID)).
		WithCampus().
		WithUser().
		Where(location.EndAtIsNil()).
		Paginate(ctx, page.After, &page.First, page.Before, page.Last)
}

// LocationsByCampusName is the resolver for the locationsByCampusName field.
func (r *queryResolver) LocationsByCampusName(ctx context.Context, page typesgen.PageInput, campusName string) (*generated.LocationConnection, error) {
	return r.client.Campus.Query().
		Where(campus.Name(campusName)).
		QueryLocations().
		WithCampus().
		WithUser().
		Where(location.EndAtIsNil()).
		Paginate(ctx, page.After, &page.First, page.Before, page.Last)
}

// LocationsByCluster is the resolver for the locationsByCluster field.
func (r *queryResolver) LocationsByCluster(ctx context.Context, page typesgen.PageInput, campusName string, identifierPrefix *string) (*generated.LocationConnection, error) {
	return r.client.Campus.Query().
		Where(campus.NameEqualFold(campusName)).
		QueryLocations().
		WithCampus().
		WithUser().
		Where(location.IdentifierHasPrefix(*identifierPrefix), location.EndAtIsNil()).
		Paginate(ctx, page.After, &page.First, page.Before, page.Last)
}

// LocationsStatsByPrefixes is the resolver for the locationsStatsByPrefixes field.
func (r *queryResolver) LocationsStatsByPrefixes(ctx context.Context, campusName string, identifierPrefixes []string) ([]*typesgen.LocationStats, error) {
	sqlResults := []*typesgen.LocationStats{}
	prefixes := make([]any, len(identifierPrefixes))
	identifierMinSize := 1
	identifierMaxSize := 10

	// We need to convert the prefixes to any to be able to use them in the query
	// and we also need to know the max size of the prefixes to be able to
	// know how many characters we need to take from the identifier to filter
	// after the request.
	for i, prefix := range identifierPrefixes {
		// Validate the length of the prefix.
		if len(prefix) < identifierMinSize || len(prefix) > identifierMaxSize {
			return nil, fmt.Errorf("invalid prefix size. Must be between %d and %d", identifierMinSize, identifierMaxSize)
		}

		prefixes[i] = prefix
	}

	err := r.client.Location.Query().
		Modify(func(s *sql.Selector) {
			s.Select(
				sql.As(sql.Table(campus.Table).C(campus.FieldID), "campusID"),
				sql.As(sql.Count(sql.Table(location.Table).C(location.FieldID)), "occupiedWorkspace"),
				sql.As(fmt.Sprintf("left(%s, %d)", sql.Table(location.Table).C(location.FieldIdentifier), identifierMaxSize), "prefix"),
			).
				Join(sql.Table(campus.Table).As(campus.Table)).
				On(
					sql.Table(campus.Table).C(campus.FieldID),
					sql.Table(location.Table).C(location.FieldCampusID),
				).
				Where(
					sql.And(
						sql.EqualFold(sql.Table(campus.Table).C(campus.FieldName), campusName),
						sql.IsNull(sql.Table(location.Table).C(location.FieldEndAt)),
						sql.ExprP(
							fmt.Sprintf(
								"%q.%q ~ $2",
								location.Table,
								location.FieldIdentifier,
							),
							"^"+strings.Join(identifierPrefixes, "|"),
						),
					),
				).
				GroupBy("(campus.id, prefix)")
		}).
		Scan(ctx, &sqlResults)

	// We need to loop over the result to calculate the total number of workspaces
	// for each prefix. This is because we can't use group by sql instruction
	// with the count function over a regex filter.
	// Need more research on this topic. Maybe there is a better way to do this.
	var finalLocationStats = make([]*typesgen.LocationStats, len(identifierPrefixes))
	for i, prefix := range identifierPrefixes {
		finalLocationStats[i] = &typesgen.LocationStats{
			Prefix: prefix,
		}

		for _, locationStat := range sqlResults {
			if finalLocationStats[i].CampusID == uuid.Nil {
				finalLocationStats[i].CampusID = locationStat.CampusID
			}

			if strings.HasPrefix(locationStat.Prefix, prefix) {
				finalLocationStats[i].OccupiedWorkspace += locationStat.OccupiedWorkspace
			}
		}
	}

	return finalLocationStats, err
}

// PresignedUploadURL is the resolver for the presignedUploadURL field.
func (r *queryResolver) PresignedUploadURL(ctx context.Context, contentType string, contentLength int, kind typesgen.PresignedUploadURLKind) (string, error) {
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return "", err
	}

	// Validate the content type
	if !utils.Contains([]string{"image/png", "image/jpeg"}, contentType) {
		return "", fmt.Errorf("we only accept png and jpeg files")
	}

	// Validate the content length
	const _5MB = 5 * 1024 * 1024
	if contentLength > _5MB {
		return "", fmt.Errorf("the file is too big. Max size is 5MB")
	}

	// File extension
	extension, err := mime.ExtensionsByType(contentType)
	if err != nil {
		return "", err
	}

	// Initialize the s3 client
	s3client, err := s3.NewS3Client(s3.UsersBucketConfigKey)
	if err != nil {
		return "", err
	}

	// Generate the key
	key := fmt.Sprintf("%s/%s_%d%s", strings.ToLower(kind.String()), cu.ID, time.Now().Unix(), extension[0])

	// Generate the presigned url
	return s3client.PresignedUploadURL(key, contentType, int64(contentLength), "public-read", 5*time.Minute)
}

// MyFollowings is the resolver for the myFollowings field.
func (r *queryResolver) MyFollowings(ctx context.Context, followsGroupID *uuid.UUID, followsGroupSlug *string) ([]*generated.User, error) {
	cu, _ := CurrentUserFromContext(ctx)

	withCampus := func(lq *generated.LocationQuery) {
		lq.WithCampus()
	}

	return r.client.User.Query().
		Where(user.ID(cu.ID)).
		QueryFollows().
		Where(func(s *sql.Selector) {
			jt := sql.Table(followsgroup.FollowsTable)
			gt := sql.Table(followsgroup.Table)

			s.LeftJoin(jt).On(s.C(follow.FieldID), jt.C(followsgroup.FollowsPrimaryKey[1]))
			s.LeftJoin(gt).On(jt.C(followsgroup.FollowsPrimaryKey[0]), gt.C(followsgroup.FieldID))

			predicates := []*sql.Predicate{}

			if followsGroupID != nil {
				predicates = append(predicates, sql.EQ(gt.C(followsgroup.FieldID), *followsGroupID))
			}

			if followsGroupSlug != nil {
				predicates = append(predicates, sql.EQ(gt.C(followsgroup.FieldSlug), *followsGroupSlug))
			}

			if len(predicates) > 0 {
				s.Where(predicates[0])
			}
		}).
		QueryFollow().
		WithCurrentLocation(withCampus).
		WithLastLocation(withCampus).
		// Unique is necessary because the query builder always add a DISTINCT clause
		// and cannot order the query properly by location identifier
		Unique(false).
		Modify(func(s *sql.Selector) {
			//: Hack to order the friends as A -> Z over the connected status
			t := sql.Table(location.Table).As("cl")
			s.LeftJoin(t).On(s.C(user.FieldCurrentLocationID), t.C(location.FieldID))
			s.SelectExpr(
				sql.Expr(
					"DISTINCT ON (cl.user_duo_login, users.duo_login) duo_login, users.*",
				),
			)
			s.OrderBy(t.C(location.FieldUserDuoLogin), s.C(user.FieldDuoLogin))
			//: Hack to order the friends as A -> Z over the connected status
		}).
		All(ctx)
}

// MyFollowsGroups is the resolver for the myFollowsGroups field.
func (r *queryResolver) MyFollowsGroups(ctx context.Context) ([]*generated.FollowsGroup, error) {
	cu, _ := CurrentUserFromContext(ctx)

	return r.client.User.Query().
		Where(user.ID(cu.ID)).
		QueryFollowsGroups().
		Order(generated.Asc(followsgroup.FieldName)).
		All(ctx)
}

// FollowsGroupsForUser is the resolver for the followsGroupsForUser field.
func (r *queryResolver) FollowsGroupsForUser(ctx context.Context, userID uuid.UUID) ([]*generated.FollowsGroup, error) {
	// Get the current user from the context
	cu, err := CurrentUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	// Make sure the current user is not trying to assign itself to a group.
	if cu.ID == userID {
		return nil, errors.New("you can't do that on yourself")
	}

	// Return all the FollowsGroups for the given user, ordered by the FollowsGroup name.
	// It includes only for the requesting user.
	return r.client.User.Query().
		Where(user.ID(cu.ID)).
		QueryFollowsGroups().
		Where(
			followsgroup.KindNotIn(gotype.FollowsGroupKindDynamic),
			followsgroup.HasFollowsWith(follow.FollowID(userID)),
		).
		Order(generated.Asc(followsgroup.FieldName)).
		All(ctx)
}

// InternalGetUserByAccount is the resolver for the internalGetUserByAccount field.
func (r *queryResolver) InternalGetUserByAccount(ctx context.Context, provider gotype.AccountProvider, uid string) (*generated.User, error) {
	return r.client.Account.Query().
		Where(account.ProviderEQ(provider), account.ProviderAccountID(uid)).
		QueryUser().
		Only(ctx)
}

// InternalGetUserByEmail is the resolver for the internalGetUserByEmail field.
func (r *queryResolver) InternalGetUserByEmail(ctx context.Context, email string) (*generated.User, error) {
	return r.client.User.Query().
		Where(user.Email(email)).
		Only(ctx)
}

// InternalGetUser is the resolver for the internalGetUser field.
func (r *queryResolver) InternalGetUser(ctx context.Context, id uuid.UUID) (*generated.User, error) {
	return r.client.User.Get(ctx, id)
}

// IsSwimmer is the resolver for the isSwimmer field.
func (r *userResolver) IsSwimmer(ctx context.Context, obj *generated.User) (bool, error) {
	if obj.PoolYear == nil || obj.PoolMonth == nil {
		return false, nil
	}

	now := time.Now()
	return (*obj.PoolYear == strconv.Itoa(now.Year()) &&
		strings.EqualFold(*obj.PoolMonth, now.Format("January"))), nil
}

// PublicAccounts is the resolver for the publicAccounts field.
func (r *userResolver) PublicAccounts(ctx context.Context, obj *generated.User) ([]*generated.Account, error) {
	return r.client.User.QueryAccounts(obj).Where(account.Public(true)).All(ctx)
}

// IntraProxy is the resolver for the intraProxy field.
func (r *userResolver) IntraProxy(ctx context.Context, obj *generated.User) (*duoapi.User, error) {
	cacheKey := cache.NewKeyBuilder().WithPrefix("intra-proxy").WithObject(obj.ID).Build()

	loader := cache.New[*duoapi.User](r.cache).WithLoader(func(ctx context.Context, key cache.CacheKey) (*duoapi.User, error) {
		return duoapi.UserGet(ctx, strconv.Itoa(obj.DuoID))
	})
	defer loader.Close()

	return loader.Get(ctx, cacheKey, cache.WithExpiration(24*time.Hour))
}

// FollowersCount is the resolver for the followersCount field.
func (r *userResolver) FollowersCount(ctx context.Context, obj *generated.User) (int, error) {
	return r.client.Follow.Query().
		Where(follow.FollowID(obj.ID)).
		Count(ctx)
}

// FollowingsCount is the resolver for the followingsCount field.
func (r *userResolver) FollowingsCount(ctx context.Context, obj *generated.User) (int, error) {
	return r.client.Follow.Query().
		Where(follow.UserID(obj.ID)).
		Count(ctx)
}

// Me returns apigen.MeResolver implementation.
func (r *Resolver) Me() apigen.MeResolver { return &meResolver{r} }

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

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

// User returns apigen.UserResolver implementation.
func (r *Resolver) User() apigen.UserResolver { return &userResolver{r} }

type meResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type userResolver struct{ *Resolver }
