/*
 * 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 infra

import (
	"context"
	"fmt"
	"os"

	"github.com/RanFeng/ilog"
	"github.com/cloudwego/eino-ext/callbacks/apmplus"
	clc "github.com/cloudwego/eino-ext/callbacks/cozeloop"
	"github.com/cloudwego/eino/callbacks"
	hertzconfig "github.com/cloudwego/hertz/pkg/common/config"
	"github.com/coze-dev/cozeloop-go"
	"github.com/hertz-contrib/obs-opentelemetry/provider"
	hertztracing "github.com/hertz-contrib/obs-opentelemetry/tracing"
)

var EmptyHertzConfigOption = hertzconfig.Option{}

// InitAPMPlusTracing initializes APMPlus observability components for Eino applications
// Creates Eino APMPlus callbacks and optionally initializes Hertz server tracing integration based on the withHertzServer flag.
// Parameters:
//
//	withHertzServer - If true, enables Hertz HTTP server tracing integration
//
// Returns:
//
//	tracer - Hertz server configuration (nil if withHertzServer=false)
//	cfg - Tracing configuration instance (nil if withHertzServer=false)
//	shutdown - cleanup function for eino apmplus callback to flush telemetry data to APMPlus Server
func InitAPMPlusTracing(ctx context.Context, withHertzServer bool) (tracer hertzconfig.Option, cfg *hertztracing.Config, shutdown func(ctx context.Context) error) {
	appKey := os.Getenv("APMPLUS_APP_KEY")
	if appKey == "" {
		return EmptyHertzConfigOption, nil, nil
	}
	region := os.Getenv("APMPLUS_REGION")
	if region == "" {
		region = "cn-beijing"
	}
	_, shutdown = initAPMPlusCallback(ctx, appKey, region)
	if !withHertzServer {
		return EmptyHertzConfigOption, nil, shutdown
	}
	// for hertz server, init hertz tracing integration
	tracer, cfg = initHertzTracing(ctx, appKey, region)
	return tracer, cfg, shutdown
}

// initAPMPlusCallback initializes the Eino APMPlus callback handler.
// It creates trace spans and collects metrics for Eino applications,
// which will be sent to the APMPlus server for observability.
func initAPMPlusCallback(ctx context.Context, appKey, region string) (callbacks.Handler, func(ctx context.Context) error) {
	ilog.EventInfo(ctx, "Init APMPlus callback, watch at: https://console.volcengine.com/apmplus-server", region)
	cbh, shutdown, err := apmplus.NewApmplusHandler(&apmplus.Config{
		Host:        fmt.Sprintf("apmplus-%s.volces.com:4317", region),
		AppKey:      appKey,
		ServiceName: "deer-go",
		Release:     "release/v0.0.1",
	})
	if err != nil {
		ilog.EventError(ctx, err, "init apmplus callback failed")
		return nil, nil
	}
	callbacks.AppendGlobalHandlers(cbh)
	ilog.EventInfo(ctx, "Init APMPlus Callback success")
	return cbh, shutdown
}

// initHertzTracing initializes Hertz framework tracing integration
// It creates trace spans and collects metrics for incoming HTTP requests,
// which will be sent to the APMPlus server for observability.
func initHertzTracing(ctx context.Context, appKey, region string) (hertzconfig.Option, *hertztracing.Config) {
	ilog.EventInfo(ctx, "Init Hertz Tracing", region)
	_ = provider.NewOpenTelemetryProvider(
		provider.WithServiceName("deer-go"),
		provider.WithExportEndpoint(fmt.Sprintf("apmplus-%s.volces.com:4317", region)),
		provider.WithInsecure(),
		provider.WithHeaders(map[string]string{"X-ByteAPM-AppKey": appKey}),
	)
	tracer, cfg := hertztracing.NewServerTracer()
	return tracer, cfg
}

func InitCozeLoopTracing() {
	cozeloopApiToken := os.Getenv("COZELOOP_API_TOKEN")
	cozeloopWorkspaceID := os.Getenv("COZELOOP_WORKSPACE_ID") // use cozeloop trace, from https://loop.coze.cn/open/docs/cozeloop/go-sdk#4a8c980e

	if cozeloopApiToken == "" || cozeloopWorkspaceID == "" {
		return
	}
	client, err := cozeloop.NewClient(
		cozeloop.WithAPIToken(cozeloopApiToken),
		cozeloop.WithWorkspaceID(cozeloopWorkspaceID),
	)
	if err != nil {
		panic(err)
	}
	cozeloop.SetDefaultClient(client)
	callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
}
