package swagger_api

import (
	"context"
	"fmt"

	"git.ikuban.com/server/swagger-api/v2/generator"

	"github.com/go-kratos/kratos/v2/api/metadata"
	"github.com/xmkuban/utils/utils"
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/types/descriptorpb"
	"google.golang.org/protobuf/types/pluginpb"
)

// Service is service
type Service struct {
	ser *Server
}

// New service
func New(skipError bool) *Service {
	return &Service{
		ser: NewServer(nil, skipError),
	}
}

// ListServices list services
func (s *Service) ListServices(ctx context.Context, in *metadata.ListServicesRequest) (*metadata.ListServicesReply, error) {
	return s.ser.ListServices(ctx, &metadata.ListServicesRequest{})
}

// GetServiceOpenAPI get service open api
func (s *Service) GetServiceOpenAPI(ctx context.Context, in *metadata.GetServiceDescRequest) (string, error) {
	protoSet, err := s.ser.GetServiceDesc(ctx, in)
	if err != nil {
		return "", err
	}
	files := protoSet.FileDescSet.File
	newFiles := make([]*descriptorpb.FileDescriptorProto, 0)
	newFileMap := make(map[string]byte)
	for _, v := range files {
		if _, ok := newFileMap[v.GetName()]; !ok {
			newFiles = append(newFiles, v)
			newFileMap[v.GetName()] = 1
		}
	}
	files = newFiles
	var target string
	if len(files) == 0 {
		return "", fmt.Errorf("proto file is empty")
	}
	if files[len(files)-1].Name == nil {
		return "", fmt.Errorf("proto file's name is null")
	}
	target = files[len(files)-1].GetName()
	req := new(pluginpb.CodeGeneratorRequest)
	req.FileToGenerate = []string{target}
	var para = ""
	req.Parameter = &para
	req.ProtoFile = files

	opts := protogen.Options{}
	plugin, err := opts.New(req)
	if err != nil {
		return "", err
	}
	plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)

	var content []byte
	gen := generator.NewOpenAPIv3Generator(plugin, generator.Configuration{
		Version:         utils.ToPointString("1.0"),
		Title:           utils.ToPointString(""),
		Description:     utils.ToPointString(""),
		Naming:          utils.ToPointString("proto"),
		FQSchemaNaming:  utils.ToPointBool(true),
		EnumType:        utils.ToPointString("integer"),
		CircularDepth:   utils.ToPointInt(2),
		DefaultResponse: utils.ToPointBool(false),
		OutputMode:      utils.ToPointString("merged"),
	}, plugin.Files)
	content, err = gen.RunV2()

	if err != nil {
		return "", err
	}
	return string(content), nil
}

// GetAllServicesOpenAPI get all services merged into one openapi document
// servicesList: specific services to include, pass nil or empty slice to include all services
// title: the title for the merged API document
// description: the description for the merged API document
func (s *Service) GetAllServicesOpenAPI(ctx context.Context, servicesList []string, title, description string) (string, error) {
	// Determine which services to process
	var servicesToProcess []string

	if len(servicesList) > 0 {
		// Use the specified services list
		servicesToProcess = servicesList
	} else {
		// Get all services if no list specified
		allServices, err := s.ser.ListServices(ctx, &metadata.ListServicesRequest{})
		if err != nil {
			return "", err
		}
		servicesToProcess = allServices.Services
	}

	if len(servicesToProcess) == 0 {
		return "", fmt.Errorf("no services found")
	}

	// Collect all proto files from specified services
	allFiles := make([]*descriptorpb.FileDescriptorProto, 0)
	fileMap := make(map[string]byte)
	filesToGenerate := make([]string, 0)

	for _, serviceName := range servicesToProcess {
		protoSet, err := s.ser.GetServiceDesc(ctx, &metadata.GetServiceDescRequest{Name: serviceName})
		if err != nil {
			// Skip services that fail to load
			continue
		}

		for _, file := range protoSet.FileDescSet.File {
			if _, ok := fileMap[file.GetName()]; !ok {
				allFiles = append(allFiles, file)
				fileMap[file.GetName()] = 1
				// Add files that contain services to the generation list
				if len(file.Service) > 0 {
					filesToGenerate = append(filesToGenerate, file.GetName())
				}
			}
		}
	}

	if len(allFiles) == 0 {
		return "", fmt.Errorf("no proto files found")
	}

	if len(filesToGenerate) == 0 {
		return "", fmt.Errorf("no service files found")
	}

	// Create code generator request
	req := new(pluginpb.CodeGeneratorRequest)
	req.FileToGenerate = filesToGenerate
	var para = ""
	req.Parameter = &para
	req.ProtoFile = allFiles

	opts := protogen.Options{}
	plugin, err := opts.New(req)
	if err != nil {
		return "", err
	}
	plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)

	// Generate merged OpenAPI document
	gen := generator.NewOpenAPIv3Generator(plugin, generator.Configuration{
		Version:         utils.ToPointString("1.0"),
		Title:           utils.ToPointString(title),
		Description:     utils.ToPointString(description),
		Naming:          utils.ToPointString("proto"),
		FQSchemaNaming:  utils.ToPointBool(true),
		EnumType:        utils.ToPointString("integer"),
		CircularDepth:   utils.ToPointInt(2),
		DefaultResponse: utils.ToPointBool(false),
		OutputMode:      utils.ToPointString("merged"),
		PreserveInfo:    utils.ToPointBool(true),
	}, plugin.Files)
	content, err := gen.RunV2()

	if err != nil {
		return "", err
	}
	return string(content), nil
}
