/*
 * Copyright 2025 coze-dev Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package service

import (
	"context"
	"fmt"
	"sort"

	model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
	searchModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/search"
	pluginCommon "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop_common"
	resCommon "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
	"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crosssearch"
	pluginConf "github.com/coze-dev/coze-studio/backend/domain/plugin/conf"
	"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
	"github.com/coze-dev/coze-studio/backend/domain/plugin/repository"
	"github.com/coze-dev/coze-studio/backend/pkg/errorx"
	"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
	"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
	"github.com/coze-dev/coze-studio/backend/pkg/logs"
	"github.com/coze-dev/coze-studio/backend/types/errno"
)

func (p *pluginServiceImpl) GetOnlinePlugin(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error) {
	pl, exist, err := p.pluginRepo.GetOnlinePlugin(ctx, pluginID)
	if err != nil {
		return nil, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", pluginID)
	}
	if !exist {
		return nil, errorx.New(errno.ErrPluginRecordNotFound)
	}

	return pl, nil
}

func (p *pluginServiceImpl) MGetOnlinePlugins(ctx context.Context, pluginIDs []int64) (plugins []*entity.PluginInfo, err error) {
	plugins, err = p.pluginRepo.MGetOnlinePlugins(ctx, pluginIDs)
	if err != nil {
		return nil, errorx.Wrapf(err, "MGetOnlinePlugins failed, pluginIDs=%v", pluginIDs)
	}

	res := make([]*model.PluginInfo, 0, len(plugins))
	for _, pl := range plugins {
		res = append(res, pl.PluginInfo)
	}

	return plugins, nil
}

func (p *pluginServiceImpl) GetOnlineTool(ctx context.Context, toolID int64) (tool *entity.ToolInfo, err error) {
	tool, exist, err := p.toolRepo.GetOnlineTool(ctx, toolID)
	if err != nil {
		return nil, errorx.Wrapf(err, "GetOnlineTool failed, toolID=%d", toolID)
	}
	if !exist {
		return nil, errorx.New(errno.ErrPluginRecordNotFound)
	}

	return tool, nil
}

func (p *pluginServiceImpl) MGetOnlineTools(ctx context.Context, toolIDs []int64) (tools []*entity.ToolInfo, err error) {
	tools, err = p.toolRepo.MGetOnlineTools(ctx, toolIDs)
	if err != nil {
		return nil, errorx.Wrapf(err, "MGetOnlineTools failed, toolIDs=%v", toolIDs)
	}

	return tools, nil
}

func (p *pluginServiceImpl) MGetVersionTools(ctx context.Context, versionTools []entity.VersionTool) (tools []*entity.ToolInfo, err error) {
	tools, err = p.toolRepo.MGetVersionTools(ctx, versionTools)
	if err != nil {
		return nil, errorx.Wrapf(err, "MGetVersionTools failed, versionTools=%v", versionTools)
	}

	return tools, nil
}

func (p *pluginServiceImpl) ListPluginProducts(ctx context.Context, req *ListPluginProductsRequest) (resp *ListPluginProductsResponse, err error) {
	plugins := slices.Transform(pluginConf.GetAllPluginProducts(), func(p *pluginConf.PluginInfo) *entity.PluginInfo {
		return entity.NewPluginInfo(p.Info)
	})
	sort.Slice(plugins, func(i, j int) bool {
		return plugins[i].GetRefProductID() < plugins[j].GetRefProductID()
	})

	return &ListPluginProductsResponse{
		Plugins: plugins,
		Total:   int64(len(plugins)),
	}, nil
}

func (p *pluginServiceImpl) GetPluginProductAllTools(ctx context.Context, pluginID int64) (tools []*entity.ToolInfo, err error) {
	res, err := p.toolRepo.GetPluginAllOnlineTools(ctx, pluginID)
	if err != nil {
		return nil, errorx.Wrapf(err, "GetPluginAllOnlineTools failed, pluginID=%d", pluginID)
	}

	return res, nil
}

func (p *pluginServiceImpl) DeleteAPPAllPlugins(ctx context.Context, appID int64) (pluginIDs []int64, err error) {
	return p.pluginRepo.DeleteAPPAllPlugins(ctx, appID)
}

func (p *pluginServiceImpl) GetAPPAllPlugins(ctx context.Context, appID int64) (plugins []*entity.PluginInfo, err error) {
	plugins, err = p.pluginRepo.GetAPPAllDraftPlugins(ctx, appID)
	if err != nil {
		return nil, errorx.Wrapf(err, "GetAPPAllDraftPlugins failed, appID=%d", appID)
	}

	return plugins, nil
}

func (p *pluginServiceImpl) MGetVersionPlugins(ctx context.Context, versionPlugins []entity.VersionPlugin) (plugins []*entity.PluginInfo, err error) {
	plugins, err = p.pluginRepo.MGetVersionPlugins(ctx, versionPlugins)
	if err != nil {
		return nil, errorx.Wrapf(err, "MGetVersionPlugins failed, versionPlugins=%v", versionPlugins)
	}

	return plugins, nil
}

func (p *pluginServiceImpl) ListCustomOnlinePlugins(ctx context.Context, spaceID int64, pageInfo entity.PageInfo) (plugins []*entity.PluginInfo, total int64, err error) {
	if pageInfo.Name == nil || *pageInfo.Name == "" {
		plugins, total, err = p.pluginRepo.ListCustomOnlinePlugins(ctx, spaceID, pageInfo)
		if err != nil {
			return nil, 0, errorx.Wrapf(err, "ListCustomOnlinePlugins failed, spaceID=%d", spaceID)
		}
		return plugins, total, nil
	}

	res, err := crosssearch.DefaultSVC().SearchResources(ctx, &searchModel.SearchResourcesRequest{
		SpaceID:  spaceID,
		Name:     *pageInfo.Name,
		OrderAsc: false,
		ResTypeFilter: []resCommon.ResType{
			resCommon.ResType_Plugin,
		},
		OrderFiledName: func() string {
			if pageInfo.SortBy == nil || *pageInfo.SortBy != entity.SortByCreatedAt {
				return searchModel.FieldOfUpdateTime
			}
			return searchModel.FieldOfCreateTime
		}(),
		Page:  ptr.Of(int32(pageInfo.Page)),
		Limit: int32(pageInfo.Size),
	})
	if err != nil {
		return nil, 0, errorx.Wrapf(err, "SearchResources failed, spaceID=%d", spaceID)
	}

	plugins = make([]*entity.PluginInfo, 0, len(res.Data))
	for _, pl := range res.Data {
		draftPlugin, exist, err := p.pluginRepo.GetOnlinePlugin(ctx, pl.ResID)
		if err != nil {
			return nil, 0, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", pl.ResID)
		}
		if !exist {
			logs.CtxWarnf(ctx, "online plugin not exist, pluginID=%d", pl.ResID)
			continue
		}
		plugins = append(plugins, draftPlugin)
	}

	if res.TotalHits != nil {
		total = *res.TotalHits
	}

	return plugins, total, nil
}

func (p *pluginServiceImpl) MGetPluginLatestVersion(ctx context.Context, pluginIDs []int64) (resp *MGetPluginLatestVersionResponse, err error) {
	plugins, err := p.pluginRepo.MGetOnlinePlugins(ctx, pluginIDs,
		repository.WithPluginID(),
		repository.WithPluginVersion())
	if err != nil {
		return nil, errorx.Wrapf(err, "MGetOnlinePlugins failed, pluginIDs=%v", pluginIDs)
	}

	versions := make(map[int64]string, len(plugins))
	for _, pl := range plugins {
		versions[pl.ID] = pl.GetVersion()
	}

	resp = &MGetPluginLatestVersionResponse{
		Versions: versions,
	}

	return resp, nil
}

func (p *pluginServiceImpl) CopyPlugin(ctx context.Context, req *CopyPluginRequest) (resp *CopyPluginResponse, err error) {
	err = p.checkCanCopyPlugin(ctx, req.PluginID, req.CopyScene)
	if err != nil {
		return nil, err
	}

	plugin, tools, err := p.getCopySourcePluginAndTools(ctx, req.PluginID, req.CopyScene)
	if err != nil {
		return nil, err
	}

	p.changePluginAndToolsInfoForCopy(req, plugin, tools)

	toolMap := make(map[int64]*entity.ToolInfo, len(tools))
	for _, tool := range tools {
		toolMap[tool.ID] = tool
	}

	plugin, tools, err = p.pluginRepo.CopyPlugin(ctx, &repository.CopyPluginRequest{
		Plugin: plugin,
		Tools:  tools,
	})
	if err != nil {
		return nil, errorx.Wrapf(err, "CopyPlugin failed, pluginID=%d", req.PluginID)
	}

	resp = &CopyPluginResponse{
		Plugin: plugin,
		Tools:  toolMap,
	}

	return resp, nil
}

func (p *pluginServiceImpl) changePluginAndToolsInfoForCopy(req *CopyPluginRequest, plugin *entity.PluginInfo, tools []*entity.ToolInfo) {
	plugin.Version = nil
	plugin.VersionDesc = nil

	plugin.DeveloperID = req.UserID

	if req.CopyScene != model.CopySceneOfAPPDuplicate {
		plugin.SetName(fmt.Sprintf("%s_copy", plugin.GetName()))
	}

	if req.CopyScene == model.CopySceneOfToLibrary {
		const (
			defaultVersion     = "v0.0.1"
			defaultVersionDesc = "copy to library"
		)

		plugin.APPID = nil
		plugin.Version = ptr.Of(defaultVersion)
		plugin.VersionDesc = ptr.Of(defaultVersionDesc)

		for _, tool := range tools {
			tool.Version = ptr.Of(defaultVersion)
		}
	}

	if req.CopyScene == model.CopySceneOfToAPP {
		plugin.APPID = req.TargetAPPID

		for _, tool := range tools {
			tool.DebugStatus = ptr.Of(pluginCommon.APIDebugStatus_DebugPassed)
		}
	}

	if req.CopyScene == model.CopySceneOfAPPDuplicate {
		plugin.APPID = req.TargetAPPID
	}
}

func (p *pluginServiceImpl) checkCanCopyPlugin(ctx context.Context, pluginID int64, scene model.CopyScene) (err error) {
	switch scene {
	case model.CopySceneOfToAPP, model.CopySceneOfDuplicate, model.CopySceneOfAPPDuplicate:
		return nil
	case model.CopySceneOfToLibrary:
		return p.checkToolsDebugStatus(ctx, pluginID)
	default:
		return fmt.Errorf("unsupported copy scene '%s'", scene)
	}
}

func (p *pluginServiceImpl) getCopySourcePluginAndTools(ctx context.Context, pluginID int64, scene model.CopyScene) (plugin *entity.PluginInfo, tools []*entity.ToolInfo, err error) {
	switch scene {
	case model.CopySceneOfToAPP:
		return p.getOnlinePluginAndTools(ctx, pluginID)
	case model.CopySceneOfToLibrary, model.CopySceneOfDuplicate, model.CopySceneOfAPPDuplicate:
		return p.getDraftPluginAndTools(ctx, pluginID)
	default:
		return nil, nil, fmt.Errorf("unsupported copy scene '%s'", scene)
	}
}

func (p *pluginServiceImpl) getOnlinePluginAndTools(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, tools []*entity.ToolInfo, err error) {
	onlinePlugin, exist, err := p.pluginRepo.GetOnlinePlugin(ctx, pluginID)
	if err != nil {
		return nil, nil, err
	}
	if !exist {
		return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
	}

	onlineTools, err := p.toolRepo.GetPluginAllOnlineTools(ctx, pluginID)
	if err != nil {
		return nil, nil, err
	}

	return onlinePlugin, onlineTools, nil
}

func (p *pluginServiceImpl) getDraftPluginAndTools(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, tools []*entity.ToolInfo, err error) {
	draftPlugin, exist, err := p.pluginRepo.GetDraftPlugin(ctx, pluginID)
	if err != nil {
		return nil, nil, err
	}
	if !exist {
		return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
	}

	draftTools, err := p.toolRepo.GetPluginAllDraftTools(ctx, pluginID)
	if err != nil {
		return nil, nil, err
	}

	return draftPlugin, draftTools, nil
}

func (p *pluginServiceImpl) MoveAPPPluginToLibrary(ctx context.Context, pluginID int64) (draftPlugin *entity.PluginInfo, err error) {
	draftPlugin, exist, err := p.pluginRepo.GetDraftPlugin(ctx, pluginID)
	if err != nil {
		return nil, err
	}
	if !exist {
		return nil, errorx.New(errno.ErrPluginRecordNotFound)
	}

	err = p.checkToolsDebugStatus(ctx, pluginID)
	if err != nil {
		return nil, err
	}

	draftTools, err := p.toolRepo.GetPluginAllDraftTools(ctx, pluginID)
	if err != nil {
		return nil, err
	}

	p.changePluginAndToolsInfoForMove(draftPlugin, draftTools)

	err = p.pluginRepo.MoveAPPPluginToLibrary(ctx, draftPlugin, draftTools)
	if err != nil {
		return nil, errorx.Wrapf(err, "MoveAPPPluginToLibrary failed, pluginID=%d", pluginID)
	}

	return draftPlugin, nil
}

func (p *pluginServiceImpl) changePluginAndToolsInfoForMove(plugin *entity.PluginInfo,
	tools []*entity.ToolInfo) {

	const (
		defaultVersion     = "v0.0.1"
		defaultVersionDesc = "move to library"
	)

	plugin.Version = ptr.Of(defaultVersion)
	plugin.VersionDesc = ptr.Of(defaultVersionDesc)

	for _, tool := range tools {
		tool.Version = ptr.Of(defaultVersion)
	}

	plugin.APPID = nil
}
