// Package clinic_internal_api provides a client for accessing Clinic internal APIs
package clinic_internal_api

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

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	clinictracing "github.com/pingcap-inc/clinic/clinic_data_service/pkg/tracing"
	"go.uber.org/zap"
)

const DEFAULT_CLINIC_API_BASE = "http://clinic-internal-api:80"

// Client is a client for Clinic internal APIs
type Client struct {
	baseURL    string
	httpClient *http.Client
}

// NewClient creates a new Clinic internal API client
func NewClient(baseURL string) *Client {
	if baseURL == "" {
		baseURL = DEFAULT_CLINIC_API_BASE
	}
	return &Client{
		baseURL: baseURL,
		httpClient: &http.Client{
			Timeout:   10 * time.Second,
			Transport: clinictracing.NewHTTPTransport(nil),
		},
	}
}

// NewClientWithTimeout creates a new client with custom timeout
func NewClientWithTimeout(baseURL string, timeout time.Duration) *Client {
	if baseURL == "" {
		baseURL = DEFAULT_CLINIC_API_BASE
	}
	return &Client{
		baseURL: baseURL,
		httpClient: &http.Client{
			Timeout:   timeout,
			Transport: clinictracing.NewHTTPTransport(nil),
		},
	}
}

// ClusterDataURLs represents cluster data URLs and filters
type ClusterDataURLs struct {
	ClusterName         string `json:"clusterName"`
	ClusterID           string `json:"clusterId"`
	OrgID               string `json:"orgId"`
	ProjectID           string `json:"projectId"`
	Region              string `json:"region"`
	DeployType          string `json:"deployType"`
	DeployTypeV2        string `json:"deployTypeV2"`
	Provider            string `json:"provider"`
	CreateTime          int64  `json:"createTime"`
	Status              string `json:"status"`
	ClusterMetricsURL   string `json:"clusterMetricsUrl,omitempty"`
	SlowQueryS3URL      string `json:"slowQueryS3Url,omitempty"`
	StatementsS3URL     string `json:"statementsS3Url,omitempty"`
	TopSQLS3URL         string `json:"topsqlS3Url,omitempty"`
	LogStatsS3URL       string `json:"logStatsS3Url,omitempty"`
	DataPlaneMetricsURL string `json:"dataPlaneMetricsUrl,omitempty"`
	LokiURL             string `json:"lokiUrl,omitempty"`
	ParentID            string `json:"parentId,omitempty"` // nextgen physical cluster id, for Premium/Byoc dataplane metrics
	ClinicTenantID      string `json:"clinicTenantId,omitempty"`
}

// GetClusterDataURLs fetches cluster data URLs and filters from Clinic API
// GET /internal/clusters/{cluster_id}/cluster-dataurls
func (c *Client) GetClusterDataURLs(ctx context.Context, clusterID string) (*ClusterDataURLs, error) {
	if clusterID == "" {
		log.Warn(ctx, "cluster_id is required")
		return nil, fmt.Errorf("cluster_id is required")
	}

	url := fmt.Sprintf("%s/clinic/api/v1/internal/clusters/%s/cluster-dataurls", c.baseURL, clusterID)
	log.Info(ctx, "Fetching cluster data URLs", zap.String("cluster_id", clusterID), zap.String("url", url))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		log.Error(ctx, "failed to create request", zap.Error(err))
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		log.Error(ctx, "failed to execute request", zap.Error(err))
		return nil, fmt.Errorf("failed to execute request: %w", err)
	}
	defer resp.Body.Close()

	log.Info(ctx, "Received response", zap.Int("status_code", resp.StatusCode))

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("clinic API returned status %d: %s", resp.StatusCode, string(body))
	}

	var result ClusterDataURLs
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}
	log.Info(ctx, "Successfully decoded cluster data URLs form clinic-api", zap.String("cluster_id", clusterID), zap.Any("cluster data URLs", result))

	return &result, nil
}

// GetCanonicalClusterDataURLs fetches the same target as GetClusterDataURLs
// with bounded, typed error handling for public canonical adapters. The legacy
// method above intentionally keeps its established error contract.
func (c *Client) GetCanonicalClusterDataURLs(ctx context.Context, clusterID string) (*ClusterDataURLs, error) {
	if clusterID == "" {
		return nil, fmt.Errorf("cluster_id is required")
	}

	requestURL := fmt.Sprintf("%s/clinic/api/v1/internal/clusters/%s/cluster-dataurls", c.baseURL, clusterID)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
	if err != nil {
		return nil, fmt.Errorf("create canonical cluster target request: %w", err)
	}

	canonicalHTTPClient := *c.httpClient
	canonicalHTTPClient.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
		return http.ErrUseLastResponse
	}
	resp, err := canonicalHTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("call canonical cluster target API: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, maxResourceResponseBytes))
		return nil, &UpstreamError{StatusCode: resp.StatusCode}
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, maxResourceResponseBytes+1))
	if err != nil {
		return nil, fmt.Errorf("read canonical cluster target response: %w", err)
	}
	if len(body) > maxResourceResponseBytes {
		return nil, fmt.Errorf("canonical cluster target response exceeds %d bytes", maxResourceResponseBytes)
	}
	var result ClusterDataURLs
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, fmt.Errorf("decode canonical cluster target response: %w", err)
	}
	return &result, nil
}

// SetHTTPClient allows customizing the HTTP client (useful for testing)
func (c *Client) SetHTTPClient(client *http.Client) {
	c.httpClient = client
}

// GetBaseURL returns the base URL of the client
func (c *Client) GetBaseURL() string {
	return c.baseURL
}
