package main

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/google/uuid"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/api/healthcheck"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/o11y"
	"github.com/pingcap-inc/clinic/pkg/o11y/urls"
)

const (
	defaultEndpoint = "https://clinic.pingcap.com/clinic-internal/healthcheck/sql"
)

var (
	endpoint           string
	clusterID          string
	reportID           string
	deployType         string
	projectID          string
	provider           string
	region             string
	tenantID           string
	topN               int
	verbose            bool
	o11yEnabled        bool
	o11yEndpoint       string
	o11yClientID       string
	o11ySecret         string
	o11yDataServiceTPL string
)

func init() {
	flag.StringVar(&endpoint, "endpoint", defaultEndpoint, "Health check endpoint URL")
	flag.StringVar(&clusterID, "cluster-id", "1379661944646413035", "Cluster ID")
	flag.StringVar(&reportID, "report-id", "", "Report ID (auto-generated if empty)")
	flag.StringVar(&deployType, "deploy-type", "dedicated", "Deploy type: dedicated, serverless, etc.")
	flag.StringVar(&projectID, "project-id", "1372813089454526833", "Project ID")
	flag.StringVar(&provider, "provider", "aws", "Cloud provider")
	flag.StringVar(&region, "region", "us-west-2", "Cloud region")
	flag.StringVar(&tenantID, "tenant-id", "1372813089209213994", "Tenant ID")
	flag.IntVar(&topN, "topn", 3, "Top N value")
	flag.BoolVar(&verbose, "v", false, "Verbose output")

	// O11y configuration for ClusterURLsResolver
	flag.BoolVar(&o11yEnabled, "o11y", false, "Enable o11y client to use ClusterURLsResolver (recommended)")
	flag.StringVar(&o11yEndpoint, "o11y-endpoint", "", "O11y GS endpoint (required if -o11y is set)")
	flag.StringVar(&o11yDataServiceTPL, "o11y-ds-tpl", "https://www.ds.%s.aws.observability.tidbcloud.com", "O11y Data Service endpoint template")
	flag.StringVar(&o11yClientID, "o11y-client-id", "", "O11y client ID")
	flag.StringVar(&o11ySecret, "o11y-secret", "", "O11y client secret")
}

func main() {
	flag.Parse()

	// Generate report ID if not provided
	if reportID == "" {
		reportID = uuid.NewString()
		log.Info("Generated report ID", zap.String("report_id", reportID))
	}

	// Build request
	req, err := buildRequest()
	if err != nil {
		log.Fatal("Failed to build request", zap.Error(err))
	}

	if verbose {
		printRequest(req)
	}

	// Send request
	resp, err := sendRequest(req)
	if err != nil {
		log.Fatal("Failed to send request", zap.Error(err))
	}

	// Print response
	printResponse(resp)
}

func buildRequest() (*healthcheck.HealthCheckJobRequest, error) {
	now := time.Now().UTC()
	startTime := now.Add(-24 * time.Hour).Format(time.RFC3339)
	endTime := now.Format(time.RFC3339)

	// Build cluster URLs using ClusterURLsResolver if o11y is enabled
	clusterURLs, err := buildClusterURLs()
	if err != nil {
		return nil, fmt.Errorf("failed to build cluster URLs: %w", err)
	}

	return &healthcheck.HealthCheckJobRequest{
		ClusterId:  clusterID,
		ReportId:   reportID,
		DeployType: deployType,
		ProjectId:  projectID,
		Provider:   provider,
		Region:     region,
		TenantId:   tenantID,
		TimeRange: healthcheck.HealthCheckTimeRange{
			Start: startTime,
			End:   endTime,
		},
		ClusterUrls: clusterURLs,
	}, nil
}

// buildClusterURLs constructs cluster URLs
// If o11y is enabled, uses ClusterURLsResolver to get real URLs
// Otherwise returns error (manual URL construction is not supported)
func buildClusterURLs() (healthcheck.HealthCheckClusterUrls, error) {
	if !o11yEnabled {
		return healthcheck.HealthCheckClusterUrls{}, fmt.Errorf("o11y must be enabled to use ClusterURLsResolver. Please provide -o11y flag and -o11y-endpoint")
	}

	if o11yEndpoint == "" {
		return healthcheck.HealthCheckClusterUrls{}, fmt.Errorf("o11y-endpoint is required when o11y is enabled")
	}

	// Initialize o11y client
	o11yCli, err := o11y.New(
		o11yEndpoint,
		o11yDataServiceTPL,
		o11yClientID,
		o11ySecret,
		"prod", // env
		o11y.TimeoutOption(30*time.Second),
	)
	if err != nil {
		return healthcheck.HealthCheckClusterUrls{}, fmt.Errorf("failed to create o11y client: %w", err)
	}

	// Create ClusterURLsResolver
	resolver := urls.NewClusterURLsResolver(o11yCli)
	if !resolver.IsEnabled() {
		return healthcheck.HealthCheckClusterUrls{}, fmt.Errorf("ClusterURLsResolver is not enabled")
	}

	// Build cluster object
	cluster := &db.Cluster{
		ClusterID:  clusterID,
		DeployType: deployType,
		Provider:   provider,
		Region:     region,
		ProjectID:  projectID,
		Organization: db.Organization{
			DbaasOrgID: parseUint(tenantID),
		},
	}

	// Resolve URLs
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	clusterURLs, err := resolver.Resolve(ctx, cluster)
	if err != nil {
		return healthcheck.HealthCheckClusterUrls{}, fmt.Errorf("failed to resolve cluster URLs: %w", err)
	}

	log.Info("Resolved cluster URLs using ClusterURLsResolver",
		zap.String("cluster_id", clusterID),
		zap.String("cluster_metrics_url", clusterURLs.ClusterMetricsURL),
		zap.String("dataplane_metrics_url", clusterURLs.DataPlaneMetricsURL),
		zap.String("slowquery_s3_url", clusterURLs.SlowQueryS3URL),
	)

	return healthcheck.HealthCheckClusterUrls{
		ClusterMetricsUrl:   clusterURLs.ClusterMetricsURL,
		DataPlaneMetricsUrl: clusterURLs.DataPlaneMetricsURL,
		SlowQueryS3Url:      clusterURLs.SlowQueryS3URL,
	}, nil
}

func parseUint(s string) uint {
	var result uint
	_, _ = fmt.Sscanf(s, "%d", &result)
	return result
}

func printRequest(req *healthcheck.HealthCheckJobRequest) {
	jsonData, err := json.MarshalIndent(req, "", "  ")
	if err != nil {
		log.Error("Failed to marshal request", zap.Error(err))
		return
	}

	fmt.Println("========== REQUEST ==========")
	fmt.Printf("Endpoint: %s\n", endpoint)
	fmt.Printf("Method: POST\n")
	fmt.Printf("Headers:\n")
	fmt.Printf("  Content-Type: application/json\n")
	fmt.Printf("Body:\n%s\n", string(jsonData))
	fmt.Println("=============================")
}

func sendRequest(req *healthcheck.HealthCheckJobRequest) (*http.Response, error) {
	jsonData, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("marshal request: %w", err)
	}

	httpReq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	httpReq.Header.Set("Content-Type", "application/json")

	client := &http.Client{
		Timeout: 60 * time.Second,
	}

	log.Info("Sending request...",
		zap.String("endpoint", endpoint),
		zap.String("cluster_id", clusterID),
		zap.String("report_id", reportID),
	)

	return client.Do(httpReq)
}

func printResponse(resp *http.Response) {
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("Failed to read response body", zap.Error(err))
		return
	}

	fmt.Println("\n========== RESPONSE ==========")
	fmt.Printf("Status: %d %s\n", resp.StatusCode, resp.Status)
	fmt.Printf("Headers:\n")
	for key, values := range resp.Header {
		for _, value := range values {
			fmt.Printf("  %s: %s\n", key, value)
		}
	}
	fmt.Printf("Body:\n%s\n", string(body))
	fmt.Println("==============================")

	// Save response to file if successful
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		filename := fmt.Sprintf("healthcheck_response_%s.json", reportID)
		if err := os.WriteFile(filename, body, 0o644); err != nil {
			log.Error("Failed to save response", zap.Error(err))
		} else {
			log.Info("Response saved to file", zap.String("filename", filename))
		}
	}

	// Exit with error code if request failed
	if resp.StatusCode >= 400 {
		os.Exit(1)
	}
}
