package com.cameleer.server.app.config;

import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Configuration
@SecurityScheme(name = "bearer", type = SecuritySchemeType.HTTP,
        scheme = "bearer", bearerFormat = "JWT")
public class OpenApiConfig {

    /**
     * Core domain models that always have all fields populated.
     * Mark all their properties as required so the generated TypeScript
     * types are non-optional.
     */
    private static final Set<String> ALL_FIELDS_REQUIRED = Set.of(
            "ExecutionSummary", "ExecutionDetail", "ExecutionStats",
            "StatsTimeseries", "TimeseriesBucket",
            "SearchResultExecutionSummary", "UserInfo",
            "ProcessorNode",
            "AppCatalogEntry", "RouteSummary", "AgentSummary",
            "RouteMetrics", "AgentEventResponse", "AgentInstanceResponse",
            "ProcessorMetrics", "AgentMetricsResponse", "MetricBucket"
    );

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .info(new Info().title("Cameleer Server API").version("1.0"))
                .addSecurityItem(new SecurityRequirement().addList("bearer"))
                .servers(List.of(new Server().url("/api/v1").description("Relative")));
    }

    @Bean
    public OpenApiCustomizer pathPrefixStripper() {
        return openApi -> {
            var original = openApi.getPaths();
            if (original == null) return;
            String prefix = "/api/v1";
            var stripped = new Paths();
            for (var entry : original.entrySet()) {
                String path = entry.getKey();
                stripped.addPathItem(
                        path.startsWith(prefix) ? path.substring(prefix.length()) : path,
                        entry.getValue());
            }
            openApi.setPaths(stripped);
        };
    }

    @Bean
    @SuppressWarnings("unchecked")
    public OpenApiCustomizer schemaCustomizer() {
        return openApi -> {
            var schemas = openApi.getComponents().getSchemas();
            if (schemas == null) return;

            // Add children to ProcessorNode if missing (recursive self-reference)
            if (schemas.containsKey("ProcessorNode")) {
                Schema<Object> processorNode = schemas.get("ProcessorNode");
                if (processorNode.getProperties() != null
                        && !processorNode.getProperties().containsKey("children")) {
                    Schema<?> selfRef = new Schema<>().$ref("#/components/schemas/ProcessorNode");
                    ArraySchema childrenArray = new ArraySchema().items(selfRef);
                    processorNode.addProperty("children", childrenArray);
                }
            }

            // Mark all fields as required for core domain models
            for (String schemaName : ALL_FIELDS_REQUIRED) {
                if (schemas.containsKey(schemaName)) {
                    Schema<Object> schema = schemas.get(schemaName);
                    if (schema.getProperties() != null) {
                        schema.setRequired(new ArrayList<>(schema.getProperties().keySet()));
                    }
                }
            }
        };
    }
}
