package metrics

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/dto"
	log2 "github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"go.uber.org/zap"

	"github.com/spf13/viper"
)

var AppConfig MetricsConfig
var OrgsConfig dto.Orgs

func LoadConfigForMetrics() error {
	configName := "config-metrics"
	// viper.SetConfigName("config")
	viper.SetConfigName(configName)
	viper.SetConfigType("yaml")
	viper.AddConfigPath("./configs") // path to look for the config file in

	if err := viper.ReadInConfig(); err != nil {
		return err
	}

	viper.AutomaticEnv() // 👈 启用环境变量
	viper.SetEnvPrefix("METRICS")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	if err := viper.Unmarshal(&AppConfig); err != nil {
		return err
	}

	// Load organization configuration
	if err := LoadOrgsConfig(); err != nil {
		return fmt.Errorf("failed to load orgs config: %w", err)
	}

	return nil
}

// LoadOrgsConfig loads organization configuration from cluster.yaml
func LoadOrgsConfig() error {
	v := viper.New()
	v.SetConfigName("cluster")
	v.SetConfigType("yaml")
	v.AddConfigPath("./configs")

	if err := v.ReadInConfig(); err != nil {
		return fmt.Errorf("failed to read cluster config: %w", err)
	}

	if err := v.Unmarshal(&OrgsConfig); err != nil {
		return fmt.Errorf("failed to unmarshal cluster config: %w", err)
	}

	return nil
}

// GetClusterByID Get cluster configuration by cluster ID
func GetClusterByID(clusterID string) *dto.Cluster {
	for _, org := range OrgsConfig.Orgs {
		for _, cluster := range org.Clusters {
			if cluster.ClusterID == clusterID {
				return &cluster
			}
		}
	}
	return nil
}

func (c *MetricsConfig) GetCompareQuery() []MetricsQueryConfig {
	return c.CompareQuery
}

func (c *MetricsConfig) GetTnxQueries() []MetricsQueryConfig {
	return c.TnxQueries
}

func (c *MetricsConfig) GetLatencyQueries() []MetricsQueryConfig {
	return c.LatencyQueries
}

func (c *MetricsConfig) GetCPUResource() []MetricsQueryConfig {
	return c.CPUResource
}

func (c *MetricsConfig) GetMEMResource() []MetricsQueryConfig {
	return c.MEMResource
}

func (c *MetricsConfig) GetDISKResource() []MetricsQueryConfig {
	return c.DiskQueries
}

func InitForMetrics() {
	// 1. Load configuration and set global configuration
	err := LoadConfigForMetrics()
	if err != nil {
		// t.Fatalf("Failed to load config: %v", err)
		log2.PlainError("Failed to load config", zap.Error(err))
	}
	log2.PlainInfo("load config successful")

	// 2. Initialize log configuration
	if err := log2.InitLog(config.AppConfig.LogDir); err != nil {
		log2.PlainError("Failed to initialize log", zap.Error(err))
	}

	flag.Parse() // Parse command line arguments

}

// GetO11yToken gets Auth0 OAuth token using client credentials flow
func getO11yToken() string {
	o11y := &AppConfig.O11yConfig

	// Create HTTP client with timeout
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Prepare form data
	data := url.Values{}
	data.Set("grant_type", "client_credentials")
	data.Set("client_id", o11y.Auth0ClientId)
	data.Set("client_secret", o11y.Auth0ClientSecret)
	data.Set("audience", o11y.Auth0Audience)

	// Create POST request
	req, err := http.NewRequest("POST", o11y.Auth0TokenUrl, strings.NewReader(data.Encode()))
	if err != nil {
		log2.PlainInfo("Failed to create request", zap.Error(err))
	}

	// Set headers
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	log2.PlainInfo("Getting Auth0 token...", zap.String("url", o11y.Auth0TokenUrl))

	// Send request
	resp, err := client.Do(req)
	if err != nil {
		log2.PlainError("Failed to get Auth0 token", zap.Error(err))
	}
	defer resp.Body.Close()

	// Read response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log2.PlainInfo("Failed to get Auth0 token", zap.Error(err))
	}

	// Check status code
	if resp.StatusCode != http.StatusOK {
		log2.PlainError("Auth0 token request failed",
			zap.Int("statusCode", resp.StatusCode),
			zap.String("response", string(body)))
		return ""
	}

	// Parse JSON response and extract access_token
	var tokenResp map[string]interface{}
	if err := json.Unmarshal(body, &tokenResp); err != nil {
		return ""
	}

	accessToken, ok := tokenResp["access_token"].(string)
	if !ok {
		return ""
	}

	log2.PlainInfo("Auth0 token obtained successfully")

	return accessToken
}

func GetO11yPrometheusUrl(reqURL string, clusterId string, headerMap map[string]string) (string, error) {
	// Create HTTP client with timeout
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Create GET request
	req, err := http.NewRequest("GET", reqURL, nil)
	if err != nil {
		return "", fmt.Errorf("failed to create request: %w", err)
	}
	token := getO11yToken()
	if token == "" {
		return "", fmt.Errorf("failed to get O11y token")
	}
	// Set headers with Bearer token
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	for k, v := range headerMap {
		req.Header.Set(k, v)
	}

	log2.PlainInfo("Querying cluster information...", zap.String("url", reqURL))

	// Send request
	resp, err := client.Do(req)
	if err != nil {
		log2.PlainError("Failed to query clusters", zap.Error(err))
		return "", fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	// Read response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("failed to read response: %w", err)
	}

	// Check status code
	if resp.StatusCode != http.StatusOK {
		log2.PlainError("Query clusters request failed",
			zap.Int("statusCode", resp.StatusCode),
			zap.String("response", string(body)))
		return "", fmt.Errorf("query failed with status %d: %s", resp.StatusCode, string(body))
	}

	// Parse JSON response
	var response map[string]interface{}
	if err := json.Unmarshal(body, &response); err != nil {
		return "", fmt.Errorf("failed to unmarshal response: %w", err)
	}

	// Check response code
	code, ok := response["code"].(float64)
	if !ok || code != 0 {
		message, _ := response["message"].(string)
		log2.PlainError("Query failed", zap.String("message", message))
		return "", fmt.Errorf("query failed: %s", message)
	}

	// Extract clusters from data
	data, ok := response["data"].(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("data field not found or invalid")
	}

	clusters, ok := data["clusters"].([]interface{})
	if !ok || len(clusters) == 0 {
		log2.PlainError("No clusters found in o11y api :v1/clusters/")
		return "", fmt.Errorf("no clusters found")
	}

	var targetCluster map[string]interface{}
	if clusterId != "" {
		for _, cluster := range clusters {
			if clusterMap, ok := cluster.(map[string]interface{}); ok {
				applicationID := getStringValue(clusterMap, "applicationID")
				if applicationID == clusterId {
					targetCluster = clusterMap
					log2.PlainInfo("Found matching cluster by applicationID",
						zap.String("clusterId", clusterId),
						zap.String("applicationID", applicationID))
					break
				}
			}
		}
	}

	// If no matching cluster found, use the first one
	if targetCluster == nil {
		if clusterId != "" {
			log2.PlainInfo("Warning: No matching cluster found by applicationID, using first cluster",
				zap.String("clusterId", clusterId))
		}
		if clusterMap, ok := clusters[0].(map[string]interface{}); ok {
			targetCluster = clusterMap
		}
	}

	if targetCluster == nil {
		log2.PlainError("No cluster information found")
		return "", fmt.Errorf("no cluster information found")
	}

	// Extract internalReadUri
	internalReadUri, ok := targetCluster["internalReadUri"].(string)
	if !ok || internalReadUri == "" {
		log2.PlainError("internalReadUri not found in cluster")
		return "", fmt.Errorf("internalReadUri not found in cluster")
	}

	log2.PlainInfo("Cluster information obtained successfully",
		zap.String("clusterId", getStringValue(targetCluster, "id")),
		zap.String("applicationID", getStringValue(targetCluster, "applicationID")),
		zap.String("internalReadUri", internalReadUri))

	// Remove /api/v1 from the URL and assign to prometheusURL
	prometheusURL := strings.ReplaceAll(internalReadUri, "/api/v1/", "")

	// Ensure the URL has a protocol scheme (http:// or https://)
	if !strings.HasPrefix(prometheusURL, "http://") && !strings.HasPrefix(prometheusURL, "https://") {
		// Default to https:// if no protocol is specified
		prometheusURL = "https://" + prometheusURL
		log2.PlainInfo("Added https:// protocol prefix to prometheusURL", zap.String("prometheusURL", prometheusURL))
	}
	log2.PlainInfo("GetO11yPrometheusUrl", zap.String("prometheusURL", prometheusURL))
	return prometheusURL, nil
}

// Helper function to safely get string value from map
func getStringValue(m map[string]interface{}, key string) string {
	if val, ok := m[key].(string); ok {
		return val
	}
	return ""
}
