package riomhaire.lti.core.business.commands;

import com.auth0.jwt.JWT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import riomhaire.lti.core.adapters.token.JWKBasedJwtToMapAdapter;
import riomhaire.lti.core.model.LTIMessage;
import riomhaire.lti.core.model.LTIMessageType;
import riomhaire.lti.core.model.configuration.ClientConfiguration;
import riomhaire.lti.core.model.interfaces.ApplicationRegistry;
import riomhaire.lti.core.model.interfaces.Command;
import riomhaire.lti.core.model.interfaces.DecodeException;

import java.util.HashMap;
import java.util.Map;

/**
 * Defines  execute command on message P within environment E returning T
 *
 *
 * Processes lti launch content and create deep link messages - IE claims within a JWT
 * Delegates to the actual handler  depending on if its just a launch or a create-deep-link launch
 */
public class LTILaunchCommand implements Command<ModelAndView, ApplicationRegistry<ModelAndView,LTIMessage>, LTIMessage> {
    public static final String CLAIM_MESSAGE_TYPE = "https://purl.imsglobal.org/spec/lti/claim/message_type";
    public static final String LTI_RESOURCE_LINK_REQUEST = "LtiResourceLinkRequest";
    public static final String LTI_DEEP_LINKING_REQUEST = "LtiDeepLinkingRequest";
    // Logger
    Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public ModelAndView execute(ApplicationRegistry<ModelAndView,LTIMessage> registry, LTIMessage message) {
        var idToken = message.getQueryParams().get("id_token");
        var jwtToken = message.getQueryParams().get("JWT");
        // Crack open so we can find the config
        String token = (idToken != null) ? idToken : jwtToken;
        var jwt = JWT.decode(token);
        // Lookup client id and issuer
        var issuer = jwt.getClaim("iss").asString();
        var clientId = jwt.getClaim("aud").asString();

        var toolRegistration = registry.clientRegistrationResolver().lookup(String.format("%s%s",issuer, clientId));
        var claims = new HashMap<String,Object>();
        if( toolRegistration.isPresent()) {
            claims.putAll(decodeToken(token, issuer, clientId, toolRegistration.get()));
        }

        // OK based off of the message delegate to the right sub-action
        var messageType = String.valueOf(claims.get(CLAIM_MESSAGE_TYPE));
        return switch (messageType) {
            case LTI_RESOURCE_LINK_REQUEST:
                // Build Message
                var delegateLaunchMessage = LTIMessage.builder()
                        .messageType(LTIMessageType.LaunchContent.name())
                        .metadata(message.getMetadata())
                        .queryParams(message.getQueryParams())
                        .claims(claims)
                        .build();
                yield registry.commandDispatcher().dispatch(registry, delegateLaunchMessage);
            case LTI_DEEP_LINKING_REQUEST:
                // Build Message
                var delegateDeepLinkMessage = LTIMessage.builder()
                        .messageType(LTIMessageType.SelectDeepLinkContent.name())
                        .metadata(message.getMetadata())
                        .queryParams(message.getQueryParams())
                        .claims(claims)
                        .build();
                yield registry.commandDispatcher().dispatch(registry, delegateDeepLinkMessage);
            default:
                throw new IllegalStateException("Unexpected value: " + messageType);
        };
    }

    /**
     * This method using info tool configuration for jwks etc
     *
     * @param token            token to decode
     * @param issuer           who the issuer was
     * @param clientId         the client id
     * @param clientConfiguration the tool configuration
     * @return Model and View to Return the code
     */
    private Map<String, Object> decodeToken(String token, String issuer, String clientId, ClientConfiguration clientConfiguration) {
        var claims = new HashMap<String, Object>();

        if (clientConfiguration != null) {
            // Validate JWT to verify its by who they say they are
            var adapter = new JWKBasedJwtToMapAdapter(clientConfiguration.getJwksUrl(), clientConfiguration.isSkipVerification());
            try {
                claims.putAll(adapter.decode(token));
            } catch (DecodeException e) {
                // OK not valid
                claims.put("error", "cannot verify token because of: " + e.toString());
            }
        } else {
            claims.put("error", "cannot find client for " + issuer + "  client-id " + clientId);
        }
        return claims;
    }


}
