package app

import (
	"context"
	"net/http"
	"sync"
	"time"

	"{{GO_MODULE}}/pkg/httperror"
	"{{GO_MODULE}}/pkg/httpresponse"
)

// HealthChecker is a function that checks the health of a dependency.
// Returns nil if healthy, an error describing the issue otherwise.
type HealthChecker func(ctx context.Context) error

// HealthCheckResult represents the result of a single health check.
type HealthCheckResult struct {
	Name    string `json:"name"`
	Status  string `json:"status"` // "healthy" or "unhealthy"
	Latency string `json:"latency,omitempty"`
	Error   string `json:"error,omitempty"`
}

// HealthResponse is the response structure for health endpoints.
type HealthResponse struct {
	Status   string              `json:"status"` // "healthy" or "unhealthy"
	Service  string              `json:"service"`
	Checks   []HealthCheckResult `json:"checks,omitempty"`
	Duration string              `json:"duration,omitempty"`
}

// HealthConfig configures health check behavior.
type HealthConfig struct {
	// Service name for identification
	Service string

	// Timeout for individual health checks (default: 5s)
	Timeout time.Duration

	// Checks is a map of check names to checker functions
	Checks map[string]HealthChecker
}

// NewHealthHandler creates an HTTP handler that runs health checks concurrently.
// Returns 200 if all checks pass, 503 if any check fails.
//
// Example:
//
//	healthHandler := app.NewHealthHandler(app.HealthConfig{
//	    Service: "my-service",
//	    Timeout: 5 * time.Second,
//	    Checks: map[string]app.HealthChecker{
//	        "database": func(ctx context.Context) error {
//	            return db.PingContext(ctx)
//	        },
//	        "redis": func(ctx context.Context) error {
//	            return redis.Ping(ctx).Err()
//	        },
//	    },
//	})
//
//	r.Get("/health", healthHandler)
func NewHealthHandler(cfg HealthConfig) http.HandlerFunc {
	if cfg.Timeout == 0 {
		cfg.Timeout = 5 * time.Second
	}

	return func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		// If no checks configured, return simple healthy response
		if len(cfg.Checks) == 0 {
			httpresponse.OK(w, r, HealthResponse{
				Status:  "healthy",
				Service: cfg.Service,
			})
			return
		}

		// Create context with timeout
		ctx, cancel := context.WithTimeout(r.Context(), cfg.Timeout)
		defer cancel()

		// Run checks concurrently
		results := make([]HealthCheckResult, 0, len(cfg.Checks))
		var mu sync.Mutex
		var wg sync.WaitGroup

		for name, checker := range cfg.Checks {
			wg.Add(1)
			go func(name string, checker HealthChecker) {
				defer wg.Done()

				checkStart := time.Now()
				err := checker(ctx)
				latency := time.Since(checkStart)

				result := HealthCheckResult{
					Name:    name,
					Status:  "healthy",
					Latency: latency.Round(time.Millisecond).String(),
				}

				if err != nil {
					result.Status = "unhealthy"
					result.Error = err.Error()
				}

				mu.Lock()
				results = append(results, result)
				mu.Unlock()
			}(name, checker)
		}

		wg.Wait()

		// Determine overall status
		status := "healthy"
		httpStatus := http.StatusOK

		for _, result := range results {
			if result.Status == "unhealthy" {
				status = "unhealthy"
				httpStatus = http.StatusServiceUnavailable
				break
			}
		}

		resp := HealthResponse{
			Status:   status,
			Service:  cfg.Service,
			Checks:   results,
			Duration: time.Since(start).Round(time.Millisecond).String(),
		}

		httpresponse.JSON(w, r, httpStatus, resp)
	}
}

// NewLivenessHandler creates a simple liveness probe handler.
// Always returns 200 OK if the process is running.
// Use for Kubernetes liveness probes.
//
// Example:
//
//	r.Get("/health/live", app.NewLivenessHandler("my-service"))
func NewLivenessHandler(service string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		httpresponse.OK(w, r, map[string]string{
			"status":  "ok",
			"service": service,
		})
	}
}

// NewReadinessHandler creates a readiness probe handler with dependency checks.
// Returns 200 if all checks pass, 503 if any check fails.
// Use for Kubernetes readiness probes.
//
// Example:
//
//	r.Get("/health/ready", app.NewReadinessHandler(app.HealthConfig{
//	    Service: "my-service",
//	    Checks: map[string]app.HealthChecker{
//	        "database": dbHealthCheck,
//	    },
//	}))
func NewReadinessHandler(cfg HealthConfig) http.HandlerFunc {
	return NewHealthHandler(cfg)
}

// -----------------------------------------------------------------------------
// Common Health Checkers
// -----------------------------------------------------------------------------

// PingChecker creates a health checker from a Ping function.
// Many database clients have a Ping or PingContext method.
//
// Example:
//
//	checks := map[string]app.HealthChecker{
//	    "postgres": app.PingChecker(db.PingContext),
//	}
func PingChecker(pingFn func(context.Context) error) HealthChecker {
	return pingFn
}

// HTTPChecker creates a health checker that makes an HTTP GET request.
// Returns error if status is not 2xx.
//
// Example:
//
//	checks := map[string]app.HealthChecker{
//	    "external-api": app.HTTPChecker("https://api.example.com/health"),
//	}
func HTTPChecker(url string) HealthChecker {
	return func(ctx context.Context) error {
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			return err
		}

		client := &http.Client{Timeout: 5 * time.Second}
		resp, err := client.Do(req)
		if err != nil {
			return err
		}
		defer resp.Body.Close()

		if resp.StatusCode < 200 || resp.StatusCode >= 300 {
			return httperror.Internalf("unhealthy: status %d", resp.StatusCode)
		}

		return nil
	}
}
