package io.apicurio.registry.ccompat.rest.v7.impl;

import io.apicurio.registry.auth.Authorized;
import io.apicurio.registry.auth.AuthorizedLevel;
import io.apicurio.registry.auth.AuthorizedStyle;
import io.apicurio.registry.ccompat.rest.v7.ConfigResource;
import io.apicurio.registry.ccompat.rest.v7.beans.ConfigUpdateRequest;
import io.apicurio.registry.ccompat.rest.v7.beans.GlobalConfigResponse;
import io.apicurio.registry.ccompat.rest.v7.beans.SubjectConfigResponse;
import io.apicurio.registry.logging.Logged;
import io.apicurio.registry.logging.audit.Audited;
import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck;
import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck;
import io.apicurio.registry.model.GA;
import io.apicurio.registry.rest.MethodMetadata;
import io.apicurio.registry.rules.RulesProperties;
import io.apicurio.registry.rules.compatibility.CompatibilityLevel;
import io.apicurio.registry.storage.dto.RuleConfigurationDto;
import io.apicurio.registry.storage.error.RuleNotFoundException;
import io.apicurio.registry.types.RuleType;
import io.apicurio.registry.utils.Functional.Runnable1Ex;
import jakarta.inject.Inject;
import jakarta.interceptor.Interceptors;

import java.util.Optional;
import java.util.function.Supplier;

import static io.apicurio.registry.rest.MethodParameterKeys.MPK_ARTIFACT_ID;
import static io.apicurio.registry.rest.MethodParameterKeys.MPK_RULE;

@Interceptors({ ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class })
@Logged
public class ConfigResourceImpl extends AbstractResource implements ConfigResource {

    @Inject
    RulesProperties rulesProperties;

    //FIMXE: The configuration implementation is not complete. We only do global and artifact level compatibility. Confluent has more options.

    private GlobalConfigResponse getCompatibilityLevel(Supplier<String> supplyLevel) {
        try {
            // We're assuming the configuration == compatibility level
            // TODO make it more explicit
            GlobalConfigResponse response = new GlobalConfigResponse();
            response.setCompatibilityLevel(Optional.of(CompatibilityLevel.valueOf(supplyLevel.get())).get().name());
            return response;
        }
        catch (RuleNotFoundException ex) {
            GlobalConfigResponse response = new GlobalConfigResponse();
            var compatRuleDto = rulesProperties.getDefaultGlobalRuleConfiguration(RuleType.COMPATIBILITY);
            if (compatRuleDto != null) {
                // Fallback to the default global rule
                response.setCompatibilityLevel(compatRuleDto.getConfiguration());
            } else {
                // Fallback to NONE if no default rule is found
                response.setCompatibilityLevel(CompatibilityLevel.NONE.name());
            }
            return response;
        }
    }

    private <X extends Exception> void updateCompatibilityLevel(String level,
                                                                Runnable1Ex<RuleConfigurationDto, X> updater) throws X {
        try {
            CompatibilityLevel.valueOf(level);
        }
        catch (IllegalArgumentException ex) {
            throw new IllegalArgumentException("Illegal compatibility level: " + level);
        }
        updater.run(RuleConfigurationDto.builder().configuration(level).build());
        // TODO config should take CompatibilityLevel as param
    }

    @Override
    @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Read)
    public GlobalConfigResponse getGlobalConfig() {
        return getCompatibilityLevel(() -> storage.getGlobalRule(RuleType.COMPATIBILITY).getConfiguration());
    }

    @Override
    @MethodMetadata(extractParameters = { "0", MPK_RULE })
    @Audited
    @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Admin)
    public GlobalConfigResponse updateGlobalConfig(ConfigUpdateRequest request) {
        updateCompatibilityLevel(request.getCompatibility(), dto -> {
            if (!doesGlobalRuleExist(RuleType.COMPATIBILITY)) {
                storage.createGlobalRule(RuleType.COMPATIBILITY, dto);
            }
            else {
                storage.updateGlobalRule(RuleType.COMPATIBILITY, dto);
            }
        });

        GlobalConfigResponse response = new GlobalConfigResponse();
        response.setCompatibilityLevel(request.getCompatibility());
        return response;
    }

    @Override
    @MethodMetadata(extractParameters = { "0", MPK_RULE })
    @Audited
    @Authorized(style = AuthorizedStyle.None, level = AuthorizedLevel.Admin)
    public GlobalConfigResponse deleteGlobalConfig() {
        GlobalConfigResponse current = getGlobalConfig();
        try {
            storage.deleteGlobalRule(RuleType.COMPATIBILITY);
        } catch (RuleNotFoundException e) {
            // Already at default, nothing to delete
        }
        return current;
    }

    @Override
    @MethodMetadata(extractParameters = { "0", MPK_ARTIFACT_ID, "1", MPK_RULE })
    @Audited
    @Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Write)
    public GlobalConfigResponse updateSubjectConfig(String subject, String groupId, ConfigUpdateRequest request) {
        final GA ga = getGA(groupId, subject);

        updateCompatibilityLevel(request.getCompatibility(), dto -> {
            if (!doesArtifactRuleExist(ga.getRawArtifactId(), RuleType.COMPATIBILITY,
                    ga.getRawGroupIdWithNull())) {
                storage.createArtifactRule(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(),
                        RuleType.COMPATIBILITY, dto);
            }
            else {
                storage.updateArtifactRule(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(),
                        RuleType.COMPATIBILITY, dto);
            }
        });

        GlobalConfigResponse response = new GlobalConfigResponse();
        response.setCompatibilityLevel(request.getCompatibility());
        return response;
    }

    @Override
    @Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Read)
    public SubjectConfigResponse getSubjectConfig(String subject, Boolean defaultToGlobal,
                                                  String groupId) {
        final GA ga = getGA(groupId, subject);
        SubjectConfigResponse response = new SubjectConfigResponse();

        response.setCompatibilityLevel(SubjectConfigResponse.CompatibilityLevel.valueOf(getCompatibilityLevel(() -> {
            String level;
            try {
                level = storage.getArtifactRule(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(),
                        RuleType.COMPATIBILITY).getConfiguration();
            }
            catch (RuleNotFoundException e) {
                if (defaultToGlobal != null && defaultToGlobal) {
                    level = storage.getGlobalRule(RuleType.COMPATIBILITY).getConfiguration();
                }
                else {
                    throw e;
                }
            }
            return level;
        }).getCompatibilityLevel()));

        return response;
    }

    @Override
    @MethodMetadata(extractParameters = { "0", MPK_ARTIFACT_ID })
    @Audited
    @Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Write)
    public GlobalConfigResponse deleteSubjectConfig(String subject, String groupId) {
        final GA ga = getGA(groupId, subject);
        final GlobalConfigResponse compatibilityLevel = getCompatibilityLevel(() -> storage
                .getArtifactRule(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), RuleType.COMPATIBILITY)
                .getConfiguration());
        if (!CompatibilityLevel.NONE.name().equals(compatibilityLevel.getCompatibilityLevel())) {
            storage.deleteArtifactRule(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(),
                    RuleType.COMPATIBILITY);
        }
        return compatibilityLevel;
    }
}
