package health

import (
	"context"
	"sync"
	"time"
)

type Probe func(context.Context) error

type Dependency struct {
	Name     string
	Required bool
	Probe    Probe
}

type Result struct {
	Status    string  `json:"status"`
	Required  bool    `json:"required"`
	LatencyMS float64 `json:"latency_ms"`
	Error     string  `json:"error,omitempty"`
}

type Report struct {
	Ready      bool              `json:"ready"`
	Status     string            `json:"status"`
	Components map[string]Result `json:"components"`
}

type Checker struct {
	Timeout      time.Duration
	Dependencies []Dependency
}

func (c Checker) Readiness(ctx context.Context) Report {
	report := Report{Ready: true, Status: "ok", Components: make(map[string]Result, len(c.Dependencies))}
	type namedResult struct {
		name   string
		result Result
	}
	results := make(chan namedResult, len(c.Dependencies))
	var wg sync.WaitGroup

	for _, dependency := range c.Dependencies {
		dependency := dependency
		wg.Add(1)
		go func() {
			defer wg.Done()
			result := Result{Status: "ok", Required: dependency.Required}
			if dependency.Probe == nil {
				result.Status = "not_configured"
				results <- namedResult{dependency.Name, result}
				return
			}
			probeCtx, cancel := context.WithTimeout(ctx, c.Timeout)
			defer cancel()
			started := time.Now()
			err := dependency.Probe(probeCtx)
			result.LatencyMS = float64(time.Since(started).Microseconds()) / 1000
			if err != nil {
				result.Status = "unavailable"
				result.Error = err.Error()
			}
			results <- namedResult{dependency.Name, result}
		}()
	}

	go func() {
		wg.Wait()
		close(results)
	}()
	for item := range results {
		report.Components[item.name] = item.result
		if item.result.Required && item.result.Status != "ok" {
			report.Ready = false
		}
		if !item.result.Required && item.result.Status == "unavailable" && report.Status == "ok" {
			report.Status = "degraded"
		}
	}
	if !report.Ready {
		report.Status = "unavailable"
	}
	return report
}
