package connectors

import (
	"context"
	"errors"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"

	"github.com/pingcap-inc/nexuscrm-connectors/features/connector/syncstate"
)

// ConnectorHealthSyncStore is the worker-internal API consumed by the health endpoint.
type ConnectorHealthSyncStore interface {
	ListByNamespace(ctx context.Context, namespace string) ([]connector_syncstate.SyncState, error)
}

// ConnectorHealthCredentialStore computes a connector-level credential validity
// signal without exposing secret values to the REST layer.
type ConnectorHealthCredentialStore interface {
	CredentialValid(ctx context.Context, namespace string) (bool, error)
}

// ConnectorHealthHandler serves GET /api/connectors/:namespace/health.
type ConnectorHealthHandler struct {
	sync  ConnectorHealthSyncStore
	creds ConnectorHealthCredentialStore
}

// NewConnectorHealthHandler constructs the health handler.
func NewConnectorHealthHandler(sync ConnectorHealthSyncStore) *ConnectorHealthHandler {
	return &ConnectorHealthHandler{sync: sync}
}

func NewConnectorHealthHandlerWithCredentials(sync ConnectorHealthSyncStore, creds ConnectorHealthCredentialStore) *ConnectorHealthHandler {
	return &ConnectorHealthHandler{sync: sync, creds: creds}
}

// GetHealth handles GET /api/connectors/:namespace/health.
// Returns an aggregated health payload per (object, direction) tuple known to
// SyncState.
//
// 404 when the connector pack is missing/soft-deleted/inactive (ListByNamespace
// surfaces ErrConnectorPackInactive). Otherwise 200 with the payload (possibly
// empty tuples list if the worker has not yet recorded any SyncState row).
//
// lastErrorMessage is intentionally NOT included in the response. ViewConnectorHealth
// is a delegatable permission distinct from ManageNamedCredentials; worker-supplied
// error text can contain secrets, upstream response bodies, or tenant data that
// must not be surfaced to delegated-permission holders. The DB column
// (_System_SyncState.LastErrorMessage) is retained for worker-internal retry
// decisions but is never serialised to HTTP callers.
func (h *ConnectorHealthHandler) GetHealth(c *gin.Context) {
	namespace := c.Param("namespace")

	states, err := h.sync.ListByNamespace(c.Request.Context(), namespace)
	if err != nil {
		if errors.Is(err, connector_syncstate.ErrConnectorPackInactive) {
			c.JSON(http.StatusNotFound, gin.H{"error": "connector pack not available"})
			return
		}
		log.Printf("connector health: GetHealth namespace=%q internal error: %v", namespace, err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
		return
	}

	tuples := make([]gin.H, 0, len(states))
	for _, s := range states {
		var lastSyncTime, lastErrorAt interface{}
		if s.LastSyncAt.Valid {
			lastSyncTime = s.LastSyncAt.Time
		}
		if s.LastErrorAt.Valid {
			lastErrorAt = s.LastErrorAt.Time
		}
		tuples = append(tuples, gin.H{
			"object":       s.Object,
			"direction":    s.Direction,
			"lastSyncTime": lastSyncTime,
			"lastErrorAt":  lastErrorAt,
			"errorCount":   s.RunningErrorCount,
			"isActive":     s.IsActive,
		})
	}

	body := gin.H{
		"namespace": namespace,
		"states":    tuples,
	}
	if h.creds != nil {
		credentialValid, err := h.creds.CredentialValid(c.Request.Context(), namespace)
		if err != nil {
			log.Printf("connector health: CredentialValid namespace=%q failed closed: %v", namespace, err)
			credentialValid = false
		}
		body["credentialValid"] = credentialValid
	}
	c.JSON(http.StatusOK, body)
}

// RegisterRoutes registers the connector health endpoint on the provided group.
// The provided group is expected to already enforce auth + permLoader + the
// ViewConnectorHealth permission gate.
func (h *ConnectorHealthHandler) RegisterRoutes(group *gin.RouterGroup) {
	group.GET("/:namespace/health", h.GetHealth)
}
