package httpapi

import (
	"context"
	"fmt"
	"time"

	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/httpapi/gen"
	"github.com/jackc/pgx/v5/pgtype"
)

// ─── Metrics ───────────────────────────────────────────────────────────

func (s *Server) QueryMetrics(ctx context.Context, req gen.QueryMetricsRequestObject) (gen.QueryMetricsResponseObject, error) {
	if req.Params.EntityId == nil || *req.Params.EntityId == "" {
		return nil, fmt.Errorf("%w: entity_id is required", domain.ErrInvalidInput)
	}
	entityID, err := s.resolveEntityID(ctx, *req.Params.EntityId)
	if err != nil {
		return nil, err
	}

	from := time.Now().Add(-24 * time.Hour)
	if req.Params.From != nil {
		from = *req.Params.From
	}
	to := time.Now()
	if req.Params.To != nil {
		to = *req.Params.To
	}

	var metricNames []string
	if req.Params.Metric != nil && len(*req.Params.Metric) > 0 {
		metricNames = *req.Params.Metric
	} else {
		// metric omitted: report every metric recorded for this entity in range.
		rows, err := s.pool.Query(ctx, `
			SELECT DISTINCT metric FROM metric_samples
			WHERE entity_id = $1 AND ts >= $2 AND ts <= $3
			ORDER BY metric`, entityID, from, to)
		if err != nil {
			return nil, err
		}
		for rows.Next() {
			var name string
			if err := rows.Scan(&name); err != nil {
				rows.Close()
				return nil, err
			}
			metricNames = append(metricNames, name)
		}
		if err := rows.Err(); err != nil {
			return nil, err
		}
	}

	items := []gen.MetricSeries{}
	for _, metricName := range metricNames {
		series := gen.MetricSeries{
			EntityId: entityID.String(),
			Metric:   metricName,
			Rollup:   gen.MetricSeriesRollupRaw,
		}

		rows, err := s.pool.Query(ctx, `
			SELECT ts, value
			FROM metric_samples
			WHERE entity_id = $1 AND metric = $2
			  AND ts >= $3 AND ts <= $4
			ORDER BY ts ASC`,
			entityID, metricName, from, to)
		if err != nil {
			return nil, err
		}

		samples := []struct {
			Avg   *float32  `json:"avg"`
			Count *int      `json:"count"`
			Max   *float32  `json:"max"`
			Min   *float32  `json:"min"`
			Ts    time.Time `json:"ts"`
			Value *float32  `json:"value"`
		}{}

		for rows.Next() {
			var ts time.Time
			var val float64
			if err := rows.Scan(&ts, &val); err != nil {
				rows.Close()
				return nil, err
			}
			f := float32(val)
			samples = append(samples, struct {
				Avg   *float32  `json:"avg"`
				Count *int      `json:"count"`
				Max   *float32  `json:"max"`
				Min   *float32  `json:"min"`
				Ts    time.Time `json:"ts"`
				Value *float32  `json:"value"`
			}{Value: &f, Ts: ts})
		}
		rows.Close()
		if rows.Err() != nil {
			return nil, rows.Err()
		}

		series.Samples = samples
		items = append(items, series)
	}

	if items == nil {
		items = []gen.MetricSeries{}
	}
	return gen.QueryMetrics200JSONResponse{Items: items}, nil
}

func (s *Server) GetTrends(ctx context.Context, req gen.GetTrendsRequestObject) (gen.GetTrendsResponseObject, error) {
	entityID, err := s.resolveEntityID(ctx, req.EntityId)
	if err != nil {
		return nil, err
	}

	from := time.Now().Add(-7 * 24 * time.Hour)
	if req.Params.From != nil {
		from = *req.Params.From
	}

	rows, err := s.pool.Query(ctx, `
		SELECT metric,
		       ROUND(avg(value)::numeric, 2) AS avg_val,
		       ROUND(stddev(value)::numeric, 2) AS std_val,
		       count(*) AS sample_count,
		       ROUND(regr_slope(value, EXTRACT(EPOCH FROM ts)::numeric)::numeric, 4) AS slope
		FROM metric_samples
		WHERE entity_id = $1 AND ts >= $2
		GROUP BY metric
		ORDER BY metric`, entityID, from)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	items := []gen.Trend{}
	for rows.Next() {
		var t gen.Trend
		var avgVal, stdVal, slopeNum pgtype.Numeric
		var sampleCount int
		if err := rows.Scan(&t.Metric, &avgVal, &stdVal, &sampleCount, &slopeNum); err != nil {
			return nil, err
		}

		// Determine direction.
		if slopeNum.Valid {
			f, _ := slopeNum.Float64Value()
			t.Slope = float32Ptr(float32(f.Float64))
			if f.Float64 > 0.01 {
				t.Direction = gen.TrendDirectionImproving
			} else if f.Float64 < -0.01 {
				t.Direction = gen.TrendDirectionDegrading
			} else {
				t.Direction = gen.TrendDirectionStable
			}
		} else {
			t.Direction = gen.TrendDirectionUnknown
		}
		items = append(items, t)
	}
	if rows.Err() != nil {
		return nil, rows.Err()
	}
	if items == nil {
		items = []gen.Trend{}
	}
	return gen.GetTrends200JSONResponse{Items: items}, nil
}

func float32Ptr(f float32) *float32 {
	return &f
}
