module profile.api;

import std.json;
import handy_http_primitives;
import handy_http_data.json;
import handy_http_handlers.path_handler : getPathParamAs, PathMapping;
import slf4d;

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

const PROFILE_PATH = "/api/profiles/:profile";

struct NewProfilePayload {
    string name;
}

@PathMapping(HttpMethod.POST, "/api/profiles")
void handleCreateNewProfile(ref ServerHttpRequest request, ref ServerHttpResponse response) {
    auto payload = readJsonBodyAs!NewProfilePayload(request);
    string name = payload.name;
    if (!validateProfileName(name)) {
        response.status = HttpStatus.BAD_REQUEST;
        response.writeBodyString("Invalid profile name.");
        return;
    }
    AuthContext auth = getAuthContext(request);
    ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
    Profile p = profileRepo.createProfile(name);
    writeJsonBody(response, p);
}

@PathMapping(HttpMethod.GET, "/api/profiles")
void handleGetProfiles(ref ServerHttpRequest request, ref ServerHttpResponse response) {
    AuthContext auth = getAuthContext(request);
    ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
    Profile[] profiles = profileRepo.findAll();
    writeJsonBody(response, profiles);
}

@PathMapping(HttpMethod.GET, PROFILE_PATH)
void handleGetProfile(ref ServerHttpRequest request, ref ServerHttpResponse response) {
    ProfileContext profileCtx = getProfileContextOrThrow(request);
    writeJsonBody(response, profileCtx.profile);
}

@PathMapping(HttpMethod.DELETE, PROFILE_PATH)
void handleDeleteProfile(ref ServerHttpRequest request, ref ServerHttpResponse response) {
    string name = request.getPathParamAs!string("profile");
    if (!validateProfileName(name)) {
        response.status = HttpStatus.BAD_REQUEST;
        response.writeBodyString("Invalid profile name.");
        return;
    }
    AuthContext auth = getAuthContext(request);
    ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
    profileRepo.deleteByName(name);
}

@PathMapping(HttpMethod.GET, PROFILE_PATH ~ "/properties")
void handleGetProperties(ref ServerHttpRequest request, ref ServerHttpResponse response) {
    ProfileContext profileCtx = getProfileContextOrThrow(request);
    ProfileRepository profileRepo = new FileSystemProfileRepository(profileCtx.user.username);
    ProfileDataSource ds = profileRepo.getDataSource(profileCtx.profile);
    auto propsRepo = ds.getPropertiesRepository();
    ProfileProperty[] props = propsRepo.findAll();
    writeJsonBody(response, props);
}

@PathMapping(HttpMethod.GET, PROFILE_PATH ~ "/download")
void handleDownloadProfile(ref ServerHttpRequest request, ref ServerHttpResponse response) {
    ProfileContext profileCtx = getProfileContextOrThrow(request);
    ProfileRepository profileRepo = new FileSystemProfileRepository(profileCtx.user.username);
    Optional!ProfileDownloadData data = profileRepo.getProfileData(profileCtx.profile.name);
    if (data.isNull) {
        response.status = HttpStatus.NOT_FOUND;
        response.writeBodyString("Profile data not found.");
        return;
    }

    import streams : StreamResult;
    import std.conv : to;
    response.headers.add("Content-Type", data.value.contentType);
    response.headers.add("Content-Disposition", "attachment; filename=" ~ data.value.filename);
    response.headers.add("Content-Length", data.value.size.to!string);
    // Transfer the file:
    StreamResult result;
    ubyte[4096] buffer;
    ulong bytesWritten = 0;
    while (bytesWritten < data.value.size) {
        result = data.value.inputStream.readFromStream(buffer);
        if (result.hasError) {
            errorF!"Failed to read from stream: %s"(result.error);
            return;
        }
        if (result.count == 0) {
            return; // Done!
        }
        result = response.outputStream.writeToStream(buffer[0 .. result.count]);
        if (result.hasError) {
            errorF!"Failed to write to stream: %s"(result.error);
            return;
        }
        bytesWritten += result.count;
    }
}