package app

import (
	"context"
	"sort"
	"time"

	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/core/ports"
)

// SignalService manages signal lifecycle: upsert from check results, resolve
// on recovery, flap suppression, and health aggregation.
type SignalService struct {
	signals ports.SignalRepository
	metrics ports.MetricsRepository
}

func NewSignalService(signals ports.SignalRepository, metrics ports.MetricsRepository) *SignalService {
	return &SignalService{signals: signals, metrics: metrics}
}

// CheckOutcome is what one probe produces.
type CheckOutcome struct {
	EntityID   domain.UUID
	TargetID   domain.UUID
	Slug       string
	Kind       string
	Value      float64
	State      string
	Message    string
	PrevHealth string
}

// HealthChange describes a health transition.
type HealthChange struct {
	TargetID domain.UUID
	Slug     string
	From     string
	To       string
}

// ProcessCheckResult evaluates a probe outcome, upserts signals as needed,
// records metrics, and returns any health changes.
func (s *SignalService) ProcessCheckResult(ctx context.Context, oc CheckOutcome) (*HealthChange, error) {
	_ = s.metrics.InsertSamples(ctx, oc.TargetID, []ports.MetricSample{
		{Metric: oc.Kind + "_value", Value: oc.Value, Timestamp: time.Now()},
	})

	switch oc.State {
	case "ok":
		_, _ = s.signals.Transition(ctx, ports.SignalTransitionInput{
			SignalID: oc.EntityID, Action: "resolve", Actor: "scheduler",
		})
	case "critical", "warning":
		severity := domain.SeverityWarning
		if oc.State == "critical" {
			severity = domain.SeverityCritical
		}
		_ = s.signals.UpsertWithTriggers(ctx, ports.SignalUpsertInput{
			Signal: domain.Signal{
				EntityID:   oc.TargetID,
				Kind:       oc.Kind,
				Severity:   severity,
				State:      domain.SignalRaised,
			},
		})
	}

	health := aggregateCheckStates(oc.State, oc.PrevHealth)
	if health == oc.PrevHealth {
		return nil, nil
	}
	return &HealthChange{TargetID: oc.TargetID, Slug: oc.Slug, From: oc.PrevHealth, To: health}, nil
}

func aggregateCheckStates(probeState, prevHealth string) string {
	switch probeState {
	case "critical":
		return "down"
	case "warning":
		return "degraded"
	case "unknown":
		return "stale"
	default:
		return "ok"
	}
}

// WorstHealthForTarget computes entity health from all open signals.
func (s *SignalService) WorstHealthForTarget(ctx context.Context, targetID domain.UUID) (string, error) {
	signals, err := s.signals.Open(ctx)
	if err != nil {
		return "", err
	}
	if len(signals) == 0 {
		return "ok", nil
	}
	sort.Slice(signals, func(i, j int) bool {
		return severityRank(signals[i].Severity) > severityRank(signals[j].Severity)
	})
	switch signals[0].Severity {
	case domain.SeverityCritical:
		return "down", nil
	case domain.SeverityWarning:
		return "degraded", nil
	default:
		return "stale", nil
	}
}

func severityRank(s string) int {
	switch s {
	case domain.SeverityCritical:
		return 3
	case domain.SeverityWarning:
		return 2
	case domain.SeverityInfo:
		return 1
	default:
		return 0
	}
}