//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package service

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"ragflow/internal/cache"
	"ragflow/internal/dao"
	"ragflow/internal/engine"
	"ragflow/internal/server"
	"ragflow/internal/storage"
	"ragflow/internal/utility"
)

// SystemService system service
type SystemService struct{}

// NewSystemService create system service
func NewSystemService() *SystemService {
	return &SystemService{}
}

// ConfigResponse system configuration response
type ConfigResponse struct {
	RegisterEnabled      int  `json:"registerEnabled"`
	DisablePasswordLogin bool `json:"disablePasswordLogin"`
}

// GetConfig get system configuration
func (s *SystemService) GetConfig() (*ConfigResponse, error) {
	cfg := server.GetConfig()
	registerEnabled := 1
	if !cfg.Authentication.RegisterEnabled {
		registerEnabled = 0
	}
	return &ConfigResponse{
		RegisterEnabled:      registerEnabled,
		DisablePasswordLogin: cfg.Authentication.DisablePasswordLogin,
	}, nil
}

// VersionResponse version response
type VersionResponse struct {
	Version string `json:"version"`
}

type HealthzMeta struct {
	Elapsed string `json:"elapsed"`
	Error   string `json:"error,omitempty"`
}

type HealthzResponse struct {
	DB        string                 `json:"db"`
	Redis     string                 `json:"redis"`
	DocEngine string                 `json:"doc_engine"`
	Storage   string                 `json:"storage"`
	Status    string                 `json:"status"`
	Meta      map[string]HealthzMeta `json:"_meta,omitempty"`
}

// GetVersion get RAGFlow version
func (s *SystemService) GetVersion() (*VersionResponse, error) {
	version := utility.GetRAGFlowVersion()
	return &VersionResponse{
		Version: version,
	}, nil
}

// ComponentStatus describes one dependency health check.
type ComponentStatus map[string]interface{}

// StatusResponse system status response.
type StatusResponse struct {
	DocEngine              ComponentStatus          `json:"doc_engine"`
	Storage                ComponentStatus          `json:"storage"`
	Database               ComponentStatus          `json:"database"`
	Redis                  ComponentStatus          `json:"redis"`
	TaskExecutorHeartbeats map[string][]interface{} `json:"task_executor_heartbeats"`
}

// GetStatus gets health status for core system dependencies.
func (s *SystemService) GetStatus() (*StatusResponse, error) {
	return &StatusResponse{
		DocEngine:              s.getDocEngineStatus(),
		Storage:                s.getStorageStatus(),
		Database:               s.getDatabaseStatus(),
		Redis:                  s.getRedisStatus(),
		TaskExecutorHeartbeats: s.getTaskExecutorHeartbeats(),
	}, nil
}

func (s *SystemService) getDocEngineStatus() ComponentStatus {
	cfg := server.GetConfig()
	docEngineType := ""
	if cfg != nil {
		docEngineType = strings.ToLower(string(cfg.DocEngine.Type))
	}

	startedAt := time.Now()
	docEngine := engine.Get()
	if docEngine == nil {
		return ComponentStatus{
			"type":    docEngineType,
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   "doc engine not initialized",
		}
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := docEngine.Ping(ctx); err != nil {
		return ComponentStatus{
			"type":    docEngine.GetType(),
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   err.Error(),
		}
	}

	return ComponentStatus{
		"type":    docEngine.GetType(),
		"status":  "green",
		"elapsed": elapsedMilliseconds(startedAt),
	}
}

func (s *SystemService) getStorageStatus() ComponentStatus {
	cfg := server.GetConfig()
	storageType := ""
	if cfg != nil {
		storageType = strings.ToLower(string(cfg.StorageEngine.Type))
	}

	startedAt := time.Now()
	factory := storage.GetStorageFactory().GetStorage()
	if factory == nil {
		return ComponentStatus{
			"type":    storageType,
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   "storage not initialized",
		}
	}

	_, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if !factory.Health() {
		return ComponentStatus{
			"type":    storageType,
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   "storage health check failed",
		}
	}

	return ComponentStatus{
		"type":    storageType,
		"status":  "green",
		"elapsed": elapsedMilliseconds(startedAt),
	}
}

func (s *SystemService) getDatabaseStatus() ComponentStatus {
	cfg := server.GetConfig()
	databaseType := ""
	if cfg != nil {
		databaseType = cfg.Database.Driver
	}

	startedAt := time.Now()
	if dao.DB == nil {
		return ComponentStatus{
			"type":    databaseType,
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   "database not initialized",
		}
	}

	sqlDB, err := dao.GetDB().DB()
	if err != nil {
		return ComponentStatus{
			"type":    databaseType,
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   err.Error(),
		}
	}

	if err = sqlDB.Ping(); err != nil {
		return ComponentStatus{
			"type":    databaseType,
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   err.Error(),
		}
	}

	return ComponentStatus{
		"type":    databaseType,
		"status":  "green",
		"elapsed": elapsedMilliseconds(startedAt),
	}
}

func (s *SystemService) getRedisStatus() ComponentStatus {
	startedAt := time.Now()
	redisClient := cache.Get()
	if redisClient == nil {
		return ComponentStatus{
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   "redis not initialized",
		}
	}
	if !redisClient.Health() {
		return ComponentStatus{
			"status":  "red",
			"elapsed": elapsedMilliseconds(startedAt),
			"error":   "Lost connection!",
		}
	}

	return ComponentStatus{
		"status":  "green",
		"elapsed": elapsedMilliseconds(startedAt),
	}
}

func (s *SystemService) getTaskExecutorHeartbeats() map[string][]interface{} {
	heartbeatsByExecutor := map[string][]interface{}{}
	redisClient := cache.Get()
	if redisClient == nil {
		return heartbeatsByExecutor
	}

	taskExecutorIDs, err := redisClient.SMembers("TASKEXE")
	if err != nil {
		return heartbeatsByExecutor
	}

	now := float64(time.Now().Unix())
	for _, taskExecutorID := range taskExecutorIDs {
		rawHeartbeats, err := redisClient.ZRangeByScore(taskExecutorID, now-60*30, now)
		if err != nil {
			continue
		}

		heartbeats := make([]interface{}, 0, len(rawHeartbeats))
		for _, rawHeartbeat := range rawHeartbeats {
			var heartbeat interface{}
			if err := json.Unmarshal([]byte(rawHeartbeat), &heartbeat); err != nil {
				heartbeats = append(heartbeats, rawHeartbeat)
				continue
			}
			heartbeats = append(heartbeats, heartbeat)
		}
		heartbeatsByExecutor[taskExecutorID] = heartbeats
	}

	return heartbeatsByExecutor
}

func elapsedMilliseconds(startedAt time.Time) string {
	return fmt.Sprintf("%.1f", float64(time.Since(startedAt).Microseconds())/1000.0)
}

func okNok(ok bool) string {
	if ok {
		return "ok"
	}
	return "nok"
}

func timedHealthCheck(check func() error) (bool, HealthzMeta) {
	start := time.Now()
	err := check()
	meta := HealthzMeta{
		Elapsed: fmt.Sprintf("%.1f", float64(time.Since(start).Microseconds())/1000.0),
	}
	if err != nil {
		meta.Error = err.Error()
		return false, meta
	}
	return true, meta
}

// Healthz runs lightweight dependency checks for /api/v1/system/healthz.
func (s *SystemService) Healthz(ctx context.Context) (*HealthzResponse, bool) {
	meta := map[string]HealthzMeta{}

	dbOK, dbMeta := timedHealthCheck(func() error {
		if dao.DB == nil {
			return fmt.Errorf("database is not initialized")
		}
		sqlDB, err := dao.DB.DB()
		if err != nil {
			return err
		}
		return sqlDB.PingContext(ctx)
	})
	if !dbOK {
		meta["db"] = dbMeta
	}

	redisOK, redisMeta := timedHealthCheck(func() error {
		redisClient := cache.Get()
		if redisClient == nil || !redisClient.Health() {
			return fmt.Errorf("redis is not healthy")
		}
		return nil
	})
	if !redisOK {
		meta["redis"] = redisMeta
	}

	docOK, docMeta := timedHealthCheck(func() error {
		docEngine := engine.Get()
		if docEngine == nil {
			return fmt.Errorf("document engine is not initialized")
		}
		return docEngine.Ping(ctx)
	})
	if !docOK {
		meta["doc_engine"] = docMeta
	}

	storageOK, storageMeta := timedHealthCheck(func() error {
		store := storage.GetStorageFactory().GetStorage()
		if store == nil || !store.Health() {
			return fmt.Errorf("storage is not healthy")
		}
		return nil
	})
	if !storageOK {
		meta["storage"] = storageMeta
	}

	allOK := dbOK && redisOK && docOK && storageOK
	result := &HealthzResponse{
		DB:        okNok(dbOK),
		Redis:     okNok(redisOK),
		DocEngine: okNok(docOK),
		Storage:   okNok(storageOK),
		Status:    okNok(allOK),
	}
	if len(meta) > 0 {
		result.Meta = meta
	}
	return result, allOK
}
