//go:generate go run main.go --dir="../../spec/tidb_cloud_open_api"

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

const (

	// swgger json file: {SwaggerDir}/{service_name}/{version}/services.swagger.json
	// ui swgger json file: {SwaggerDir}/{service_name}/{version}/services.ui.swagger.json
	defaultSwaggerDir = "./service.swagger.json"
)

// cd central/docs/gwopenapi   && go run main.go
func main() {
	swaggerPath := defaultSwaggerDir
	swagger, err := ParseSwaggerFromFile(swaggerPath)
	if err != nil {
		panic(err)
	}

	uiSwagger := GenerateUISwagger("", swagger)
	uis, _ := json.MarshalIndent(uiSwagger, "", "  ")
	err = os.WriteFile("service.ui.swagger.json", uis, 0o600)
	if err != nil {
		panic(err)
	}

	openapiSwagger := GenerateTcpnOpenApiSwagger("", swagger)
	apis, _ := json.MarshalIndent(openapiSwagger, "", "  ")
	err = os.WriteFile("service.openapi.swagger.json", apis, 0o600)
	if err != nil {
		panic(err)
	}

}

func GenerateTcpnOpenApiSwagger(svcName string, swagger *SwaggerGenerate) *SwaggerGenerate {
	var newTags []Tags
	for _, temTag := range swagger.Tags {
		if temTag.Name == "FileUploadService" {
			continue
		}
		newTags = append(newTags, temTag)
	}
	swagger.Tags = newTags
	swagger.Tags = nil

	newPaths := map[string]map[string]*Method{}
	for path, method2EntMap := range swagger.Paths {
		if path == "/v1beta1/partners/orgs" ||
			path == "/v1beta1/users/me" ||
			path == "/v1beta1/projects/{projectId}/users:invite" ||
			path == "/v1beta1/partners/logo:upload" {
			continue
		}
		for _, ent := range method2EntMap {
			ent.CodeSamples = []CodeSample{
				{
					Lang:   "curl",
					Source: operationId2CurlMap[ent.OperationID],
				},
			}
		}
		newPaths[path] = method2EntMap
	}
	swagger.Paths = newPaths

	return swagger
}

var operationId2CurlMap = map[string]string{
	"ApiKeyService_ListApiKeys":             "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request GET \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/apiKeys'",
	"ApiKeyService_CreateApiKey":            "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request POST \\\n  -d '{\"displayName\": \"New Key\", \"role\": \"api:org:owner\"}' \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/apiKeys'",
	"ApiKeyService_DeleteApiKey":            "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request DELETE \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/apiKeys/{accessKey}'",
	"ApiKeyService_GetApiKey":               "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request GET \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/apiKeys/{accessKey}'",
	"OrgService_GetOrg":                     "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request GET \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/org'",
	"PartnerService_ListCustomers":          "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request GET \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/partners/customers'",
	"PartnerService_CreateSignupUrl":        "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request POST \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/partners/msp/signupUrl'",
	"PartnerService_GetPartnerProfile":      "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request GET \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/partners/partnerProfile'",
	"PartnerService_UpdatePartnerProfile":   "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request PATCH \\\n  -d '{\"logo\": \"my_logo.svg\", \"advertising\": [{\"index\": \"index\", \"heading\": \"heading\", \"body\": \"body\"}]}' \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/partners/partnerProfile'",
	"UserService_DeleteProjectUser":         "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request DELETE \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/projects/{projectId}/users/{userId}'",
	"UserService_DeleteOrgUser":             "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request DELETE \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/users/{userId}'",
	"UserService_ListUsers":                 "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request GET \\\n  --url 'https://tcpn.tidbapi.com/v1beta1/users'",
	"InvitationService_CreateOrgInvitation": "curl --digest \\\n  --user 'YOUR_PUBLIC_KEY:YOUR_PRIVATE_KEY' \\\n  --request POST \\\n  -d '{\"inviteesEmails\": [\"my@test.com\"], \"role\": \"partner:admin\"}' \\\n  --url 'https://tcpn.tidbapi.com//v1beta1/users:invite'",
}

func GenerateUISwagger(svcName string, swagger *SwaggerGenerate) *SwaggerGenerate {
	// edit swagger
	paths := make(Paths, len(swagger.Paths))
	for path, methods := range swagger.Paths {
		newMethods := make(map[string]*Method, len(methods))
		for method, m := range methods {
			fmt.Printf("generate %s ui swagger. api_version: %s. path: %s. method: %s\n", svcName, swagger.Info.Version, path, method)
			parameters := make([]*Parameters, 0, len(m.Parameters))
			hasOrgID := false

			if m.Parameters != nil {
				for _, p := range m.Parameters {
					if p.Name == "orgId" && p.IN == "query" {
						if p.Required {
							hasOrgID = true
						}
						continue
					}
					// for update cluster
					if p.Name == "cluster.clusterId" {
						p.Name = "clusterId"
					}
					parameters = append(parameters, p)
				}
			}

			if !hasOrgID {
				parameters = append(parameters, &Parameters{
					Name:        "accessOrgId",
					Description: "Organization id",
					IN:          "query",
					Required:    true,
					Type:        "string",
					Format:      "uint64",
				})
			}
			m.Parameters = parameters
			newMethods[method] = m
		}
		// replace cluster.clusterId to clusterId
		path = strings.ReplaceAll(path, "cluster.clusterId", "clusterId")

		if _, ok := paths[filepath.Join(swagger.BasePath, path)]; ok {
			for method, m := range newMethods {
				paths[filepath.Join(swagger.BasePath, path)][method] = m
			}
		} else {
			paths[filepath.Join(swagger.BasePath, path)] = newMethods
		}
	}

	swagger.Paths = paths
	return swagger
}

// parse swagger json from a file path
func ParseSwaggerFromFile(path string) (*SwaggerGenerate, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to open: %w", err)
	}
	defer func() {
		_ = file.Close()
	}()
	content, err := io.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to read: %w", err)
	}
	return ParseConfig(content)
}

// parse swagger json from bytes
func ParseConfig(content []byte) (*SwaggerGenerate, error) {
	var swagger SwaggerGenerate

	err := json.Unmarshal(content, &swagger)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal: %w", err)
	}

	return &swagger, nil
}

type SwaggerGenerate struct {
	Swagger     string                        `json:"swagger,omitempty"`
	Info        Info                          `json:"info,omitempty"`
	Tags        []Tags                        `json:"tags,omitempty"`
	Host        string                        `json:"host,omitempty"`
	BasePath    string                        `json:"basePath,omitempty"`
	Schemes     []string                      `json:"schemes,omitempty"`
	Consumes    []string                      `json:"consumes,omitempty"`
	Produces    []string                      `json:"produces,omitempty"`
	Paths       map[string]map[string]*Method `json:"paths,omitempty"`
	Definitions Definitions                   `json:"definitions,omitempty"`
}
type Info struct {
	Title       string `json:"title,omitempty"`
	Description string `json:"description,omitempty"`
	Version     string `json:"version,omitempty"`
}
type Tags struct {
	Name string `json:"name,omitempty"`
}
type Schema struct {
	Ref         string     `json:"$ref,omitempty"`
	Type        string     `json:"type,omitempty"`
	Properties  Properties `json:"properties,omitempty"`
	Description string     `json:"description,omitempty"`
}

type HTTPStatusOK struct {
	Description string `json:"description,omitempty"`
	Schema      Schema `json:"schema,omitempty"`
}

type Default struct {
	Description string `json:"description,omitempty"`
	Schema      Schema `json:"schema,omitempty"`
}

type Responses struct {
	HTTPStatusOK  HTTPStatusOK `json:"200,omitempty"`
	HTTPStatus400 HTTPStatusOK `json:"400,omitempty"`
	HTTPStatus401 HTTPStatusOK `json:"401,omitempty"`
	HTTPStatus403 HTTPStatusOK `json:"403,omitempty"`
	HTTPStatus404 HTTPStatusOK `json:"404,omitempty"`
	HTTPStatus429 HTTPStatusOK `json:"429,omitempty"`
	HTTPStatus500 HTTPStatusOK `json:"500,omitempty"`
	Default       Default      `json:"default,omitempty"`
}

type Paths map[string]map[string]*Method

type Parameters struct {
	Name        string   `json:"name,omitempty"`
	Description string   `json:"description,omitempty"`
	IN          string   `json:"in,omitempty"`
	Required    bool     `json:"required"`
	Type        string   `json:"type,omitempty"`
	Format      string   `json:"format,omitempty"`
	Enmu        []string `json:"enum,omitempty"`
	Default     string   `json:"default,omitempty"`
	Schema      *Schema  `json:"schema,omitempty"`
}

type Properties map[string]any

type Method struct {
	Summary     string        `json:"summary,omitempty"`
	OperationID string        `json:"operationId,omitempty"`
	Responses   Responses     `json:"responses,omitempty"`
	Parameters  []*Parameters `json:"parameters,omitempty"`
	Tags        []string      `json:"tags,omitempty"`
	CodeSamples []CodeSample  `json:"x-code-samples,omitempty"`
}

type CodeSample struct {
	Lang   string `json:"lang,omitempty"`
	Source string `json:"source,omitempty"`
}

type Definitions map[string]any
