package ru.kirillius.pf.sdn.web.RPC;

import org.json.JSONArray;
import ru.kirillius.json.JSONUtility;
import ru.kirillius.json.rpc.Annotations.JRPCArgument;
import ru.kirillius.json.rpc.Annotations.JRPCContext;
import ru.kirillius.json.rpc.Annotations.JRPCMethod;
import ru.kirillius.json.rpc.Servlet.CallContext;
import ru.kirillius.pf.sdn.core.Auth.AuthManager;
import ru.kirillius.pf.sdn.core.Auth.AuthToken;
import ru.kirillius.pf.sdn.core.Auth.TokenService;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.web.ProtectedMethod;

import java.io.IOException;
import java.util.Date;

/**
 * JSON-RPC handler exposing authentication, session, and token management endpoints.
 */
public class Auth implements RPC {
    private final Context context;

    /**
     * Creates the handler bound to the shared application context.
     */
    public Auth(Context context) {
        this.context = context;
    }

    /**
     * Returns a JSON array containing all stored API tokens.
     */
    @ProtectedMethod
    @JRPCMethod
    public JSONArray listTokens() {
        return JSONUtility.serializeCollection(context.getServiceManager().getService(TokenService.class).getTokens(), AuthToken.class, null);
    }

    @ProtectedMethod
    @JRPCMethod
    public void changePassword(@JRPCArgument(name = "password") String password) {
        context.getServiceManager().getService(AuthManager.class).updatePassword(password);
    }

    /**
     * Removes a token identified by its raw value.
     */
    @ProtectedMethod
    @JRPCMethod
    public void removeToken(@JRPCArgument(name = "token") String token) throws IOException {
        var service = context.getServiceManager().getService(TokenService.class);
        service.remove(new AuthToken(token));
        service.store();
    }

    /**
     * Creates a new token with the provided description and returns its value.
     */
    @ProtectedMethod
    @JRPCMethod
    public String createAPIToken(@JRPCArgument(name = "description") String description) throws IOException {
        var service = context.getServiceManager().getService(TokenService.class);
        var token = new AuthToken();
        token.setDescription(description);
        service.add(token);
        service.store();
        return token.getToken();
    }

    /**
     * Creates a session-bound token associated with the caller's user agent.
     */
    @ProtectedMethod
    @JRPCMethod
    public String createToken(@JRPCContext CallContext call) {
        var UA = call.getRequest().getHeader("User-Agent");
        if (UA == null) {
            UA = "Unknown user agent";
        }
        var authManager = context.getServiceManager().getService(AuthManager.class);
        var token = authManager.createToken(UA + " " + new Date());
        authManager.setSessionToken(call.getSession(), token);
        return token.getToken();
    }

    /**
     * Attempts to authenticate the session using a password.
     */
    @JRPCMethod
    public boolean startSessionByPassword(@JRPCArgument(name = "password") String password, @JRPCContext CallContext call) {
        var authManager = context.getServiceManager().getService(AuthManager.class);
        if (authManager.validatePassword(password)) {
            authManager.setSessionAuthState(call.getSession(), true);
            return true;
        }
        return false;
    }

    /**
     * Attempts to authenticate the session using an existing token.
     */
    @JRPCMethod
    public boolean startSessionByToken(@JRPCArgument(name = "token") String token, @JRPCContext CallContext call) {
        var authManager = context.getServiceManager().getService(AuthManager.class);
        if (authManager.validateToken(token)) {
            authManager.setSessionAuthState(call.getSession(), true);
            return true;
        }
        return false;
    }

    /**
     * Reports whether the current session is authenticated.
     */
    @JRPCMethod
    public boolean isAuthenticated(@JRPCContext CallContext call) {
        var authManager = context.getServiceManager().getService(AuthManager.class);
        return authManager.getSessionAuthState(call.getSession());
    }


    /**
     * Logs out the current session and invalidates its token when present.
     */
    @JRPCMethod
    public void logout(@JRPCContext CallContext call) {
        var authManager = context.getServiceManager().getService(AuthManager.class);
        authManager.setSessionAuthState(call.getSession(), false);
        var token = authManager.getSessionToken(call.getSession());
        if (token != null) {
            authManager.invalidateToken(token);
            authManager.setSessionToken(call.getSession(), null);
        }
    }
}
