// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*
 * Copyright (c) 杭州颉创科技有限公司 2025. All rights reserved.
 * This source file is licensed under the MIT License found in the
 * LICENSE file in the root directory of this source tree.
 */

package simcu::simapi.openapi

import std.reflect.*
import std.collection.*
import std.unicode.*
import soulsoft_web_mvc.*
import soulsoft_web_http.*
import soulsoft_web_routing.http.*
import soulsoft_web_mvc.utilities.*
import simcu::simapi.openapi.models.*
import soulsoft_extensions_options.*
import soulsoft_web_mvc.controllers.*
import soulsoft_extensions_injection.*
import soulsoft_web_mvc.abstractions.*
import simcu::simapi.configurations.*
import simcu::simapi.annotations.*

/**
 * @brief 提供 OpenAPI 文档生成功能。
 */
protected class OpenApiDocumentService {
    private let _documentName: String
    private let _endpointSource: EndpointDataSource
    private let _openApiSchemaService: OpenApiSchemaService
    private let _openApiParameterService: OpenApiParameterService

    /**
     * @brief 创建 OpenAPI 文档生成服务实例。
     * @param endpointSource 终结点数据源。
     * @param openApiSchemaService OpenAPI Schema 服务。
     * @param openApiParameterService OpenAPI 参数服务。
     * @param documentName 当前正在生成的文档名称。
     */
    public init(
        documentName: String,
        endpointSource: EndpointDataSource,
        openApiSchemaService: OpenApiSchemaService,
        openApiParameterService: OpenApiParameterService
    ) {
        _documentName = documentName
        _endpointSource = endpointSource
        _openApiSchemaService = openApiSchemaService
        _openApiParameterService = openApiParameterService
    }

    /**
     * @brief 生成 OpenAPI 文档。
     * @param services 服务提供器，用于构建操作转换器上下文。
     * @return 生成后的 OpenAPI 文档。
     */
    public func getOpenApiDocument(services: IServiceProvider): OpenApiDocument {
        let document = OpenApiDocument()
        // 从 SimApiDocOptions 中查找当前文档组的标题和描述
        var docTitle = "OpenApi | ${_documentName}"
        var docDescription: ?String = None
        if (let Some(simOptions) <- services.get<SimApiOptions>()) {
            docTitle = simOptions.simApiDocOptions.documentTitle
            for (g in simOptions.simApiDocOptions.distinctGroups()) {
                if (g.id == _documentName) {
                    docTitle = g.name
                    if (!g.description.isEmpty()) {
                        docDescription = g.description
                    }
                    break
                }
            }
        }
        let info = OpenApiInfo(title: docTitle, version: "1.0.0")
        info.description = docDescription
        document.info = info
        document.paths = createOpenApiPaths(services)
        document.components = createOpenApiComponents()
        applyAuthSecurity(document, services)
        return document
    }

    /// 当启用 SimApiAuth 时，为文档注册 Token 认证方案（apiKey Header）。
    /// 仅注册 scheme，不添加全局 security；由 createOpenApiOperation 按接口注解逐个添加。
    private func applyAuthSecurity(document: OpenApiDocument, services: IServiceProvider): Unit {
        let enableAuth = services.get<SimApiOptions>().flatMap {f => Some(f.enableSimApiAuth)} ?? false
        if (!enableAuth) {
            return
        }
        if (let Some(components) <- document.components) {
            let scheme = OpenApiSecurityScheme()
            scheme.name = Some("Token")
            scheme.location = Some("header")
            scheme.description = Some("登录后返回的 Token（Header: Token）")
            components.securitySchemes.add("Token", scheme)
        }
    }

    private func createOpenApiPaths(services: IServiceProvider) {
        let paths = OpenApiPaths()
        let openApiOptions = services.getOrThrow<IOptionsMonitor<OpenApiOptions>>().get(_documentName)
        for (endpoint in _endpointSource.endpoints |> filterMap {f => f as RouteEndpoint} where !isIgnore(endpoint)) {
            if (!isShouldInclude(endpoint, openApiOptions.includeUnGrouped)) {
                continue
            }

            // 构建用于显示的url路径
            let key = if (let Some(displayText) <- endpoint.routePattern.displayText) {
                "/${displayText.trimStart('/')}"
            } else {
                "/${endpoint.routePattern.rawText.trimStart('/')}"
            }

            let pathItem = if (paths.contains(key)) {
                paths[key]
            } else {
                let item = OpenApiPathItem()
                paths[key] = item
                item
            }

            // add operations
            if (let Some(metadata) <- endpoint.metadata.getMetadata<IHttpMethodMetadata>()) {
                for (httpMethod in metadata.httpMethods) {
                    let operationType = OperationType.parse(httpMethod)
                    let actionDescriptor = endpoint.metadata.getMetadata<ControllerActionDescriptor>()
                    let operation = createOpenApiOperation(endpoint, actionDescriptor)
                    pathItem.operations.add(operationType, operation)
                }
            }
        }
        return paths
    }

    private func isShouldInclude(endpoint: RouteEndpoint, includeUnGrouped: Bool) {
        let rawGroupNames = endpoint.metadata.getOrderedMetadata<SimApiDoc>() |>
            filterMap { f => f.groupNames } |> collectArray
        if (rawGroupNames.isEmpty()) {
            // 未标注 groupNames 的接口：仅进入默认文档（includeUnGrouped=true 的文档）
            return includeUnGrouped
        }
        // 合并所有注解的 groupNames，按逗号拆分并去空白
        let names = ArrayList<String>()
        for (raw in rawGroupNames) {
            for (part in raw.split(",")) {
                let trimmed = part.trim()
                if (trimmed.isEmpty()) {
                    continue
                }
                if (!names.contains(trimmed)) {
                    names.add(trimmed)
                }
            }
        }
        if (names.isEmpty()) {
            // 仅标注了空串：视为未分组
            return includeUnGrouped
        }
        // "*" 表示出现在所有文档
        if (names.contains("*")) {
            return true
        }
        return names.contains(_documentName)
    }

    private func createOpenApiComponents() {
        let components = OpenApiComponents()
        let schemas = _openApiSchemaService.getSchemas()
        components.schemas = schemas
        return components
    }

    /*
    生成每个终结点的文档
     */
    private func createOpenApiOperation(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor): OpenApiOperation {
        let operation = OpenApiOperation()

        // tags
        operation.tags.add(all: createOpenApiOperationTags(endpoint, actionDescriptor))

        // parameters
        if (let Some(actionDescriptor) <- actionDescriptor) {
            operation.parameters = _openApiParameterService.createParameters(actionDescriptor)
        }

        // requestBody
        if (let Some(actionDescriptor) <- actionDescriptor) {
            operation.requestBody = createOpenApiOperationRequestBody(endpoint, actionDescriptor)
        }

        // response
        operation.response = createOpenApiOperationResponses(endpoint, actionDescriptor)

        // summary
        if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.summary.isSome()}) {
            if (let Some(summary) <- metadata.summary) {
                operation.summary = summary
            }
        }

        // description
        if (let Some(metadata) <- endpoint
            .metadata
            .getLastMetadata<SimApiDoc> {f => f.description.isSome()}) {
            if (let Some(description) <- metadata.description) {
                operation.description = description
            }
        }

        // operationId
        if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.name.isSome()}) {
            operation.operationId = metadata.name
        }

        // security：仅标注了 @SimApiAuth 的接口才显示锁图标
        if (let Some(_) <- endpoint.metadata.getMetadata<SimApiAuth>()) {
            let requirement = OpenApiSecurityRequirement()
            requirement.addScheme("Token")
            operation.security.add(requirement)
        }

        return operation
    }

    private func createOpenApiOperationTags(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
        let result = ArrayList<OpenApiTag>()
        // tags
        if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.tags.isSome()}) {
            if (let Some(tags) <- metadata.tags) {
                for (name in tags.split(',')) {
                    result.add(OpenApiTag(name: name))
                }
            }
        }
        if (!result.isEmpty()) {
            return result
        }

        if (let Some(actionDescriptor) <- actionDescriptor) {
            result.add(OpenApiTag(name: actionDescriptor.controllerName))
        } else {
            result.add(OpenApiTag(name: "default"))
        }

        return result
    }

    /*
    来自请求体的参数
     */
    private func createOpenApiOperationRequestBody(endpoint: RouteEndpoint, actionDescriptor: ControllerActionDescriptor): ?OpenApiRequestBody { // cjlint-ignore !G.FUN.02 endpoint 未使用
        var requestBody = OpenApiRequestBody(required: true)
        let parameters = HashMap<String, OpenApiSchema>()
        for (parameter in actionDescriptor.actionFunction.parameters) {
            let typeInfo = Nullable.getUnderlyingType(parameter.typeInfo) ?? parameter.typeInfo
            let hasBindingSourceMetadata = parameter.annotations |> any {f => f is IBindingSourceMetadata}
            if (parameter.findAnnotation<FromBody>().isSome() || !hasBindingSourceMetadata) {
                let schema = _openApiSchemaService.createSchema(typeInfo)
                requestBody.content.add("application/json", OpenApiMediaType(schema))
            } else if (parameter.findAnnotation<FromForm>().isSome()) {
                if (let classTypeInfo: ClassTypeInfo <- typeInfo) {
                    let schema = _openApiSchemaService.createInlineSchema(typeInfo)
                    for ((name, property) in schema.properties) {
                        parameters.add(name, property)
                    }
                } else {
                    parameters.add(BindingNameUtilities.resolveFormName(parameter), _openApiSchemaService.createSchema(typeInfo))
                }
            }
        }
        if (parameters.size > 0) {
            let formSchema = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT)
            for ((name, schema) in parameters) {
                formSchema.properties.add(name, schema)
            }
            requestBody.content.add("multipart/form-data", OpenApiMediaType(formSchema))
        }
        if (requestBody.content.size > 0) {
            return requestBody
        }
        return None
    }

    /*
      生成响应描述
      */
    private func createOpenApiOperationResponses(endpoint: RouteEndpoint, actionDescriptor: ?ControllerActionDescriptor) {
        let responses = OpenApiResponses()

        // 获取action的返回类型：优先从 actionDescriptor，其次从 ApiResponseTypeMetadata（动态路由）
        let returnType: ?TypeInfo = if (let Some(actionDescriptor) <- actionDescriptor) {
            Nullable.getUnderlyingType(actionDescriptor.actionFunction.returnType) ?? actionDescriptor
                .actionFunction
                .returnType
        } else if (let Some(meta) <- endpoint.metadata.getMetadata<ApiResponseTypeMetadata>()) {
            meta.responseType
        } else {
            None
        }

        // 判断是否跳过封装：@OriginResponse 或返回类型已是 SimApiBaseResponse/SimApiResponse/SimApiDataResponse
        var skipWrap = false
        if (let Some(_) <- endpoint.metadata.getMetadata<OriginResponse>()) {
            skipWrap = true
        }
        if (let Some(rt) <- returnType) {
            let rtName = rt.name
            if (rtName == "SimApiBaseResponse" || rtName == "SimApiDataResponse" || rtName.startsWith("SimApiResponse")) {
                skipWrap = true
            }
        }

        if (let Some(returnType) <- returnType && returnType != TypeInfo.of<Unit>()) {
            let response = OpenApiResponse("OK")
            let dataSchema = _openApiSchemaService.createSchema(returnType)
            let schema = if (skipWrap) { dataSchema } else { wrapResponseSchema(dataSchema) }
            response.content.add("text/plain", OpenApiMediaType(schema))
            response.content.add("application/json", OpenApiMediaType(schema))
            response.content.add("text/json", OpenApiMediaType(schema))
            responses.add("200", response)
        } else {
            // Unit（void）→ SimApiBaseResponse {code, message}
            let response = OpenApiResponse("OK")
            let schema = if (skipWrap) { OpenApiSchema() } else { wrapResponseSchema(None) }
            response.content.add("text/plain", OpenApiMediaType(schema))
            response.content.add("application/json", OpenApiMediaType(schema))
            response.content.add("text/json", OpenApiMediaType(schema))
            responses.add("200", response)
        }
        return responses
    }

    /// 将 schema 包装为统一响应结构 {code, message, data?}。
    private func wrapResponseSchema(dataSchema: ?OpenApiSchema): OpenApiSchema {
        let wrapped = OpenApiSchema(`type`: OpenApiSchemaTypes.OBJECT)
        let codeSchema = OpenApiSchema(`type`: "integer", format: "int64")
        let msgSchema = OpenApiSchema(`type`: OpenApiSchemaTypes.STRING)
        wrapped.properties.add("code", codeSchema)
        wrapped.properties.add("message", msgSchema)
        if (let Some(ds) <- dataSchema) {
            wrapped.properties.add("data", ds)
        }
        return wrapped
    }

    // api ignore
    private func isIgnore(endpoint: RouteEndpoint) {
        if (let Some(metadata) <- endpoint.metadata.getLastMetadata<SimApiDoc> {f => f.ignore}) {
            return true
        }
        return false
    }
}
