package main

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"sync"
	"time"
)

type HTTPProbe struct {
	name        string
	url         string
	interval    time.Duration
	timeout     time.Duration
	client      *http.Client
	mu          sync.RWMutex
	status      string
	lastError   string
	lastChecked time.Time
	lastSuccess time.Time
}

type ProbeSnapshot struct {
	Status      string
	Error       string
	LastChecked time.Time
	LastSuccess time.Time
}

func NewHTTPProbe(name, url string, interval, timeout time.Duration) *HTTPProbe {
	if interval <= 0 {
		interval = 10 * time.Second
	}
	if timeout <= 0 {
		timeout = 2 * time.Second
	}

	return &HTTPProbe{
		name:     name,
		url:      url,
		interval: interval,
		timeout:  timeout,
		client: &http.Client{
			Timeout: timeout,
		},
	}
}

// Start는 프로브를 백그라운드에서 주기적으로 실행합니다.
func (p *HTTPProbe) Start() {
	go func() {
		p.checkOnce()
		ticker := time.NewTicker(p.interval)
		defer ticker.Stop()
		for range ticker.C {
			p.checkOnce()
		}
	}()
}

func (p *HTTPProbe) Snapshot() ProbeSnapshot {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return ProbeSnapshot{
		Status:      p.status,
		Error:       p.lastError,
		LastChecked: p.lastChecked,
		LastSuccess: p.lastSuccess,
	}
}

func (p *HTTPProbe) StatusText() string {
	s := p.Snapshot()
	if s.Status == "ok" {
		return "ok"
	}
	if s.Status == "" {
		return "unknown"
	}
	if s.Error == "" {
		return "error"
	}
	return "error: " + s.Error
}

func (p *HTTPProbe) checkOnce() {
	ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil)
	if err != nil {
		p.update("error", fmt.Sprintf("request build failed: %v", err), false)
		return
	}

	resp, err := p.client.Do(req)
	if err != nil {
		p.update("error", err.Error(), false)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		p.update("error", fmt.Sprintf("status=%d", resp.StatusCode), false)
		return
	}

	p.update("ok", "", true)
}

func (p *HTTPProbe) update(status, errMsg string, success bool) {
	p.mu.Lock()
	prevStatus := p.status
	p.status = status
	p.lastError = errMsg
	p.lastChecked = time.Now()
	if success {
		p.lastSuccess = p.lastChecked
	}
	p.mu.Unlock()

	if prevStatus == status {
		return
	}
	if status == "ok" {
		slog.Info("Service probe recovered", "name", p.name, "url", p.url)
		return
	}
	slog.Error("Service probe failed", "name", p.name, "url", p.url, "error", errMsg)
}
