package com.sonymusic.dx.dxuiservice.controllers;

import com.sonymusic.dx.dxuiservice.dto.USMAuthorizationRequest;
import com.sonymusic.dx.dxuiservice.dto.USMAuthorizationResponse;
import com.sonymusic.dx.dxuiservice.exception.AuthorizationException;
import com.sonymusic.dx.dxuiservice.properties.USMApiClientProperties;
import com.sonymusic.dx.dxuiservice.usm.USMAuthorizationClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Objects;
import java.util.UUID;

/**
 * USM Authorization controller.
 */
@Tag(name = "Authentication", description = "USM authentication and authorization endpoints")
@Slf4j
@Controller
@RequiredArgsConstructor
public class USMAuthorizationController {

    private final USMAuthorizationClient authorizationClient;
    private final USMApiClientProperties usmApiClientProperties;

    @Value("${usm.authorization.redirect-uri}")
    private String redirectUri;
    @Value("${usm.authorization.post-login.path}")
    private String postLoginPath;
    @Value("${usm.authorization.post-login.hosts.local}")
    private String postLoginLocalHost;
    @Value("${usm.authorization.post-login.hosts.remote}")
    private String postLoginRemoteHost;

    /**
     * Get application token via USM application code.
     *
     * @param code  Authorization code from USM.
     * @param state State from USM.
     * @return Redirect to frontend with token in URL fragment.
     */
    @Operation(summary = "USM OAuth callback",
            description = "Handles OAuth callback from USM, exchanges authorization code for token.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "302", description = "Redirects to frontend with token or error",
                    content = @Content)
    })
    @ResponseBody
    @GetMapping(value = "/usm/callback")
    public RedirectView authorize(
            @Parameter(description = "Authorization code from USM OAuth provider", required = true)
            @RequestParam String code,
            @Parameter(description = "State parameter", required = true)
            @RequestParam String state) {
        log.info("Requesting application token for code: {} state: {}.", code, state);

        String host = resolveRedirectionHost(state);
        var url = "%s/%s".formatted(host, postLoginPath);
        try {
            return new RedirectView("%s?token=%s".formatted(url, getAuthToken(code, state)));
        } catch (AuthorizationException e) {
            return new RedirectView("%s?error=authorization_error".formatted(url));
        }
    }

    private String resolveRedirectionHost(String state) {
        var url = postLoginRemoteHost;
        if (state.endsWith("local")) {
            url = postLoginLocalHost;
        } else if (state.endsWith("remote")) {
            url = postLoginRemoteHost;
        }

        return url;
    }

    private String getAuthToken(String code, String state) throws AuthorizationException {
        var authorizationRequest = USMAuthorizationRequest.builder()
                .clientId(usmApiClientProperties.getClientId())
                .clientSecret(usmApiClientProperties.getClientSecret())
                .code(code)
                .state(state)
                .redirectUri(redirectUri)
                .nonce(UUID.randomUUID().toString())
                .grantType("authorization_code")
                .build();

        USMAuthorizationResponse response;
        try {
            response = authorizationClient.authorize(authorizationRequest);
            log.info("Access token: {}. id_token: {} ", response.getAccessToken(),
                    response.getIdToken());
        } catch (Exception e) {
            log.error("Exception during token request: {}", e.getMessage());
            throw new AuthorizationException("Token request failed.");
        }

        if (Objects.isNull(response.getIdToken())) {
            log.error("Token missing in response.");
            throw new AuthorizationException("Token missing in response.");
        }

        return response.getIdToken();
    }

}
