package prom

import (
	"fmt"
	"io"
	"math"
	"strings"

	"github.com/prometheus/common/model"
	"github.com/tidbcloud/metrics-integration/pipeline/central/entity"
)

var (
	_ Metric = (*SingleMetric)(nil)
	_ Metric = (*VecMetric)(nil)

	escaper       = strings.NewReplacer("\\", `\\`, "\n", `\n`)
	quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)

	VecMetricElementBucket = "_bucket"
	VecMetricElementCount  = "_count"
	VecMetricElementSum    = "_sum"
)

type Metric interface {
	// TODO: extract to adapt different encoders
	EncodePrometheusFormat(w io.Writer) error
}

type SingleMetric struct {
	Name  string
	Help  string
	Type  string
	Value model.SampleValue
}

func (m *SingleMetric) EncodePrometheusFormat(w io.Writer) error {
	_, err := fmt.Fprintf(w, "# HELP %s %s\n", m.Name, m.Help)
	if err != nil {
		return err
	}
	_, err = fmt.Fprintf(w, "# TYPE %s %s\n", m.Name, m.Type)
	if err != nil {
		return err
	}
	_, err = fmt.Fprintf(w, "%s %s\n", m.Name, m.Value.String())
	return err
}

type VecMetric struct {
	Name      string
	Help      string
	Type      string
	Metrics   []*VecMetricElement
	Timestamp int64
	// ServicePlan is the cluster service plan (e.g. "Premium", "Dedicated").
	// For NextGen plans (Premium/Essential V2/Essential/Starter), project_id and project_name
	// labels are omitted from the output because the concept does not exist.
	ServicePlan string
}

func (m *VecMetric) EncodePrometheusFormat(w io.Writer) error {
	sp := entity.ServicePlan(m.ServicePlan)
	elementsToEncode := make([]*VecMetricElement, 0, len(m.Metrics))
	for _, me := range m.Metrics {
		if me == nil {
			continue
		}
		var element *VecMetricElement
		switch {
		case sp.IsInstanceLevel():
			// NextGen: rename cluster→instance and drop project/node labels.
			element = me.asInstanceLevelElement()
		default:
			// Dedicated: keep all labels as-is
			element = me
		}
		elementsToEncode = append(elementsToEncode, element)
	}

	if _, err := fmt.Fprintf(w, "# HELP %s %s\n", m.Name, m.Help); err != nil {
		return err
	}
	if _, err := fmt.Fprintf(w, "# TYPE %s %s\n", m.Name, m.Type); err != nil {
		return err
	}

	// Preserve the legacy Dedicated exposition format so existing consumers
	// don't observe comment-only diffs. NextGen plans keep the timestamp line
	// and suppress empty metric families.
	if sp.IsInstanceLevel() {
		if len(elementsToEncode) == 0 {
			return nil
		}
		if _, err := fmt.Fprintf(w, "# TIMESTAMP %s %d\n", m.Name, m.Timestamp); err != nil {
			return err
		}
	}

	for _, element := range elementsToEncode {
		if _, err := io.WriteString(w, m.Name); err != nil {
			return err
		}
		if err := element.EncodePrometheusFormat(w); err != nil {
			return err
		}
		if _, err := io.WriteString(w, "\n"); err != nil {
			return err
		}
	}
	return nil
}

type MetricLabel struct {
	Name  string
	Value string
}

func (label *MetricLabel) EncodePrometheusFormat(w io.Writer) error {
	if _, err := io.WriteString(w, label.Name); err != nil {
		return err
	}
	if _, err := io.WriteString(w, `="`); err != nil {
		return err
	}
	if _, err := writeEscapedString(w, label.Value, true); err != nil {
		return err
	}
	if _, err := io.WriteString(w, `"`); err != nil {
		return err
	}
	return nil
}

type VecMetricElement struct {
	Type   string // NOTE: this field should only be used for histogram(sum/count/bucket), leave it empty for other types
	Labels []MetricLabel
	Value  model.SampleValue
}

// asInstanceLevelElement applies instance-level label transforms:
//   - Delete: instance, component (node-level), project_id, project_name
//   - Rename: cluster_id → instance_id, cluster_name → instance_name
func (v *VecMetricElement) asInstanceLevelElement() *VecMetricElement {
	filtered := make([]MetricLabel, 0, len(v.Labels))
	for _, l := range v.Labels {
		switch l.Name {
		case "project_id", "project_name", "instance", "component":
			continue
		case "cluster_id":
			filtered = append(filtered, MetricLabel{Name: "instance_id", Value: l.Value})
		case "cluster_name":
			filtered = append(filtered, MetricLabel{Name: "instance_name", Value: l.Value})
		default:
			filtered = append(filtered, l)
		}
	}
	return &VecMetricElement{Type: v.Type, Labels: filtered, Value: v.Value}
}

func (v *VecMetricElement) EncodePrometheusFormat(w io.Writer) error {
	if _, err := io.WriteString(w, v.Type); err != nil {
		return err
	}

	if _, err := io.WriteString(w, "{"); err != nil {
		return err
	}
	for idx, l := range v.Labels {
		if idx > 0 {
			if _, err := io.WriteString(w, ","); err != nil {
				return err
			}
		}
		if err := l.EncodePrometheusFormat(w); err != nil {
			return err
		}
	}
	if _, err := fmt.Fprintf(w, "} "); err != nil {
		return err
	}
	if _, err := writeFloat(w, v.Value); err != nil {
		return err
	}
	return nil
}

// isInstanceLevelPlan reports whether the given service plan uses instance-level hierarchy.
func isInstanceLevelPlan(servicePlan string) bool {
	return entity.ServicePlan(servicePlan).IsInstanceLevel()
}

func writeEscapedString(w io.Writer, v string, includeDoubleQuote bool) (int, error) {
	if includeDoubleQuote {
		return quotedEscaper.WriteString(w, v)
	}
	return escaper.WriteString(w, v)
}

func writeFloat(w io.Writer, f model.SampleValue) (int, error) {
	f2 := float64(f)
	switch {
	case f2 == 1:
		return io.WriteString(w, "1")
	case f2 == 0:
		return io.WriteString(w, "0")
	case f2 == -1:
		return io.WriteString(w, "-1")
	case math.IsNaN(f2):
		return io.WriteString(w, "NaN")
	case math.IsInf(f2, +1):
		return io.WriteString(w, "+Inf")
	case math.IsInf(f2, -1):
		return io.WriteString(w, "-Inf")
	default:
		return io.WriteString(w, f.String())
	}
}
