module profile.service;

import handy_http_primitives;
import handy_http_handlers.path_handler;

import profile.model;
import profile.data;
import profile.data_impl_sqlite;
import auth.model;

/**
 * Validates a profile name.
 * Params:
 *   name = The name to check.
 * Returns: True if the profile name is valid.
 */
bool validateProfileName(string name) {
    import std.regex;
    import std.uni : toLower;
    if (name is null || name.length < 3) return false;
    auto r = ctRegex!(`^[a-zA-Z]+[a-zA-Z0-9_-]+$`);
    return !matchFirst(name, r).empty;
}

/// Contextual information that's available when handling requests under a profile.
struct ProfileContext {
    const Profile profile;
    const User user;
}

/**
 * Tries to get a profile context from a request. This will attempt to
 * extract a "profile" path parameter and the authenticated user, and combine
 * them into the ProfileContext.
 * Params:
 *   request = The request to read.
 * Returns: An optional profile context.
 */
Optional!ProfileContext getProfileContext(in ServerHttpRequest request) {
    import auth.service : AuthContext, getAuthContext;
    foreach (param; getPathParams(request)) {
        if (param.name == "profile") {
            string profileName = param.value;
            if (!validateProfileName(profileName)) return Optional!ProfileContext.empty;
            AuthContext authCtx = getAuthContext(request);
            if (authCtx is null) return Optional!ProfileContext.empty;
            User user = authCtx.user;
            ProfileRepository repo = new FileSystemProfileRepository(user.username);
            return repo.findByName(profileName)
                .mapIfPresent!(p => ProfileContext(p, user));
        }
    }
    return Optional!ProfileContext.empty;
}

/**
 * Similar to `getProfileContext`, but throws an HttpStatusException with a
 * 404 NOT FOUND status if no profile context could be obtained.
 * Params:
 *   request = The request to read.
 * Returns: The profile context that was obtained.
 */
ProfileContext getProfileContextOrThrow(in ServerHttpRequest request) {
    return getProfileContext(request).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
}

/**
 * Obtains a ProfileDataSource from a ProfileContext. Use this to get access
 * to all profile data when handling an HTTP request, for example.
 * Params:
 *   pc = The profile context.
 * Returns: The profile data source.
 */
ProfileDataSource getProfileDataSource(in ProfileContext pc) {
    ProfileRepository profileRepo = new FileSystemProfileRepository(pc.user.username);
    return profileRepo.getDataSource(pc.profile);
}

/**
 * Obtains a ProfileDataSource from an HTTP request context. Use this to
 * access all profile data when handling the request.
 * Params:
 *   request = The request.
 * Returns: The profile data source.
 */
ProfileDataSource getProfileDataSource(in ServerHttpRequest request) {
    ProfileContext pc = getProfileContextOrThrow(request);
    return getProfileDataSource(pc);
}
