package com.alibaba.spring.boot.rsocket.broker.services;

import com.alibaba.rsocket.RSocketAppContext;
import com.alibaba.rsocket.events.ConfigEvent;
import com.alibaba.rsocket.metadata.AppMetadata;
import com.alibaba.rsocket.observability.RsocketErrorCode;
import com.alibaba.spring.boot.rsocket.broker.responder.RSocketBrokerHandlerRegistry;
import com.alibaba.spring.boot.rsocket.broker.security.AuthenticationService;
import com.alibaba.spring.boot.rsocket.broker.security.JwtPrincipal;
import com.alibaba.spring.boot.rsocket.broker.security.RSocketAppPrincipal;
import io.cloudevents.v1.CloudEventBuilder;
import io.cloudevents.v1.CloudEventImpl;
import io.rsocket.exceptions.InvalidException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.UUID;

/**
 * config controller
 *
 * @author leijuan
 */
@RestController
@RequestMapping("/config")
public class ConfigController {
    @Autowired
    private ConfigurationService configurationService;
    @Autowired
    private AuthenticationService authenticationService;
    @Autowired
    private RSocketBrokerHandlerRegistry handlerRegistry;
    @Value("${rsocket.broker.auth-required:true}")
    private boolean authRequired;

    @PostMapping("/refresh/{appName}")
    public Mono<String> refresh(@PathVariable(name = "appName") String appName,
                                @RequestParam(name = "ip", required = false) String ip,
                                @RequestParam(name = "id", required = false) String id,
                                @RequestHeader(name = HttpHeaders.AUTHORIZATION) String jwtToken,
                                @RequestBody String body) {
        RSocketAppPrincipal appPrincipal = parseAppPrincipal(jwtToken);
        if (appPrincipal != null && appPrincipal.getSubject().equalsIgnoreCase("rsocket-admin")) {
            //update config for ip or id
            if (ip != null || id != null) {
                CloudEventImpl<ConfigEvent> configEvent = CloudEventBuilder.<ConfigEvent>builder()
                        .withId(UUID.randomUUID().toString())
                        .withTime(ZonedDateTime.now())
                        .withSource(URI.create("broker://" + RSocketAppContext.ID))
                        .withType(ConfigEvent.class.getCanonicalName())
                        .withDataContentType("text/x-java-properties")
                        .withData(new ConfigEvent(appName, "text/x-java-properties", body))
                        .build();
                return Flux.fromIterable(handlerRegistry.findByAppName(appName)).filter(handler -> {
                    AppMetadata appMetadata = handler.getAppMetadata();
                    return appMetadata.getUuid().equals(id) || appMetadata.getIp().equals(ip);
                }).flatMap(handler -> handler.fireCloudEventToPeer(configEvent)).then(Mono.just("success"));
            } else {
                return configurationService.put(appName + ":application.properties", body).map(aVoid -> "success");
            }
        } else {
            return Mono.error(new InvalidException(RsocketErrorCode.message("RST-500403")));
        }
    }

    @GetMapping("/last/{appName}")
    public Mono<String> fetch(@PathVariable(name = "appName") String appName, @RequestHeader(name = HttpHeaders.AUTHORIZATION) String jwtToken) {
        RSocketAppPrincipal appPrincipal = parseAppPrincipal(jwtToken);
        if (appPrincipal != null && (appName.equalsIgnoreCase(appPrincipal.getSubject()) || appPrincipal.getSubject().equalsIgnoreCase("rsocket-admin"))) {
            return configurationService.get(appName + ":application.properties");
        } else {
            return Mono.error(new InvalidException(RsocketErrorCode.message("RST-500403")));
        }
    }

    private RSocketAppPrincipal parseAppPrincipal(String jwtToken) {
        if (authRequired) {
            return authenticationService.auth("Bearer", jwtToken.substring(jwtToken.indexOf(" ") + 1));
        } else {
            return new JwtPrincipal(UUID.randomUUID().toString(), "rsocket-admin",
                    Collections.singletonList("mock_owner"),
                    new HashSet<>(Collections.singletonList("admin")),
                    Collections.emptySet(),
                    new HashSet<>(Collections.singletonList("default")),
                    new HashSet<>(Collections.singletonList("1")));
        }
    }
}