package cz.muni.ics.oauth2.web.endpoint;

import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oauth2.model.DynamicallyRegisteredRequestBody;
import cz.muni.ics.oauth2.model.DynamicallyRegisteredRequestBodyResponse;
import cz.muni.ics.oauth2.service.DynamicClientRegistrationService;
import cz.muni.ics.oauth2.view.DynamicRegistrationEndpointView;
import cz.muni.ics.openid.connect.view.JsonEntityView;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Map;
import java.util.Set;

import static cz.muni.ics.openid.connect.view.HttpCodeView.CODE;

@Controller
@Slf4j
public class DynamicRegistrationEndpoint {

    public static final String URL = "register";

    public static final String PARAM_CLIENT_ID = "dynregClientId";

    public static final String PATH_PARAM_CLIENT_ID = '{' + PARAM_CLIENT_ID + '}';

    public static final String GRANT_TYPE = "grant_type";

    public static final String CLIENT_ID = "client_id";

    public static final String CLIENT_CREDENTIALS = "client_credentials";

    public static final String SCOPE_DYNREG = "client_dynamic_registration";

    private final DynamicClientRegistrationService dynamicClientRegistrationService;

    @Autowired
    public DynamicRegistrationEndpoint(DynamicClientRegistrationService dynamicClientRegistrationService) {
        this.dynamicClientRegistrationService = dynamicClientRegistrationService;
    }

    @PostMapping(
            value = "/" + URL,
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public String registerClient(
            @RequestBody DynamicallyRegisteredRequestBody client,
            Authentication auth,
            Map<String, Object> model
    ) {
        OAuth2Authentication authentication = (OAuth2Authentication) auth;
        Set<String> scope = authentication.getOAuth2Request().getScope();
        if (scope == null || !scope.contains(SCOPE_DYNREG)) {
            throw new InvalidRequestException("The provided token does not contain the required scope");
        }
        validateTokenRequestParams(authentication);

        String tokenClientId = authentication.getOAuth2Request().getClientId();
        ClientDetailsEntity registeredClient = dynamicClientRegistrationService.saveClient(tokenClientId, client);

        DynamicallyRegisteredRequestBodyResponse response = new DynamicallyRegisteredRequestBodyResponse(
                registeredClient,
                null, //TODO: generate token, at the moment we do not support any updates
                null, //TODO: this.config.getConfigBean().getIssuer(true) + URL + '/' + registeredClient.getClientId(),
                System.currentTimeMillis() / 1000L,
                null
        );

        model.put(DynamicRegistrationEndpointView.ENTITY, response);
        model.put(CODE, HttpStatus.CREATED);
        return DynamicRegistrationEndpointView.VIEWNAME;
    }

    private void validateTokenRequestParams(OAuth2Authentication auth) {
        Map<String, String> tokenRequestParameters = auth.getOAuth2Request().getRequestParameters();
        if (tokenRequestParameters == null) {
            throw new InvalidRequestException("Could not extract token request parameters from the used token");
        } else if (!CLIENT_CREDENTIALS.equals(tokenRequestParameters.getOrDefault(GRANT_TYPE, ""))) {
            throw new InvalidRequestException("Token has to be issued using client_credentials grant type");
        } else if (!StringUtils.hasText(tokenRequestParameters.get(CLIENT_ID))) {
            throw new InvalidRequestException("Token has no associated client identifier");
        }
    }

    @DeleteMapping(
            value = "/" + URL + "/" + PATH_PARAM_CLIENT_ID
    )
    public ResponseEntity<Void> deregisterClient(
            @PathVariable(PARAM_CLIENT_ID) String dynregClientId,
            Authentication auth
    ) {
        OAuth2Authentication authentication = (OAuth2Authentication) auth;
        Set<String> scope = authentication.getOAuth2Request().getScope();
        if (scope == null || !scope.contains(SCOPE_DYNREG)) {
            throw new InvalidRequestException("The provided token does not contain the required scope");
        } else if (!StringUtils.hasText(dynregClientId)) {
            throw new InvalidRequestException("Path parameter identifying the client to be deleted not specified");
        }

        validateTokenRequestParams(authentication);

        String tokenClientId = authentication.getOAuth2Request().getClientId();
        dynamicClientRegistrationService.removeClient(tokenClientId, dynregClientId);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

}