package service

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/APIParkLab/APIPark/common"

	"github.com/mitchellh/mapstructure"

	"github.com/eolinker/go-common/register"

	"github.com/mark3labs/mcp-go/mcp"

	mcp_server "github.com/APIParkLab/APIPark/mcp-server"

	"github.com/APIParkLab/APIPark/service/release"

	"github.com/APIParkLab/APIPark/gateway"

	ai_local "github.com/APIParkLab/APIPark/service/ai-local"
	"github.com/APIParkLab/APIPark/service/cluster"

	model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime"

	"github.com/APIParkLab/APIPark/resources/access"
	log_service "github.com/APIParkLab/APIPark/service/log"
	"github.com/eolinker/eosc/log"
	"github.com/eolinker/go-common/server"

	"github.com/eolinker/ap-account/service/role"

	application_authorization "github.com/APIParkLab/APIPark/service/application-authorization"
	service_model_mapping "github.com/APIParkLab/APIPark/service/service-model-mapping"

	api_doc "github.com/APIParkLab/APIPark/service/api-doc"

	service_tag "github.com/APIParkLab/APIPark/service/service-tag"

	service_doc "github.com/APIParkLab/APIPark/service/service-doc"

	serviceDto "github.com/APIParkLab/APIPark/module/service/dto"

	"github.com/APIParkLab/APIPark/service/tag"

	"github.com/APIParkLab/APIPark/service/service"

	"github.com/APIParkLab/APIPark/service/subscribe"
	"gorm.io/gorm"

	"github.com/APIParkLab/APIPark/service/api"

	"github.com/eolinker/go-common/auto"

	team_member "github.com/APIParkLab/APIPark/service/team-member"

	"github.com/eolinker/go-common/store"

	"github.com/google/uuid"

	"github.com/eolinker/go-common/utils"

	"github.com/APIParkLab/APIPark/service/team"

	service_dto "github.com/APIParkLab/APIPark/module/service/dto"
)

var (
	_ IServiceModule       = (*imlServiceModule)(nil)
	_ IExportServiceModule = (*imlServiceModule)(nil)
)

type imlServiceModule struct {
	serviceService    service.IServiceService        `autowired:""`
	teamService       team.ITeamService              `autowired:""`
	teamMemberService team_member.ITeamMemberService `autowired:""`
	tagService        tag.ITagService                `autowired:""`
	localModelService ai_local.ILocalModelService    `autowired:""`

	serviceTagService service_tag.ITagService     `autowired:""`
	apiService        api.IAPIService             `autowired:""`
	apiDocService     api_doc.IAPIDocService      `autowired:""`
	clusterService    cluster.IClusterService     `autowired:""`
	subscribeServer   subscribe.ISubscribeService `autowired:""`

	releaseService             release.IReleaseService                           `autowired:""`
	serviceModelMappingService service_model_mapping.IServiceModelMappingService `autowired:""`
	logService                 log_service.ILogService                           `autowired:""`

	transaction store.ITransaction `autowired:""`
}

func formatHeader(header string) string {
	result, err := url.QueryUnescape(header)
	if err != nil {
		return header
	}
	result = strings.ReplaceAll(result, "&", "\n")
	result = strings.ReplaceAll(result, "=", ": ")
	return result
}

func (i *imlServiceModule) RestLogInfo(ctx context.Context, serviceId string, logId string) (*service_dto.RestLogInfo, error) {
	c, err := i.clusterService.Get(ctx, cluster.DefaultClusterID)
	if err != nil {
		return nil, fmt.Errorf("cluster %s not found", cluster.DefaultClusterID)
	}
	info, err := i.logService.LogInfo(ctx, "loki", c.Cluster, logId)
	if err != nil {
		return nil, err
	}
	if info.Service != serviceId {
		return nil, errors.New("service not match")
	}

	logInfo := &service_dto.RestLogInfo{
		Id:           info.ID,
		API:          auto.UUID(info.API),
		Consumer:     auto.UUID(info.Consumer),
		Status:       info.StatusCode,
		Ip:           info.RemoteIP,
		ResponseTime: common.FormatTime(info.ResponseTime),
		Traffic:      common.FormatByte(info.Traffic),
		LogTime:      auto.TimeLabel(info.RecordTime),
		Request: service_dto.OriginRequest{
			Header: formatHeader(info.RequestHeader),
			Origin: info.RequestBody,
			Body:   info.RequestBody,
		},
		Response: service_dto.OriginRequest{
			Header: formatHeader(info.ResponseHeader),
			Origin: info.ResponseBody,
			Body:   info.ResponseBody,
		},
	}

	if info.Consumer == "apipark-global" {
		logInfo.IsSystemConsumer = true
		logInfo.Consumer = auto.Label{
			Id:   info.Consumer,
			Name: "System Consumer",
		}
	}
	return logInfo, nil
}

func (i *imlServiceModule) AILogInfo(ctx context.Context, serviceId string, logId string) (*service_dto.AILogInfo, error) {
	c, err := i.clusterService.Get(ctx, cluster.DefaultClusterID)
	if err != nil {
		return nil, fmt.Errorf("cluster %s not found", cluster.DefaultClusterID)
	}
	info, err := i.logService.LogInfo(ctx, "loki", c.Cluster, logId)
	if err != nil {
		return nil, err
	}
	if info.Service != serviceId {
		return nil, errors.New("service not match")
	}
	response, err := parseAIResponse(info.ResponseBody)
	if err != nil {
		response = info.ResponseBody
	}

	logInfo := &service_dto.AILogInfo{
		Id:       info.ID,
		API:      auto.UUID(info.API),
		Consumer: auto.UUID(info.Consumer),
		Status:   info.StatusCode,
		Ip:       info.RemoteIP,
		Provider: auto.UUID(info.AIProvider),
		Model:    info.AIModel,
		LogTime:  auto.TimeLabel(info.RecordTime),
		Request: service_dto.OriginAIRequest{
			OriginRequest: service_dto.OriginRequest{
				Header: formatHeader(info.RequestHeader),
				Origin: info.RequestBody,
				Body:   parseAIRequest(info.RequestBody),
			},

			Token: info.InputToken,
		},
		Response: service_dto.OriginAIRequest{
			OriginRequest: service_dto.OriginRequest{
				Header: formatHeader(info.ResponseHeader),
				Origin: info.ResponseBody,
				Body:   response,
			},

			Token: info.OutputToken,
		},
	}
	if info.Consumer == "apipark-global" {
		logInfo.IsSystemConsumer = true
		logInfo.Consumer = auto.Label{
			Id:   info.Consumer,
			Name: "System Consumer",
		}
	}
	return logInfo, nil
}

func (i *imlServiceModule) RestLogs(ctx context.Context, serviceId string, start int64, end int64, page int, size int) ([]*service_dto.RestLogItem, int64, error) {
	list, total, err := i.logService.LogRecordsByService(ctx, serviceId, time.Unix(start, 0), time.Unix(end, 0), page, size)
	if err != nil {
		return nil, 0, err
	}
	return utils.SliceToSlice(list, func(s *log_service.Item) *service_dto.RestLogItem {
		item := &service_dto.RestLogItem{
			Id:           s.ID,
			API:          auto.UUID(s.API),
			Status:       s.StatusCode,
			LogTime:      auto.TimeLabel(s.RecordTime),
			Ip:           s.RemoteIP,
			Consumer:     auto.UUID(s.Consumer),
			ResponseTime: common.FormatTime(s.ResponseTime),
			Traffic:      common.FormatByte(s.Traffic),
		}
		if s.Consumer == "apipark-global" {
			item.Consumer = auto.Label{
				Id:   s.Consumer,
				Name: "System Consumer",
			}
		}
		return item
	}), total, nil
}

func (i *imlServiceModule) AILogs(ctx context.Context, serviceId string, start int64, end int64, page int, size int) ([]*service_dto.AILogItem, int64, error) {
	list, total, err := i.logService.LogRecordsByService(ctx, serviceId, time.Unix(start, 0), time.Unix(end, 0), page, size)
	if err != nil {
		return nil, 0, err
	}
	return utils.SliceToSlice(list, func(s *log_service.Item) *service_dto.AILogItem {
		item := &service_dto.AILogItem{
			Id:             s.ID,
			API:            auto.UUID(s.API),
			Status:         s.StatusCode,
			LogTime:        auto.TimeLabel(s.RecordTime),
			Ip:             s.RemoteIP,
			Token:          s.TotalToken,
			TokenPerSecond: s.TotalToken * 1000 / s.ResponseTime,
			Consumer:       auto.UUID(s.Consumer),
			Provider:       auto.UUID(s.AIProvider),
			Model:          s.AIModel,
		}
		if s.Consumer == "apipark-global" {
			item.Consumer = auto.Label{
				Id:   s.Consumer,
				Name: "System Consumer",
			}
		}
		return item
	}), total, nil
}

func (i *imlServiceModule) ServiceOverview(ctx context.Context, id string) (*service_dto.Overview, error) {
	info, err := i.serviceService.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	apiCountMap, err := i.apiDocService.APICountByServices(ctx, id)
	if err != nil {
		return nil, err
	}
	subscribeMap, err := i.subscribeServer.CountMapByService(ctx, subscribe.ApplyStatusSubscribe, id)
	if err != nil {
		return nil, err
	}
	result := &service_dto.Overview{
		Id:            info.Id,
		Name:          info.Name,
		Description:   info.Description,
		EnableMCP:     info.EnableMCP,
		ServiceKind:   info.Kind.String(),
		SubscriberNum: subscribeMap[id],
		Logo:          info.Logo,
		Catalogue:     auto.UUID(info.Catalogue),
		APINum:        apiCountMap[id],
	}
	_, err = i.releaseService.GetRunning(ctx, id)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, err
		}
	} else {
		result.IsReleased = true
	}

	return result, nil
}

func (i *imlServiceModule) OnInit() {
	register.Handle(func(v server.Server) {
		ctx := context.Background()
		services, err := i.serviceService.ServiceList(ctx)
		if err != nil {
			log.Error(err)
			return
		}
		for _, s := range services {
			err = i.updateMCPServer(ctx, s.Id, s.Name, "1.0")
			if err != nil {
				log.Error(err)
				return
			}
		}
	})
}

func (i *imlServiceModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
	services, err := i.serviceService.ServiceList(ctx)
	if err != nil {
		return err
	}
	subscribeReleases := make([]*gateway.SubscribeRelease, 0, len(services))
	hashReleases := make([]*gateway.HashRelease, 0, len(services))
	for _, s := range services {
		subscribeReleases = append(subscribeReleases, &gateway.SubscribeRelease{
			Service:     s.Id,
			Application: "apipark-global",
			Expired:     "0",
		})

		modelMap, err := i.serviceModelMappingService.Get(ctx, s.Id)
		if err != nil {
			return err
		}
		if modelMap.Content == "" {
			continue
		}
		m := make(map[string]string)
		err = json.Unmarshal([]byte(modelMap.Content), &m)
		if err != nil {
			return err
		}
		hashReleases = append(hashReleases, &gateway.HashRelease{
			HashKey: fmt.Sprintf("%s:%s", gateway.KeyServiceMapping, s.Id),
			HashMap: m,
		})

	}
	err = clientDriver.Subscribe().Online(ctx, subscribeReleases...)
	if err != nil {
		return err
	}
	return clientDriver.Hash().Online(ctx, hashReleases...)
}

func (i *imlServiceModule) updateMCPServer(ctx context.Context, sid string, name string, version string) error {
	r, err := i.releaseService.GetRunning(ctx, sid)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		return err
	}
	_, _, apiDocCommit, _, _, err := i.releaseService.GetReleaseInfos(ctx, r.UUID)
	if err != nil {
		return fmt.Errorf("get release info error: %w", err)
	}
	commitDoc, err := i.apiDocService.GetDocCommit(ctx, apiDocCommit.Commit)
	if err != nil {
		return fmt.Errorf("get api doc commit error: %w", err)
	}
	mcpInfo, err := mcp_server.ConvertMCPFromOpenAPI3Data([]byte(commitDoc.Data.Content))
	if err != nil {
		return fmt.Errorf("convert mcp from openapi3 data error: %w", err)
	}
	tools := make([]mcp_server.ITool, 0, len(mcpInfo.Apis))
	for _, a := range mcpInfo.Apis {
		toolOptions := make([]mcp.ToolOption, 0, len(a.Params)+2)
		toolOptions = append(toolOptions, mcp.WithDescription(a.Description))
		headers := make(map[string]interface{})
		queries := make(map[string]interface{})
		path := make(map[string]interface{})
		for _, v := range a.Params {
			p := map[string]interface{}{
				"type":        "string",
				"required":    v.Required,
				"description": v.Description,
			}
			switch v.In {
			case "header":
				headers[v.Name] = p
			case "query":
				queries[v.Name] = p
			case "path":
				path[v.Name] = p
			}
		}
		if len(headers) > 0 {
			toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPHeader, mcp.Properties(headers), mcp.Description("request headers.")))
		}
		if len(queries) > 0 {
			toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPQuery, mcp.Properties(queries), mcp.Description("request queries.")))
		}
		if len(path) > 0 {
			toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPPath, mcp.Properties(path), mcp.Description("request path params.")))
		}
		if a.Body != nil {
			type Schema struct {
				Type       string                 `mapstructure:"type"`
				Properties map[string]interface{} `mapstructure:"properties"`
				Items      interface{}            `mapstructure:"items"`
			}
			var tmp Schema
			err = mapstructure.Decode(a.Body, &tmp)
			if err != nil {
				return err
			}
			switch tmp.Type {
			case "object":
				toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPBody, mcp.Properties(tmp.Properties), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
			case "array":
				toolOptions = append(toolOptions, mcp.WithArray(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
			}
		}
		tools = append(tools, mcp_server.NewTool(a.Summary, a.Path, a.Method, a.ContentType, toolOptions...))
	}
	mcp_server.SetSSEServer(sid, name, version, tools...)
	return nil
}

func (i *imlServiceModule) deleteMCPServer(ctx context.Context, sid string) {
	mcp_server.DelSSEServer(sid)
}

func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.ExportService, error) {
	services, err := i.serviceService.ServiceList(ctx)
	if err != nil {
		return nil, err
	}
	serviceIds := utils.SliceToSlice(services, func(s *service.Service) string {
		return s.Id
	})
	serviceTags, err := i.serviceTagService.List(ctx, serviceIds, nil)
	if err != nil {
		return nil, err
	}
	tagMap, err := i.tagService.Map(ctx)
	if err != nil {
		return nil, err
	}
	serviceTagMap := make(map[string][]string)
	for _, st := range serviceTags {
		if _, ok := tagMap[st.Tid]; !ok {
			continue
		}
		if _, ok := serviceTagMap[st.Sid]; !ok {
			serviceTagMap[st.Sid] = make([]string, 0)
		}
		serviceTagMap[st.Sid] = append(serviceTagMap[st.Sid], tagMap[st.Tid].Name)
	}

	items := make([]*service_dto.ExportService, 0, len(services))
	for _, s := range services {
		info := &service_dto.ExportService{
			Id:          s.Id,
			Name:        s.Name,
			Prefix:      s.Prefix,
			Description: s.Description,
			Team:        s.Team,
			ServiceType: s.ServiceType.String(),
			Catalogue:   s.Catalogue,
			Logo:        s.Logo,
		}

		if tags, ok := serviceTagMap[s.Id]; ok {
			info.Tags = tags
		}
		items = append(items, info)
	}
	return items, nil
}

func (i *imlServiceModule) searchMyServices(ctx context.Context, teamId string, keyword string) ([]*service.Service, error) {
	userID := utils.UserId(ctx)
	condition := make(map[string]interface{})
	condition["as_server"] = true
	if teamId != "" {
		_, err := i.teamService.Get(ctx, teamId)
		if err != nil {
			return nil, err
		}
		condition["team"] = teamId
		return i.serviceService.Search(ctx, keyword, condition, "create_at desc")
	} else {
		membersForUser, err := i.teamMemberService.FilterMembersForUser(ctx, userID)
		if err != nil {
			return nil, err
		}
		teamIds := membersForUser[userID]
		condition["team"] = teamIds
		return i.serviceService.Search(ctx, keyword, condition, "create_at desc")
	}
}

func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string, keyword string) ([]*service_dto.ServiceItem, error) {
	services, err := i.searchMyServices(ctx, teamId, keyword)
	if err != nil {
		return nil, err
	}
	serviceIds := utils.SliceToSlice(services, func(p *service.Service) string {
		return p.Id
	})
	apiCountMap, err := i.apiDocService.APICountByServices(ctx, serviceIds...)
	if err != nil {
		return nil, err
	}

	items := make([]*service_dto.ServiceItem, 0, len(services))
	for _, model := range services {
		if teamId != "" && model.Team != teamId {
			continue
		}
		apiCount := apiCountMap[model.Id]
		item := toServiceItem(model)
		item.ApiNum = apiCount
		item.CanDelete = apiCount == 0
		items = append(items, item)

	}
	return items, nil
}

func (i *imlServiceModule) Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) {
	w := make(map[string]interface{})
	w["as_server"] = true

	services, err := i.serviceService.Search(ctx, "", w)
	if err != nil {
		return nil, err
	}

	items := make([]*service_dto.SimpleServiceItem, 0, len(services))
	for _, p := range services {
		items = append(items, &service_dto.SimpleServiceItem{
			Id:          p.Id,
			Name:        p.Name,
			Description: p.Description,
			Team:        auto.UUID(p.Team),
		})
	}
	return items, nil
}

func (i *imlServiceModule) MySimple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) {
	services, err := i.searchMyServices(ctx, "", "")
	if err != nil {
		return nil, err
	}

	items := make([]*service_dto.SimpleServiceItem, 0, len(services))
	for _, p := range services {
		items = append(items, &service_dto.SimpleServiceItem{
			Id:          p.Id,
			Name:        p.Name,
			Description: p.Description,
			Team:        auto.UUID(p.Team),
		})
	}
	return items, nil
}

func (i *imlServiceModule) Get(ctx context.Context, id string) (*service_dto.Service, error) {
	now := time.Now()
	serviceInfo, err := i.serviceService.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	tags, err := i.serviceTagService.List(ctx, []string{serviceInfo.Id}, nil)
	if err != nil {
		return nil, err
	}

	s := service_dto.ToService(serviceInfo)
	s.Tags = auto.List(utils.SliceToSlice(tags, func(p *service_tag.Tag) string {
		return p.Tid
	}))
	if s.Model == "" {
		switch s.ProviderType {
		case "online":
			p, has := model_runtime.GetProvider(s.Provider.Id)
			if has {
				m, has := p.DefaultModel(model_runtime.ModelTypeLLM)
				if has {
					s.Model = m.ID()
				}
			}
		case "local":
			info, err := i.localModelService.DefaultModel(ctx)
			if err != nil {
				return nil, err
			}
			s.Model = info.Id

		}
	}

	serviceModelMapping, err := i.serviceModelMappingService.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	s.ModelMapping = serviceModelMapping.Content

	log.Infof("get service cost %d ms", time.Since(now).Milliseconds())
	return s, nil
}

func (i *imlServiceModule) Search(ctx context.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) {
	var list []*service.Service
	var err error
	if teamID != "" {
		_, err = i.teamService.Get(ctx, teamID)
		if err != nil {
			return nil, err
		}
		list, err = i.serviceService.Search(ctx, keyword, map[string]interface{}{"team": teamID, "as_server": true}, "create_at desc")
	} else {
		list, err = i.serviceService.Search(ctx, keyword, map[string]interface{}{"as_server": true}, "create_at desc")
	}
	if err != nil {
		return nil, err
	}

	serviceIds := utils.SliceToSlice(list, func(s *service.Service) string {
		return s.Id
	})

	apiCountMap, err := i.apiDocService.APICountByServices(ctx, serviceIds...)
	if err != nil {
		return nil, err
	}

	items := make([]*service_dto.ServiceItem, 0, len(list))
	for _, model := range list {
		apiCount := apiCountMap[model.Id]
		item := toServiceItem(model)
		item.ApiNum = apiCount
		item.CanDelete = apiCount == 0
		items = append(items, item)
	}
	return items, nil
}

func toServiceItem(model *service.Service) *service_dto.ServiceItem {
	item := &service_dto.ServiceItem{
		Id:          model.Id,
		Name:        model.Name,
		Description: model.Description,
		CreateTime:  auto.TimeLabel(model.CreateTime),
		UpdateTime:  auto.TimeLabel(model.UpdateTime),
		Team:        auto.UUID(model.Team),
		EnableMCP:   model.EnableMCP,
		ServiceKind: model.Kind.String(),
	}
	state := service_dto.FromServiceState(model.State)
	if state == service_dto.ServiceStateNormal {
		item.State = model.ServiceType.String()
	} else {
		item.State = state.String()
	}

	switch model.Kind {
	case service.RestService:
		item.State = model.ServiceType.String()
		return item
	case service.AIService:
		provider := auto.UUID(model.AdditionalConfig["provider"])
		item.Provider = &provider
		return item
	default:
		return item
	}
}

func (i *imlServiceModule) Create(ctx context.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) {
	if input.Id == "" {
		input.Id = uuid.New().String()
	}
	if teamID == "" {
		item, err := i.teamService.DefaultTeam(ctx)
		if err != nil {
			return nil, err
		}
		teamID = item.Id
	}
	mo := &service.Create{
		Id:               input.Id,
		Name:             input.Name,
		Description:      input.Description,
		Team:             teamID,
		ServiceType:      service.ServiceType(input.ServiceType),
		Catalogue:        input.Catalogue,
		Prefix:           input.Prefix,
		Logo:             input.Logo,
		State:            service_dto.ServiceState(input.State).Int(),
		ApprovalType:     service.ApprovalType(input.ApprovalType),
		AdditionalConfig: make(map[string]string),
		Kind:             service.Kind(input.Kind),
		EnableMCP:        input.EnableMCP,
	}
	if mo.ServiceType == service.PublicService && mo.Catalogue == "" {
		return nil, fmt.Errorf("catalogue can not be empty")
	}
	switch mo.Kind {
	case service.AIService:
		if input.Provider == nil {
			return nil, fmt.Errorf("ai service: provider can not be empty")
		}
		mo.AdditionalConfig["provider"] = *input.Provider
		if input.Model == nil {
			return nil, fmt.Errorf("ai service: model can not be empty")
		}
		mo.AdditionalConfig["model"] = *input.Model
	}
	if input.AsApp == nil {
		// 默认值为false
		mo.AsApp = false
	} else {
		mo.AsApp = *input.AsApp
	}
	if input.AsServer == nil {
		// 默认值为true
		mo.AsServer = true
	} else {
		mo.AsServer = *input.AsServer
	}

	input.Prefix = strings.Trim(strings.Trim(input.Prefix, " "), "/")
	err := i.transaction.Transaction(ctx, func(ctx context.Context) error {
		if input.Tags != nil {
			tags, err := i.getTagUuids(ctx, input.Tags)
			if err != nil {
				return err
			}
			for _, t := range tags {
				err = i.serviceTagService.Create(ctx, &service_tag.CreateTag{
					Tid: t,
					Sid: input.Id,
				})
				if err != nil {
					return err
				}
			}
		}
		err := i.serviceService.Create(ctx, mo)
		if err != nil {
			return err
		}
		client, err := i.clusterService.GatewayClient(ctx, cluster.DefaultClusterID)
		if err != nil {
			return err
		}
		err = client.Subscribe().Online(ctx, &gateway.SubscribeRelease{
			Service:     mo.Id,
			Application: "apipark-global",
			Expired:     "0",
		})
		if err != nil {
			return err
		}
		if input.ModelMapping != "" {
			m := make(map[string]string)
			err = json.Unmarshal([]byte(input.ModelMapping), &m)
			if err != nil {
				return err
			}
			err = i.serviceModelMappingService.Save(ctx, &service_model_mapping.Save{
				Sid:     input.Id,
				Content: input.ModelMapping,
			})
			if err != nil {
				return err
			}

			err = client.Hash().Online(ctx, &gateway.HashRelease{
				HashKey: fmt.Sprintf("%s:%s", gateway.KeyServiceMapping, input.Id),
				HashMap: m,
			})
			if err != nil {
				return err
			}
		}

		if input.EnableMCP {
			err = i.updateMCPServer(ctx, input.Id, input.Name, "1.0")
			if err != nil {
				return err
			}
		} else {
			i.deleteMCPServer(ctx, input.Id)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return i.Get(ctx, input.Id)
}

func (i *imlServiceModule) Edit(ctx context.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
	info, err := i.serviceService.Get(ctx, id)
	if err != nil {
		return nil, err
	}

	switch info.Kind {
	case service.AIService:
		if input.Provider != nil {
			info.AdditionalConfig["provider"] = *input.Provider
		}
		if input.Model != nil {
			info.AdditionalConfig["model"] = *input.Model
		}
	}
	err = i.transaction.Transaction(ctx, func(ctx context.Context) error {
		serviceType := (*service.ServiceType)(input.ServiceType)
		if serviceType != nil && *serviceType == service.PublicService {
			if input.Catalogue == nil || *input.Catalogue == "" {
				return fmt.Errorf("catalogue can not be empty")
			}
		}
		var approvalType service.ApprovalType
		if input.ApprovalType != nil {
			approvalType = service.ApprovalType(*input.ApprovalType)
		}
		editCfg := &service.Edit{
			Name:             input.Name,
			Description:      input.Description,
			Logo:             input.Logo,
			ServiceType:      serviceType,
			Catalogue:        input.Catalogue,
			AdditionalConfig: &info.AdditionalConfig,
			ApprovalType:     &approvalType,
			EnableMCP:        input.EnableMCP,
		}
		if input.State != nil {
			state := service_dto.ServiceState(*input.State).Int()
			editCfg.State = &state
		}

		err = i.serviceService.Save(ctx, id, editCfg)
		if err != nil {
			return err
		}
		if input.Tags != nil {
			tags, err := i.getTagUuids(ctx, *input.Tags)
			if err != nil {
				return err
			}
			i.serviceTagService.Delete(ctx, nil, []string{id})
			for _, t := range tags {
				err = i.serviceTagService.Create(ctx, &service_tag.CreateTag{
					Tid: t,
					Sid: id,
				})
				if err != nil {
					return err
				}

			}
		}
		client, err := i.clusterService.GatewayClient(ctx, cluster.DefaultClusterID)
		if err != nil {
			return err
		}
		err = client.Subscribe().Online(ctx, &gateway.SubscribeRelease{
			Service:     id,
			Application: "apipark-global",
			Expired:     "0",
		})
		if err != nil {
			return err
		}
		if input.ModelMapping != nil && *input.ModelMapping != "" {
			m := make(map[string]string)
			err = json.Unmarshal([]byte(*input.ModelMapping), &m)
			if err != nil {
				return err
			}
			err = i.serviceModelMappingService.Save(ctx, &service_model_mapping.Save{
				Sid:     id,
				Content: *input.ModelMapping,
			})
			if err != nil {
				return err
			}

			err = client.Hash().Online(ctx, &gateway.HashRelease{
				HashKey: fmt.Sprintf("%s:%s", gateway.KeyServiceMapping, id),
				HashMap: m,
			})
			if err != nil {
				return err
			}
		}

		if input.EnableMCP != nil {
			if *input.EnableMCP {
				name := info.Name
				if input.Name != nil {
					name = *input.Name
				}
				err = i.updateMCPServer(ctx, id, name, "1.0")
				if err != nil {
					return err
				}
			} else {
				i.deleteMCPServer(ctx, id)
			}
		}

		return nil
	})
	if err != nil {
		return nil, err
	}
	return i.Get(ctx, id)
}

func (i *imlServiceModule) Delete(ctx context.Context, id string) error {
	err := i.transaction.Transaction(ctx, func(ctx context.Context) error {
		count, err := i.apiService.CountByService(ctx, id)
		if err != nil {
			return err
		}
		if count > 0 {
			return fmt.Errorf("service has apis, can not delete")
		}

		err = i.serviceService.Delete(ctx, id)
		if err != nil {
			return err
		}

		err = i.serviceModelMappingService.Delete(ctx, id)
		if err != nil {
			return err
		}
		client, err := i.clusterService.GatewayClient(ctx, cluster.DefaultClusterID)
		if err != nil {
			return err
		}
		err = client.Project().Offline(ctx, &gateway.ProjectRelease{
			Id: id,
		})
		if err != nil {
			if err.Error() != "nil" {
				return err
			}
		}
		err = client.Subscribe().Offline(ctx, &gateway.SubscribeRelease{
			Service:     id,
			Application: "apipark-global",
			Expired:     "0",
		})
		if err != nil {
			return err
		}

		err = client.Hash().Offline(ctx, &gateway.HashRelease{
			HashKey: fmt.Sprintf("%s:%s", gateway.KeyServiceMapping, id),
		})
		if err != nil {
			return err
		}
		i.deleteMCPServer(ctx, id)
		return nil
	})
	return err
}

func (i *imlServiceModule) getTagUuids(ctx context.Context, tags []string) ([]string, error) {
	list, err := i.tagService.Search(ctx, "", map[string]interface{}{"name": tags})
	if err != nil {
		return nil, err
	}
	tagMap := make(map[string]string)
	for _, t := range list {
		tagMap[t.Name] = t.Id
	}
	tagList := make([]string, 0, len(tags))
	repeatTag := make(map[string]struct{})
	for _, t := range tags {
		if _, ok := repeatTag[t]; ok {
			continue
		}
		repeatTag[t] = struct{}{}
		v := &tag.CreateTag{
			Name: t,
		}
		id, ok := tagMap[t]
		if !ok {
			v.Id = uuid.New().String()
			err = i.tagService.Create(ctx, v)
			if err != nil {
				return nil, err
			}
			tagMap[t] = v.Id
		} else {
			v.Id = id
		}
		tagList = append(tagList, v.Id)
	}
	return tagList, nil
}

type imlServiceDocModule struct {
	serviceService    service.IServiceService `autowired:""`
	serviceDocService service_doc.IDocService `autowired:""`
}

func (i *imlServiceDocModule) ServiceDoc(ctx context.Context, pid string) (*serviceDto.ServiceDoc, error) {
	_, err := i.serviceService.Check(ctx, pid, map[string]bool{"as_server": true})
	if err != nil {
		return nil, err
	}
	info, err := i.serviceService.Get(ctx, pid)
	if err != nil {
		return nil, err
	}
	doc, err := i.serviceDocService.Get(ctx, pid)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, err
		}
		return &serviceDto.ServiceDoc{
			Id:   pid,
			Name: info.Name,
			Doc:  "",
		}, nil
	}
	return &serviceDto.ServiceDoc{
		Id:         pid,
		Name:       info.Name,
		Doc:        doc.Doc,
		Creator:    auto.UUID(doc.Creator),
		CreateTime: auto.TimeLabel(doc.CreateTime),
		Updater:    auto.UUID(doc.Updater),
		UpdateTime: auto.TimeLabel(doc.UpdateTime),
	}, nil
}

func (i *imlServiceDocModule) SaveServiceDoc(ctx context.Context, pid string, input *serviceDto.SaveServiceDoc) error {
	_, err := i.serviceService.Check(ctx, pid, map[string]bool{"as_server": true})
	if err != nil {
		return err
	}
	return i.serviceDocService.Save(ctx, &service_doc.SaveDoc{
		Sid: pid,
		Doc: input.Doc,
	})
}

var (
	_ IAppModule       = &imlAppModule{}
	_ IExportAppModule = &imlAppModule{}
)

type imlAppModule struct {
	teamService       team.ITeamService                               `autowired:""`
	serviceService    service.IServiceService                         `autowired:""`
	teamMemberService team_member.ITeamMemberService                  `autowired:""`
	subscribeService  subscribe.ISubscribeService                     `autowired:""`
	authService       application_authorization.IAuthorizationService `autowired:""`
	roleService       role.IRoleService                               `autowired:""`
	roleMemberService role.IRoleMemberService                         `autowired:""`
	transaction       store.ITransaction                              `autowired:""`
}

func (i *imlAppModule) SearchCanSubscribe(ctx context.Context, serviceId string) ([]*service_dto.SubscribeAppItem, bool, error) {
	apps, err := i.searchMyApps(ctx, "", "")
	if err != nil {
		return nil, false, err
	}
	subscribes, err := i.subscribeService.ListByServices(ctx, serviceId)
	if err != nil {
		return nil, false, err
	}
	subscribeMap := utils.SliceToMapO(subscribes, func(p *subscribe.Subscribe) (string, struct{}) {
		return p.Application, struct{}{}
	}, func(s *subscribe.Subscribe) bool {
		return s.ApplyStatus == subscribe.ApplyStatusSubscribe
	})
	canSubscribe := false
	list, err := i.roleService.ListByPermit(ctx, access.SystemWorkspaceApplicationManagerAll)
	if err == nil && len(list) > 0 {
		return utils.SliceToSlice(apps, func(p *service.Service) *service_dto.SubscribeAppItem {
			_, isSubscribed := subscribeMap[p.Id]
			if !isSubscribed {
				canSubscribe = true
			}
			return &service_dto.SubscribeAppItem{
				Id:           p.Id,
				Name:         p.Name,
				IsSubscribed: isSubscribed,
			}
		}), canSubscribe, nil
	}
	list, err = i.roleService.ListByPermit(ctx, access.TeamConsumerSubscriptionSubscribe)
	if err != nil {
		return nil, false, nil
	}
	roleIds := utils.SliceToSlice(list, func(p *role.RoleByPermit) string {
		return p.Id
	})
	members, err := i.roleMemberService.ListByRoleIds(ctx, utils.UserId(ctx), roleIds...)
	if err != nil {
		return nil, false, err
	}
	if len(members) == 0 {
		return nil, false, nil
	}

	teamMap := utils.SliceToMapO(members, func(p *role.Member) (string, struct{}) {
		return role.TrimTeamTarget(p.Target), struct{}{}
	})
	result := make([]*service_dto.SubscribeAppItem, 0, len(apps))
	for _, app := range apps {
		if _, ok := teamMap[app.Team]; !ok {
			continue
		}
		_, isSubscribed := subscribeMap[app.Id]
		if !isSubscribed {
			canSubscribe = true
		}
		result = append(result, &service_dto.SubscribeAppItem{
			Id:           app.Id,
			Name:         app.Name,
			IsSubscribed: isSubscribed,
		})
	}

	return result, canSubscribe, nil
}

func (i *imlAppModule) ExportAll(ctx context.Context) ([]*service_dto.ExportApp, error) {
	apps, err := i.serviceService.AppList(ctx)
	if err != nil {
		return nil, err
	}
	return utils.SliceToSlice(apps, func(p *service.Service) *service_dto.ExportApp {
		return &service_dto.ExportApp{
			Id:          p.Id,
			Name:        p.Name,
			Description: p.Description,
			Team:        p.Team,
		}
	}), nil
}

func (i *imlAppModule) Search(ctx context.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
	var services []*service.Service
	var err error
	if teamId != "" {
		_, err = i.teamService.Get(ctx, teamId)
		if err != nil {
			return nil, err
		}
		services, err = i.serviceService.Search(ctx, keyword, map[string]interface{}{"team": teamId, "as_app": true}, "update_at desc")
	} else {
		services, err = i.serviceService.Search(ctx, keyword, map[string]interface{}{"as_app": true}, "update_at desc")
	}
	if err != nil {
		return nil, err
	}

	serviceIds := utils.SliceToSlice(services, func(p *service.Service) string {
		return p.Id
	})

	subscribers, err := i.subscribeService.SubscriptionsByApplication(ctx, serviceIds...)
	if err != nil {
		return nil, err
	}

	subscribeCount := map[string]int64{}
	subscribeVerifyCount := map[string]int64{}
	verifyTmp := map[string]struct{}{}
	subscribeTmp := map[string]struct{}{}
	for _, s := range subscribers {
		key := fmt.Sprintf("%s-%s", s.Service, s.Application)
		switch s.ApplyStatus {
		case subscribe.ApplyStatusSubscribe:
			if _, ok := subscribeTmp[key]; !ok {
				subscribeTmp[key] = struct{}{}
				subscribeCount[s.Application]++
			}
		case subscribe.ApplyStatusReview:
			if _, ok := verifyTmp[key]; !ok {
				verifyTmp[key] = struct{}{}
				subscribeVerifyCount[s.Application]++
			}
		default:

		}
	}
	authMap, err := i.authService.CountByApp(ctx, serviceIds...)
	if err != nil {
		return nil, err
	}
	items := make([]*service_dto.AppItem, 0, len(services))
	for _, model := range services {
		subscribeNum := subscribeCount[model.Id]
		verifyNum := subscribeVerifyCount[model.Id]
		items = append(items, &service_dto.AppItem{
			Id:                 model.Id,
			Name:               model.Name,
			Description:        model.Description,
			CreateTime:         auto.TimeLabel(model.CreateTime),
			UpdateTime:         auto.TimeLabel(model.UpdateTime),
			Team:               auto.UUID(model.Team),
			SubscribeNum:       subscribeNum,
			SubscribeVerifyNum: verifyNum,
			CanDelete:          subscribeNum == 0,
			AuthNum:            authMap[model.Id],
		})
	}
	sort.Slice(items, func(i, j int) bool {
		if items[i].SubscribeNum != items[j].SubscribeNum {
			return items[i].SubscribeNum > items[j].SubscribeNum
		}
		if items[i].SubscribeVerifyNum != items[j].SubscribeVerifyNum {
			return items[i].SubscribeVerifyNum > items[j].SubscribeVerifyNum
		}
		return items[i].Name < items[j].Name
	})
	return items, nil
}

func (i *imlAppModule) CreateApp(ctx context.Context, teamID string, input *service_dto.CreateApp) (*service_dto.App, error) {
	if input.Id == "" {
		input.Id = uuid.New().String()
	}
	userId := utils.UserId(ctx)
	mo := &service.Create{
		Id:          input.Id,
		Name:        input.Name,
		Description: input.Description,
		Team:        teamID,
		AsApp:       true,
	}
	// 判断用户是否在团队内
	members, err := i.teamMemberService.Members(ctx, []string{teamID}, []string{userId})
	if err != nil {
		return nil, err
	}
	if len(members) == 0 {
		return nil, fmt.Errorf("master is not in team")
	}

	err = i.transaction.Transaction(ctx, func(ctx context.Context) error {
		return i.serviceService.Create(ctx, mo)
	})
	if err != nil {
		return nil, err
	}
	return i.GetApp(ctx, input.Id)
}

func (i *imlAppModule) UpdateApp(ctx context.Context, appId string, input *service_dto.UpdateApp) (*service_dto.App, error) {
	// userId := utils.UserId(ctx)
	info, err := i.serviceService.Get(ctx, appId)
	if err != nil {
		return nil, err
	}
	if !info.AsApp {
		return nil, fmt.Errorf("not app")
	}
	//if info.Master != userId {
	//	return nil, fmt.Errorf("user is not app master, can not update")
	//}

	err = i.serviceService.Save(ctx, appId, &service.Edit{
		Name:        input.Name,
		Description: input.Description,
	})
	if err != nil {
		return nil, err
	}
	return i.GetApp(ctx, info.Id)
}

func (i *imlAppModule) searchMyApps(ctx context.Context, teamId string, keyword string) ([]*service.Service, error) {
	userID := utils.UserId(ctx)
	condition := make(map[string]interface{})
	condition["as_app"] = true
	if teamId != "" {
		_, err := i.teamService.Get(ctx, teamId)
		if err != nil {
			return nil, err
		}
		condition["team"] = teamId
		return i.serviceService.Search(ctx, keyword, condition, "update_at desc")
	} else {
		membersForUser, err := i.teamMemberService.FilterMembersForUser(ctx, userID)
		if err != nil {
			return nil, err
		}
		teamIds := membersForUser[userID]
		condition["team"] = teamIds

		return i.serviceService.Search(ctx, keyword, condition, "update_at desc")
	}
}

func (i *imlAppModule) SearchMyApps(ctx context.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
	services, err := i.searchMyApps(ctx, teamId, keyword)
	if err != nil {
		return nil, err
	}
	serviceIds := utils.SliceToSlice(services, func(p *service.Service) string {
		return p.Id
	})

	subscribers, err := i.subscribeService.SubscriptionsByApplication(ctx, serviceIds...)
	if err != nil {
		return nil, err
	}
	authMap, err := i.authService.CountByApp(ctx, serviceIds...)
	if err != nil {
		return nil, err
	}

	subscribeCount := map[string]int64{}
	subscribeVerifyCount := map[string]int64{}
	verifyTmp := map[string]struct{}{}
	subscribeTmp := map[string]struct{}{}
	for _, s := range subscribers {
		key := fmt.Sprintf("%s-%s", s.Service, s.Application)
		switch s.ApplyStatus {
		case subscribe.ApplyStatusSubscribe:
			if _, ok := subscribeTmp[key]; !ok {
				subscribeTmp[key] = struct{}{}
				subscribeCount[s.Application]++
			}
		case subscribe.ApplyStatusReview:
			if _, ok := verifyTmp[key]; !ok {
				verifyTmp[key] = struct{}{}
				subscribeVerifyCount[s.Application]++
			}
		default:

		}
	}
	items := make([]*service_dto.AppItem, 0, len(services))
	for _, model := range services {
		subscribeNum := subscribeCount[model.Id]
		verifyNum := subscribeVerifyCount[model.Id]
		items = append(items, &service_dto.AppItem{
			Id:                 model.Id,
			Name:               model.Name,
			Description:        model.Description,
			CreateTime:         auto.TimeLabel(model.CreateTime),
			UpdateTime:         auto.TimeLabel(model.UpdateTime),
			Team:               auto.UUID(model.Team),
			SubscribeNum:       subscribeNum,
			SubscribeVerifyNum: verifyNum,
			CanDelete:          subscribeNum == 0,
			AuthNum:            authMap[model.Id],
		})
	}
	sort.Slice(items, func(i, j int) bool {
		if items[i].SubscribeNum != items[j].SubscribeNum {
			return items[i].SubscribeNum > items[j].SubscribeNum
		}
		if items[i].SubscribeVerifyNum != items[j].SubscribeVerifyNum {
			return items[i].SubscribeVerifyNum > items[j].SubscribeVerifyNum
		}
		return items[i].Name < items[j].Name
	})
	return items, nil
}

func (i *imlAppModule) SimpleApps(ctx context.Context, keyword string) ([]*service_dto.SimpleAppItem, error) {
	w := make(map[string]interface{})
	w["as_app"] = true
	services, err := i.serviceService.Search(ctx, keyword, w)
	if err != nil {
		return nil, err
	}
	return utils.SliceToSlice(services, func(p *service.Service) *service_dto.SimpleAppItem {
		return &service_dto.SimpleAppItem{
			Id:          p.Id,
			Name:        p.Name,
			Description: p.Description,
			Team:        auto.UUID(p.Team),
		}
	}), nil
}

func (i *imlAppModule) MySimpleApps(ctx context.Context, keyword string) ([]*service_dto.SimpleAppItem, error) {
	services, err := i.searchMyApps(ctx, "", keyword)
	if err != nil {
		return nil, err
	}
	items := make([]*service_dto.SimpleAppItem, 0, len(services))
	for _, p := range services {
		items = append(items, &service_dto.SimpleAppItem{
			Id:          p.Id,
			Name:        p.Name,
			Description: p.Description,
			Team:        auto.UUID(p.Team),
		})
	}
	return items, nil
}

func (i *imlAppModule) GetApp(ctx context.Context, appId string) (*service_dto.App, error) {
	info, err := i.serviceService.Get(ctx, appId)
	if err != nil {
		return nil, err
	}
	if !info.AsApp {
		return nil, errors.New("not app")
	}
	return &service_dto.App{
		Id:          info.Id,
		Name:        info.Name,
		Description: info.Description,
		Team:        auto.UUID(info.Team),
		CreateTime:  auto.TimeLabel(info.CreateTime),
		UpdateTime:  auto.TimeLabel(info.UpdateTime),
		AsApp:       info.AsApp,
	}, nil
}

func (i *imlAppModule) DeleteApp(ctx context.Context, appId string) error {
	info, err := i.serviceService.Get(ctx, appId)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		return nil
	}
	if !info.AsApp {
		return errors.New("not app, can not delete")
	}

	return i.serviceService.Delete(ctx, appId)
}
