module auth.data_impl_fs;

import handy_http_primitives : Optional;

import auth.data;
import auth.model;

/**
 * User implementation that stores each user's data in a separate directory.
 */
class FileSystemUserRepository : UserRepository {
    import std.path;
    import std.file;
    import std.json;

    private string usersDir;

    this(string usersDir = "users") {
        this.usersDir = usersDir;
    }

    Optional!User findByUsername(string username) {
        if (
            !validateUsername(username) ||
            !exists(getUserDir(username)) ||
            !exists(getUserDataFile(username))
        ) {
            return Optional!User.empty;
        }
        return Optional!User.of(readUser(username));
    }

    User[] findAll() {
        if (!exists(this.usersDir)) return [];
        User[] users;
        foreach (DirEntry entry; dirEntries(this.usersDir, SpanMode.shallow, false)) {
            string username = baseName(entry.name);
            users ~= readUser(username);
        }
        import std.algorithm : sort;
        sort!((u1, u2) => u1.username < u2.username)(users);
        return users;
    }

    User createUser(string username, string passwordHash) {
        if (!validateUsername(username)) throw new Exception("Invalid username");
        if (exists(getUserDir(username))) throw new Exception("User already exists.");
        JSONValue userObj = JSONValue.emptyObject;
        userObj.object["passwordHash"] = JSONValue(passwordHash);
        string jsonStr = userObj.toPrettyString();
        if (!exists(this.usersDir)) mkdir(this.usersDir);
        mkdir(getUserDir(username));
        std.file.write(getUserDataFile(username), cast(ubyte[]) jsonStr);
        return User(username, passwordHash);
    }

    void deleteByUsername(string username) {
        if (validateUsername(username) && exists(getUserDir(username))) {
            rmdirRecurse(getUserDir(username));
        }
    }

    void updatePasswordHash(User user) {
        if (!exists(getUserDataFile(user.username))) throw new Exception("User doesn't exist.");
        JSONValue userObj = parseJSON(readText(getUserDataFile(user.username)));
        userObj.object["passwordHash"] = JSONValue(user.passwordHash);
        string jsonStr = userObj.toPrettyString();
        std.file.write(getUserDataFile(user.username), cast(ubyte[]) jsonStr);
    }

    private string getUserDir(string username) {
        return buildPath(this.usersDir, username);
    }

    private string getUserDataFile(string username) {
        return buildPath(this.usersDir, username, "user-data.json");
    }

    private User readUser(string username) {
        if (!exists(getUserDataFile(username))) {
            throw new Exception("User data file for " ~ username ~ " doesn't exist.");
        }
        JSONValue userObj = parseJSON(readText(getUserDataFile(username)));
        return User(
            username,
            userObj.object["passwordHash"].str
        );
    }
}