/*
 * Copyright 2025 CloudWeGo 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 tools

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"path/filepath"
	"strings"

	"github.com/cloudwego/eino/components/tool"
	"github.com/cloudwego/eino/schema"
	jsoniter "github.com/json-iterator/go"

	"github.com/cloudwego/eino-examples/adk/multiagent/integration-excel-agent/utils"
)

type ToolRequestPreprocess func(ctx context.Context, baseTool tool.InvokableTool, toolArguments string) (string, error)

type ToolResponsePostprocess func(ctx context.Context, baseTool tool.InvokableTool, toolResponse, toolArguments string) (string, error)

func NewWrapTool(t tool.InvokableTool, preprocess []ToolRequestPreprocess, postprocess []ToolResponsePostprocess) tool.InvokableTool {
	return &wrapTool{
		baseTool:    t,
		preprocess:  preprocess,
		postprocess: postprocess,
	}
}

type wrapTool struct {
	baseTool    tool.InvokableTool
	preprocess  []ToolRequestPreprocess
	postprocess []ToolResponsePostprocess
}

func (w *wrapTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
	return w.baseTool.Info(ctx)
}

func (w *wrapTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
	for _, pre := range w.preprocess {
		var err error
		argumentsInJSON, err = pre(ctx, w.baseTool, argumentsInJSON)
		if err != nil {
			log.Printf("[WrapTool.PreProcess] failed to process response: %v", err)
		}
	}

	resp, err := w.baseTool.InvokableRun(ctx, argumentsInJSON, opts...)
	if err != nil {
		return "", err
	}

	for _, post := range w.postprocess {
		resp, err = post(ctx, w.baseTool, resp, argumentsInJSON)
		if err != nil {
			log.Printf("[WrapTool.PostProcess] failed to process response: %v", err)
			return resp, err
		}
	}

	return resp, nil
}

func ToolRequestRepairJSON(ctx context.Context, baseTool tool.InvokableTool, toolArguments string) (string, error) {
	return utils.RepairJSON(toolArguments), nil
}

type runResult struct {
	Command    string            `json:"command"`
	StdOut     []*stdoutData     `json:"stdout"`
	StdErr     []*stderrData     `json:"stderr"`
	FileChange []*fileChangeData `json:"file_change"`
	ErrData    []*errData        `json:"err_data"`
}

type stdoutData struct {
	Stdout string `json:"stdout"`
}

type stderrData struct {
	Stderr string `json:"stderr"`
}

type errData struct {
	Err string `json:"err"`
}

type fileChangeData struct {
	FileType       string          `json:"file_type"`
	Path           string          `json:"path"`
	Type           string          `json:"type"`
	Uri            string          `json:"uri"`
	Url            string          `json:"url"`
	MultiMediaInfo *multiMediaInfo `json:"multi_media_info,omitempty"`
}

type multiMediaInfo struct {
	MediaType      string `json:"media_type"`
	AdditionalType string `json:"additional_type"`
	AdditionalInfo string `json:"additional_info"`
}

func FilePostProcess(ctx context.Context, baseTool tool.InvokableTool, toolResponse, toolArguments string) (string, error) {
	rawResult := runResult{}
	if err := json.Unmarshal([]byte(toolResponse), &rawResult); err != nil {
		return toolResponse, nil
	}

	type fileOutputFormat struct {
		FileType string `json:"Change subject (file/directory)"`
		Path     string `json:"File/directory relative path"`
		Type     string `json:"Change type (create/delete/update)"`
	}
	var (
		stdOut     []string
		fileChange []fileOutputFormat
		stdErr     []string
	)
	for _, item := range rawResult.StdOut {
		if item != nil {
			stdOut = append(stdOut, item.Stdout)
		}
	}
	for _, item := range rawResult.FileChange {
		if item != nil {
			fileChange = append(fileChange, fileOutputFormat{
				FileType: item.FileType,
				Path:     item.Path,
				Type:     item.Type,
			})
		}
	}
	for _, item := range rawResult.StdErr {
		if item != nil {
			stdErr = append(stdErr, item.Stderr)
		}
	}
	for _, item := range rawResult.ErrData {
		if item != nil {
			stdErr = append(stdErr, item.Err)
		}
	}

	var output string
	if len(fileChange) > 0 {
		fcText, _ := jsoniter.MarshalToString(fileChange)
		output += "fileChange: \n" + fcText + "\n"
	}
	if len(stdErr) > 0 {
		output += "shell command stderr and warnings:" + strings.Join(stdErr, "\n") + "\n"
	}
	if len(rawResult.StdOut) > 0 {
		output += "shell command stdout: " + strings.Join(stdOut, "\n") + "\n"
	}

	return output, nil
}

func EditFilePostProcess(ctx context.Context, baseTool tool.InvokableTool, toolResponse, toolArguments string) (string, error) {
	return fmt.Sprintf("Write file: %s success!", toolResponse), nil
}

func isImage(uri string) bool {
	ext := filepath.Ext(uri)
	for _, e := range []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".heic"} {
		if ext == e {
			return true
		}
	}
	return false
}
