package swagger

import (
	"github.com/getkin/kin-openapi/openapi3"
	"github.com/go-openapi/spec"
)

// convertSwagger2ToOpenAPI3 converts a Swagger 2.0 spec to OpenAPI 3.0
func convertSwagger2ToOpenAPI3(swagger2 *spec.Swagger) *openapi3.T {
	openapi3Doc := &openapi3.T{
		OpenAPI: "3.0.3",
		Info: &openapi3.Info{
			Title:          swagger2.Info.Title,
			Description:    swagger2.Info.Description,
			TermsOfService: swagger2.Info.TermsOfService,
			Version:        swagger2.Info.Version,
		},
		Servers: openapi3.Servers{},
		Paths:   &openapi3.Paths{},
	}

	// Convert extensions
	if swagger2.Info.VendorExtensible.Extensions != nil {
		openapi3Doc.Extensions = make(map[string]interface{})
		for k, v := range swagger2.Info.VendorExtensible.Extensions {
			openapi3Doc.Extensions[k] = v
		}
	}

	// Add extensions from swagger root
	if swagger2.Extensions != nil {
		if openapi3Doc.Extensions == nil {
			openapi3Doc.Extensions = make(map[string]interface{})
		}
		for k, v := range swagger2.Extensions {
			openapi3Doc.Extensions[k] = v
		}
	}

	// Convert Contact
	if swagger2.Info.Contact != nil {
		openapi3Doc.Info.Contact = &openapi3.Contact{
			Name:  swagger2.Info.Contact.Name,
			URL:   swagger2.Info.Contact.URL,
			Email: swagger2.Info.Contact.Email,
		}
	}

	// Convert License
	if swagger2.Info.License != nil {
		openapi3Doc.Info.License = &openapi3.License{
			Name: swagger2.Info.License.Name,
			URL:  swagger2.Info.License.URL,
		}
	}

	// Convert Servers from host, basePath, and schemes
	if len(swagger2.Host) > 0 || len(swagger2.BasePath) > 0 {
		schemes := swagger2.Schemes
		if len(schemes) == 0 {
			schemes = []string{"https"}
		}
		for _, scheme := range schemes {
			serverURL := scheme + "://"
			if len(swagger2.Host) > 0 {
				serverURL += swagger2.Host
			} else {
				serverURL += "localhost"
			}
			if len(swagger2.BasePath) > 0 && swagger2.BasePath != "/" {
				serverURL += swagger2.BasePath
			}
			openapi3Doc.Servers = append(openapi3Doc.Servers, &openapi3.Server{
				URL: serverURL,
			})
		}
	}

	// Convert Paths
	openapi3Doc.Paths = openapi3.NewPaths()
	for path, pathItem := range swagger2.Paths.Paths {
		newPathItem := &openapi3.PathItem{}

		if pathItem.Get != nil {
			newPathItem.Get = convertOperation(pathItem.Get)
		}
		if pathItem.Post != nil {
			newPathItem.Post = convertOperation(pathItem.Post)
		}
		if pathItem.Put != nil {
			newPathItem.Put = convertOperation(pathItem.Put)
		}
		if pathItem.Delete != nil {
			newPathItem.Delete = convertOperation(pathItem.Delete)
		}
		if pathItem.Patch != nil {
			newPathItem.Patch = convertOperation(pathItem.Patch)
		}
		if pathItem.Head != nil {
			newPathItem.Head = convertOperation(pathItem.Head)
		}
		if pathItem.Options != nil {
			newPathItem.Options = convertOperation(pathItem.Options)
		}

		openapi3Doc.Paths.Set(path, newPathItem)
	}

	// Convert Definitions to Components/Schemas
	if len(swagger2.Definitions) > 0 {
		openapi3Doc.Components = &openapi3.Components{
			Schemas: make(openapi3.Schemas),
		}
		for name, schema := range swagger2.Definitions {
			openapi3Doc.Components.Schemas[name] = convertSchemaToSchemaRef(&schema)
		}
	}

	// Convert SecurityDefinitions to Components/SecuritySchemes
	if len(swagger2.SecurityDefinitions) > 0 {
		if openapi3Doc.Components == nil {
			openapi3Doc.Components = &openapi3.Components{}
		}
		openapi3Doc.Components.SecuritySchemes = make(openapi3.SecuritySchemes)
		for name, secDef := range swagger2.SecurityDefinitions {
			openapi3Doc.Components.SecuritySchemes[name] = convertSecurityScheme(secDef)
		}
	}

	return openapi3Doc
}

// convertOperation converts a Swagger 2.0 operation to OpenAPI 3.0
func convertOperation(op *spec.Operation) *openapi3.Operation {
	newOp := &openapi3.Operation{
		Tags:        op.Tags,
		Summary:     op.Summary,
		Description: op.Description,
		OperationID: op.ID,
		Parameters:  openapi3.Parameters{},
		Responses:   &openapi3.Responses{},
		Deprecated:  op.Deprecated,
	}

	// Convert ExternalDocs
	if op.ExternalDocs != nil {
		newOp.ExternalDocs = &openapi3.ExternalDocs{
			Description: op.ExternalDocs.Description,
			URL:         op.ExternalDocs.URL,
		}
	}

	// Convert Parameters and RequestBody
	var bodyParam *spec.Parameter
	for _, param := range op.Parameters {
		if param.In == "body" {
			bodyParam = &param
		} else {
			newOp.Parameters = append(newOp.Parameters, convertParameter(&param))
		}
	}

	// Convert body parameter to requestBody
	if bodyParam != nil {
		newOp.RequestBody = &openapi3.RequestBodyRef{
			Value: convertBodyParameter(bodyParam, op.Consumes),
		}
	}

	// Convert Responses
	for code, response := range op.Responses.StatusCodeResponses {
		newOp.Responses.Set(intToString(code), convertResponse(&response, op.Produces))
	}

	// Convert Security
	if len(op.Security) > 0 {
		newOp.Security = &openapi3.SecurityRequirements{}
		for _, sec := range op.Security {
			secReq := openapi3.SecurityRequirement{}
			for key, scopes := range sec {
				secReq[key] = scopes
			}
			*newOp.Security = append(*newOp.Security, secReq)
		}
	}

	return newOp
}

// convertParameter converts a Swagger 2.0 parameter to OpenAPI 3.0
func convertParameter(param *spec.Parameter) *openapi3.ParameterRef {
	schema := &openapi3.Schema{
		Format:  param.Format,
		Default: param.Default,
	}
	if param.Type != "" {
		schema.Type = &openapi3.Types{param.Type}
	}

	newParam := &openapi3.Parameter{
		Name:        param.Name,
		In:          param.In,
		Description: param.Description,
		Required:    param.Required,
		Schema:      &openapi3.SchemaRef{Value: schema},
	}

	// Convert validation properties
	if param.Maximum != nil {
		max := *param.Maximum
		newParam.Schema.Value.Max = &max
	}
	if param.Minimum != nil {
		min := *param.Minimum
		newParam.Schema.Value.Min = &min
	}
	if param.ExclusiveMaximum {
		newParam.Schema.Value.ExclusiveMax = true
	}
	if param.ExclusiveMinimum {
		newParam.Schema.Value.ExclusiveMin = true
	}
	if len(param.Enum) > 0 {
		newParam.Schema.Value.Enum = param.Enum
	}

	// Handle array items
	if param.Items != nil {
		newParam.Schema.Value.Items = convertItemsToSchemaRef(param.Items)
	}

	return &openapi3.ParameterRef{Value: newParam}
}

// convertBodyParameter converts a body parameter to RequestBody
func convertBodyParameter(param *spec.Parameter, consumes []string) *openapi3.RequestBody {
	if len(consumes) == 0 {
		consumes = []string{"application/json"}
	}

	content := make(openapi3.Content)
	for _, contentType := range consumes {
		mediaType := &openapi3.MediaType{}
		if param.Schema != nil {
			mediaType.Schema = convertSchemaToSchemaRef(param.Schema)
		}
		content[contentType] = mediaType
	}

	return &openapi3.RequestBody{
		Description: param.Description,
		Required:    param.Required,
		Content:     content,
	}
}

// convertResponse converts a Swagger 2.0 response to OpenAPI 3.0
func convertResponse(response *spec.Response, produces []string) *openapi3.ResponseRef {
	if len(produces) == 0 {
		produces = []string{"application/json"}
	}

	newResp := &openapi3.Response{
		Description: &response.Description,
	}

	if response.Schema != nil {
		newResp.Content = make(openapi3.Content)

		// 檢查是否有業務錯誤碼（x-biz-codes）
		bizCodes, hasBizCodes := response.Extensions["x-biz-codes"]

		if hasBizCodes {
			// 使用 oneOf 來表示多個可能的回應類型
			for _, contentType := range produces {
				mediaType := &openapi3.MediaType{
					Schema: createOneOfSchemaFromBizCodes(bizCodes),
				}
				newResp.Content[contentType] = mediaType
			}
		} else {
			// 普通回應
			for _, contentType := range produces {
				mediaType := &openapi3.MediaType{
					Schema: convertSchemaToSchemaRef(response.Schema),
				}
				newResp.Content[contentType] = mediaType
			}
		}
	}

	return &openapi3.ResponseRef{Value: newResp}
}

// createOneOfSchemaFromBizCodes 從業務錯誤碼創建 oneOf schema
func createOneOfSchemaFromBizCodes(bizCodesInterface interface{}) *openapi3.SchemaRef {
	bizCodes, ok := bizCodesInterface.(map[string]BizCode)
	if !ok {
		// 嘗試轉換為 map[string]interface{}
		bizCodesMap, ok := bizCodesInterface.(map[string]interface{})
		if !ok {
			return nil
		}

		// 轉換為 BizCode
		bizCodes = make(map[string]BizCode)
		for code, val := range bizCodesMap {
			if valMap, ok := val.(map[string]interface{}); ok {
				bizCode := BizCode{
					Code: code,
				}
				if schema, ok := valMap["Schema"].(string); ok {
					bizCode.Schema = schema
				}
				if desc, ok := valMap["Description"].(string); ok {
					bizCode.Description = desc
				}
				bizCodes[code] = bizCode
			}
		}
	}

	if len(bizCodes) == 0 {
		return nil
	}

	// 創建 oneOf schema
	oneOfSchemas := make([]*openapi3.SchemaRef, 0, len(bizCodes))
	for code, bizCode := range bizCodes {
		// 創建每個業務錯誤碼的 schema 引用
		ref := "#/components/schemas/" + bizCode.Schema
		schemaRef := &openapi3.SchemaRef{
			Ref: ref,
		}

		// 添加描述作為擴展
		if bizCode.Description != "" {
			schemaRef.Value = &openapi3.Schema{
				Description: "業務錯誤碼 " + code + ": " + bizCode.Description,
			}
		}

		oneOfSchemas = append(oneOfSchemas, schemaRef)
	}

	// 返回包含 oneOf 的 schema
	return &openapi3.SchemaRef{
		Value: &openapi3.Schema{
			OneOf: oneOfSchemas,
		},
	}
}

// convertSchemaToSchemaRef converts a Swagger 2.0 schema to OpenAPI 3.0 SchemaRef
func convertSchemaToSchemaRef(schema *spec.Schema) *openapi3.SchemaRef {
	if schema == nil {
		return nil
	}

	// Handle $ref
	if schema.Ref.String() != "" {
		ref := schema.Ref.String()
		// Convert #/definitions/ to #/components/schemas/
		if len(ref) > 14 && ref[:14] == "#/definitions/" {
			ref = "#/components/schemas/" + ref[14:]
		}
		return &openapi3.SchemaRef{Ref: ref}
	}

	newSchema := &openapi3.Schema{
		Format:      schema.Format,
		Description: schema.Description,
		Default:     schema.Default,
		Example:     schema.Example,
		Required:    schema.Required,
	}

	// Convert Type from StringOrArray to *Types
	if len(schema.Type) > 0 {
		types := openapi3.Types(schema.Type)
		newSchema.Type = &types
	}

	// Convert validation properties
	if schema.Maximum != nil {
		max := *schema.Maximum
		newSchema.Max = &max
	}
	if schema.Minimum != nil {
		min := *schema.Minimum
		newSchema.Min = &min
	}
	if schema.ExclusiveMaximum {
		newSchema.ExclusiveMax = true
	}
	if schema.ExclusiveMinimum {
		newSchema.ExclusiveMin = true
	}
	if len(schema.Enum) > 0 {
		newSchema.Enum = schema.Enum
	}

	// Convert properties
	if len(schema.Properties) > 0 {
		newSchema.Properties = make(openapi3.Schemas)
		for name, prop := range schema.Properties {
			newSchema.Properties[name] = convertSchemaToSchemaRef(&prop)
		}
	}

	// Convert items
	if schema.Items != nil && schema.Items.Schema != nil {
		newSchema.Items = convertSchemaToSchemaRef(schema.Items.Schema)
	}

	// Convert additionalProperties
	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
		newSchema.AdditionalProperties = openapi3.AdditionalProperties{
			Schema: convertSchemaToSchemaRef(schema.AdditionalProperties.Schema),
		}
	}

	return &openapi3.SchemaRef{Value: newSchema}
}

// convertItemsToSchemaRef converts Swagger 2.0 items to OpenAPI 3.0 SchemaRef
func convertItemsToSchemaRef(items *spec.Items) *openapi3.SchemaRef {
	if items == nil {
		return nil
	}

	schema := &openapi3.Schema{
		Format: items.Format,
	}

	if items.Type != "" {
		schema.Type = &openapi3.Types{items.Type}
	}

	if items.Items != nil {
		schema.Items = convertItemsToSchemaRef(items.Items)
	}

	return &openapi3.SchemaRef{Value: schema}
}

// convertSecurityScheme converts a Swagger 2.0 security definition to OpenAPI 3.0
func convertSecurityScheme(secDef *spec.SecurityScheme) *openapi3.SecuritySchemeRef {
	newScheme := &openapi3.SecurityScheme{
		Type:        secDef.Type,
		Description: secDef.Description,
		Name:        secDef.Name,
	}

	// Convert In to openapi3 format
	switch secDef.In {
	case "header":
		newScheme.In = "header"
	case "query":
		newScheme.In = "query"
	case "cookie":
		newScheme.In = "cookie"
	}

	// Handle OAuth2
	if secDef.Type == "oauth2" {
		newScheme.Flows = &openapi3.OAuthFlows{}

		switch secDef.Flow {
		case "implicit":
			newScheme.Flows.Implicit = &openapi3.OAuthFlow{
				AuthorizationURL: secDef.AuthorizationURL,
				Scopes:           secDef.Scopes,
			}
		case "password":
			newScheme.Flows.Password = &openapi3.OAuthFlow{
				TokenURL: secDef.TokenURL,
				Scopes:   secDef.Scopes,
			}
		case "application":
			newScheme.Flows.ClientCredentials = &openapi3.OAuthFlow{
				TokenURL: secDef.TokenURL,
				Scopes:   secDef.Scopes,
			}
		case "accessCode":
			newScheme.Flows.AuthorizationCode = &openapi3.OAuthFlow{
				AuthorizationURL: secDef.AuthorizationURL,
				TokenURL:         secDef.TokenURL,
				Scopes:           secDef.Scopes,
			}
		}
	}

	return &openapi3.SecuritySchemeRef{Value: newScheme}
}

// intToString converts int to string for response codes
func intToString(code int) string {
	if code < 0 || code > 999 {
		return "200" // fallback to 200
	}
	hundreds := code / 100
	tens := (code / 10) % 10
	ones := code % 10
	return string(rune('0'+hundreds)) + string(rune('0'+tens)) + string(rune('0'+ones))
}
