package health

import (
	"context"
	"encoding/json"
	"sync"
	"time"

	"go.temporal.io/sdk/client"
)

// Status represents the health status of a component
type Status string

const (
	StatusHealthy   Status = "healthy"
	StatusUnhealthy Status = "unhealthy"
	StatusUnknown   Status = "unknown"
)

// ComponentHealth represents the health of a single system component
type ComponentHealth struct {
	Name     string        `json:"name"`
	Status   Status        `json:"status"`
	Latency  int64         `json:"latency_ms"`
	LastCheck time.Time   `json:"last_check"`
	Error    string        `json:"error,omitempty"`
}

// HealthReport is the overall health status of the system
type HealthReport struct {
	Status      Status                       `json:"status"`
	Timestamp   time.Time                    `json:"timestamp"`
	Components  map[string]ComponentHealth   `json:"components"`
	Latency     int64                        `json:"latency_ms"`
}

// Checker provides health check functionality
type Checker struct {
	temporalClient client.Client
	mu             sync.RWMutex
	lastReport     *HealthReport
	lastCheckTime  time.Time
	checkInterval  time.Duration
}

// NewChecker creates a new health checker
func NewChecker(temporalClient client.Client) *Checker {
	return &Checker{
		temporalClient: temporalClient,
		checkInterval:  30 * time.Second,
	}
}

// Check performs a comprehensive health check
func (h *Checker) Check(ctx context.Context) *HealthReport {
	startTime := time.Now()
	h.mu.Lock()
	defer h.mu.Unlock()

	// Skip check if recently done
	if time.Since(h.lastCheckTime) < h.checkInterval && h.lastReport != nil {
		return h.lastReport
	}

	components := make(map[string]ComponentHealth)

	// Check Temporal connectivity
	temporalHealth := h.checkTemporal(ctx)
	components["temporal"] = temporalHealth

	// Determine overall status
	overallStatus := StatusHealthy
	for _, comp := range components {
		if comp.Status == StatusUnhealthy {
			overallStatus = StatusUnhealthy
			break
		}
	}

	latency := time.Since(startTime).Milliseconds()
	report := &HealthReport{
		Status:     overallStatus,
		Timestamp:  time.Now(),
		Components: components,
		Latency:    latency,
	}

	h.lastReport = report
	h.lastCheckTime = time.Now()

	return report
}

// checkTemporal verifies Temporal cluster connectivity
func (h *Checker) checkTemporal(ctx context.Context) ComponentHealth {
	startTime := time.Now()
	comp := ComponentHealth{
		Name:      "temporal",
		Status:    StatusHealthy,
		LastCheck: time.Now(),
	}

	if h.temporalClient == nil {
		comp.Status = StatusUnhealthy
		comp.Error = "Temporal client not initialized"
		comp.Latency = time.Since(startTime).Milliseconds()
		return comp
	}

	// Create a short timeout context for the health check
	checkCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	// Try to get a workflow to verify connectivity
	// Use a dummy workflow ID that likely doesn't exist - we're just testing connectivity
	// If Temporal is unreachable, this will fail; if it's reachable, it will return NotFound error (which is fine)
	wfRun := h.temporalClient.GetWorkflow(checkCtx, "health-check-dummy-id-"+time.Now().Format("20060102150405"), "")
	comp.Latency = time.Since(startTime).Milliseconds()

	// We're only checking connectivity, so we try to peek at the result
	// This will fail if Temporal is unreachable, but return nil error if it just doesn't exist
	var result interface{}
	err := wfRun.Get(checkCtx, &result)

	if err != nil {
		errMsg := err.Error()
		// NotFound errors mean Temporal responded but workflow doesn't exist - this is healthy
		if !contains(errMsg, "not found") && !contains(errMsg, "NotFound") {
			comp.Status = StatusUnhealthy
			comp.Error = err.Error()
		}
	}

	return comp
}

// contains checks if a string contains a substring (case-insensitive)
func contains(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}

// IsHealthy returns true if the system is healthy
func (h *Checker) IsHealthy(ctx context.Context) bool {
	report := h.Check(ctx)
	return report.Status == StatusHealthy
}

// GetReport returns the last health report
func (h *Checker) GetReport(ctx context.Context) *HealthReport {
	return h.Check(ctx)
}

// ToJSON converts the health report to JSON
func (report *HealthReport) ToJSON() ([]byte, error) {
	return json.MarshalIndent(report, "", "  ")
}

// String returns the health status as a string
func (s Status) String() string {
	return string(s)
}
