package service

import (
	"context"
	"encoding/json"
	"errors"
	"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"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	thirdapio11y "github.com/pingcap-inc/clinic/clinic_data_service/pkg/thirdapi/o11y"
	clinictracing "github.com/pingcap-inc/clinic/clinic_data_service/pkg/tracing"
	"go.uber.org/zap"
)

const (
	serverlessParentDeployType = "serverless_parent"
	devtierBizType             = "devtier"
)

var (
	errInvalidControlPlaneInfo        = errors.New("invalid control_plane_info")
	errServerlessParentClusterMissing = errors.New("serverless parent o11y cluster not found")
)

// ServerlessParentControlPlaneInfo is the parsed form of
// projectID/vendor-region/applicationID used for parent/control-plane routing.
type ServerlessParentControlPlaneInfo struct {
	ProjectID     string
	Vendor        string
	Region        string
	ApplicationID string
}

type serverlessParentControlPlaneInfo = ServerlessParentControlPlaneInfo

type serverlessParentO11yResolver interface {
	GetCluster(ctx context.Context, req thirdapio11y.QueryClusterReq) (thirdapio11y.ClusterEntity, error)
}

type defaultServerlessParentO11yResolver struct{}

func (defaultServerlessParentO11yResolver) GetCluster(ctx context.Context, req thirdapio11y.QueryClusterReq) (thirdapio11y.ClusterEntity, error) {
	return thirdapio11y.GetCluster(ctx, req)
}

// ServerlessParentMetricsService handles parent/control-plane metrics queries for Starter/Essential clusters.
type ServerlessParentMetricsService struct {
	o11yResolver serverlessParentO11yResolver
	httpClient   *http.Client
	env          string
}

// NewServerlessParentMetricsService creates a metrics service backed by O11y parent/control-plane clusters.
func NewServerlessParentMetricsService(cfg *config.DataProxyConfig) *ServerlessParentMetricsService {
	return &ServerlessParentMetricsService{
		o11yResolver: defaultServerlessParentO11yResolver{},
		httpClient: &http.Client{
			Timeout:   2 * time.Minute,
			Transport: clinictracing.NewHTTPTransport(nil),
		},
		env: serverlessParentMetricsEnv(cfg),
	}
}

func serverlessParentMetricsEnv(cfg *config.DataProxyConfig) string {
	env := strings.TrimSpace(config.AppConfig.Env)
	if env == "" && cfg != nil {
		env = strings.TrimSpace(cfg.LokiProxy.Env)
	}
	if env == "" {
		env = "dev"
	}
	return env
}

func ParseServerlessParentControlPlaneInfo(raw string) (ServerlessParentControlPlaneInfo, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return ServerlessParentControlPlaneInfo{}, fmt.Errorf("%w: value is required", errInvalidControlPlaneInfo)
	}

	parts := strings.Split(raw, "/")
	if len(parts) != 3 {
		return ServerlessParentControlPlaneInfo{}, fmt.Errorf("%w: expected projectID/vendor-region/applicationID", errInvalidControlPlaneInfo)
	}

	projectID := strings.TrimSpace(parts[0])
	vendorRegion := strings.TrimSpace(parts[1])
	applicationID := strings.TrimSpace(parts[2])
	vendor, region, ok := strings.Cut(vendorRegion, "-")
	vendor = strings.TrimSpace(vendor)
	region = strings.TrimSpace(region)
	if !ok || projectID == "" || vendor == "" || region == "" || applicationID == "" {
		return ServerlessParentControlPlaneInfo{}, fmt.Errorf("%w: expected projectID/vendor-region/applicationID", errInvalidControlPlaneInfo)
	}

	return ServerlessParentControlPlaneInfo{
		ProjectID:     projectID,
		Vendor:        vendor,
		Region:        region,
		ApplicationID: applicationID,
	}, nil
}

func parseServerlessParentControlPlaneInfo(raw string) (serverlessParentControlPlaneInfo, error) {
	return ParseServerlessParentControlPlaneInfo(raw)
}

func (s *ServerlessParentMetricsService) parentBizType() string {
	env := strings.TrimSpace(s.env)
	if env == "" {
		env = "dev"
	}
	return fmt.Sprintf("%s-%s", env, devtierBizType)
}

func (s *ServerlessParentMetricsService) resolvePrometheusBaseURL(ctx context.Context, controlPlaneInfo string) (string, serverlessParentControlPlaneInfo, error) {
	info, err := parseServerlessParentControlPlaneInfo(controlPlaneInfo)
	if err != nil {
		return "", serverlessParentControlPlaneInfo{}, err
	}

	bizType := s.parentBizType()
	cluster, err := s.o11yResolver.GetCluster(ctx, thirdapio11y.QueryClusterReq{
		Vendor:        info.Vendor,
		Region:        info.Region,
		BizType:       bizType,
		TenantID:      bizType,
		ApplicationID: info.ApplicationID,
		ProjectID:     info.ProjectID,
	})
	if err != nil {
		return "", info, fmt.Errorf("query serverless parent o11y cluster: %w", err)
	}
	if strings.TrimSpace(cluster.InternalReadUri) == "" {
		return "", info, fmt.Errorf("%w: %s", errServerlessParentClusterMissing, controlPlaneInfo)
	}
	return normalizePrometheusAPIURL(cluster.InternalReadUri), info, nil
}

func normalizePrometheusAPIURL(rawURL string) string {
	rawURL = strings.TrimSpace(normalizePrometheusURL(rawURL))
	trimmed := strings.TrimRight(rawURL, "/")
	if strings.HasSuffix(trimmed, "/api/v1") {
		return trimmed
	}
	return trimmed + "/api/v1"
}

func serverlessParentMetricsErrorType(err error) string {
	switch {
	case errors.Is(err, errInvalidControlPlaneInfo):
		return "bad_data"
	case errors.Is(err, errServerlessParentClusterMissing):
		return "not_found"
	default:
		return "internal"
	}
}

func withServerlessParentRouteInfo(resp *dto.PrometheusResponse, info serverlessParentControlPlaneInfo) *dto.PrometheusResponse {
	if resp == nil {
		return nil
	}
	resp.DeployTypeV2 = serverlessParentDeployType
	resp.Provider = info.Vendor
	resp.Region = info.Region
	return resp
}

// InstantQuery executes an instant parent/control-plane metrics query.
func (s *ServerlessParentMetricsService) InstantQuery(ctx context.Context, req *dto.ServerlessParentInstantQueryRequest) *dto.PrometheusResponse {
	if req.Query == "" {
		return &dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "bad_data",
			Error:     "query parameter is required",
		}
	}

	baseURL, info, err := s.resolvePrometheusBaseURL(ctx, req.ControlPlaneInfo)
	if err != nil {
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: serverlessParentMetricsErrorType(err),
			Error:     err.Error(),
		}, info)
	}

	params := url.Values{}
	params.Set("query", req.Query)
	if !req.Time.IsZero() {
		params.Set("time", fmt.Sprintf("%d", req.Time.Unix()))
	}
	if req.Timeout != "" {
		params.Set("timeout", req.Timeout)
	}

	return s.executeQueryWithValues(ctx, strings.TrimRight(baseURL, "/")+"/query", params, info)
}

// RangeQuery executes a parent/control-plane range metrics query.
func (s *ServerlessParentMetricsService) RangeQuery(ctx context.Context, req *dto.ServerlessParentRangeQueryRequest) *dto.PrometheusResponse {
	if req.Query == "" {
		return &dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "bad_data",
			Error:     "query parameter is required",
		}
	}
	if req.Start.IsZero() || req.End.IsZero() {
		return &dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "bad_data",
			Error:     "start and end parameters are required",
		}
	}
	if req.End.Before(req.Start) {
		return &dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "bad_data",
			Error:     "end time must not be before start time",
		}
	}

	step := req.Step
	if step == 0 {
		step = 15 * time.Second
	}

	baseURL, info, err := s.resolvePrometheusBaseURL(ctx, req.ControlPlaneInfo)
	if err != nil {
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: serverlessParentMetricsErrorType(err),
			Error:     err.Error(),
		}, info)
	}

	params := url.Values{}
	params.Set("query", req.Query)
	params.Set("start", fmt.Sprintf("%d", req.Start.Unix()))
	params.Set("end", fmt.Sprintf("%d", req.End.Unix()))
	params.Set("step", fmt.Sprintf("%.0fs", step.Seconds()))
	if req.Timeout != "" {
		params.Set("timeout", req.Timeout)
	}

	return s.executeQueryWithValues(ctx, strings.TrimRight(baseURL, "/")+"/query_range", params, info)
}

// Series executes a parent/control-plane series query.
func (s *ServerlessParentMetricsService) Series(ctx context.Context, req *dto.ServerlessParentSeriesRequest) *dto.PrometheusResponse {
	if len(req.Match) == 0 {
		return &dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "bad_data",
			Error:     "match[] parameter is required",
		}
	}

	baseURL, info, err := s.resolvePrometheusBaseURL(ctx, req.ControlPlaneInfo)
	if err != nil {
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: serverlessParentMetricsErrorType(err),
			Error:     err.Error(),
		}, info)
	}

	params := url.Values{}
	for _, m := range req.Match {
		params.Add("match[]", m)
	}
	if !req.Start.IsZero() {
		params.Set("start", fmt.Sprintf("%d", req.Start.Unix()))
	}
	if !req.End.IsZero() {
		params.Set("end", fmt.Sprintf("%d", req.End.Unix()))
	}

	return s.executeQueryWithValues(ctx, strings.TrimRight(baseURL, "/")+"/series", params, info)
}

func (s *ServerlessParentMetricsService) executeQueryWithValues(ctx context.Context, queryURL string, params url.Values, info serverlessParentControlPlaneInfo) *dto.PrometheusResponse {
	u, err := url.Parse(queryURL)
	if err != nil {
		log.Error(ctx, "failed to parse serverless parent metrics query URL",
			zap.String("queryURL", queryURL),
			zap.Error(err))
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "internal",
			Error:     fmt.Sprintf("failed to parse query URL: %v", err),
		}, info)
	}

	u.RawQuery = params.Encode()
	fullURL := u.String()
	log.Info(ctx, "executing serverless parent metrics query",
		zap.String("upstreamHost", u.Host),
		zap.String("upstreamPath", u.Path))

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil)
	if err != nil {
		log.Error(ctx, "failed to create serverless parent metrics request",
			zap.Error(err))
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "internal",
			Error:     fmt.Sprintf("failed to create request: %v", err),
		}, info)
	}

	resp, err := s.httpClient.Do(req)
	if err != nil {
		log.Error(ctx, "serverless parent metrics request failed",
			zap.String("errorType", fmt.Sprintf("%T", err)))
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "execution",
			Error:     fmt.Sprintf("request failed: %v", err),
		}, info)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error(ctx, "failed to read serverless parent metrics response",
			zap.Int("statusCode", resp.StatusCode),
			zap.Error(err))
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "internal",
			Error:     fmt.Sprintf("failed to read response: %v", err),
		}, info)
	}

	if resp.StatusCode != http.StatusOK {
		upstreamError, diagnostics := newMetricsUpstreamErrorResponse(resp.StatusCode, body)
		log.Error(ctx, "serverless parent metrics upstream returned non-200 status",
			zap.Int("statusCode", resp.StatusCode),
			zap.Int("bodyLength", len(body)),
			zap.String("errorCode", diagnostics.Code),
			zap.String("upstreamErrorType", diagnostics.UpstreamErrorType),
			zap.Bool("sanitized", diagnostics.Sanitized))
		return withServerlessParentRouteInfo(upstreamError, info)
	}

	var promResp dto.PrometheusResponse
	if err := json.Unmarshal(body, &promResp); err != nil {
		log.Error(ctx, "failed to parse serverless parent metrics response",
			zap.Int("statusCode", resp.StatusCode),
			zap.Int("bodyLength", len(body)),
			zap.Error(err))
		return withServerlessParentRouteInfo(&dto.PrometheusResponse{
			Status:    "error",
			ErrorType: "internal",
			Error:     "failed to parse metrics upstream response",
		}, info)
	}

	if promResp.Status == "error" {
		log.Error(ctx, "serverless parent metrics upstream returned error",
			zap.String("errorType", safeUpstreamErrorType(promResp.ErrorType)))
	} else {
		log.Info(ctx, "serverless parent metrics query successful",
			zap.String("status", promResp.Status))
	}

	return withServerlessParentRouteInfo(&promResp, info)
}
