package api

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

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/pingcap/log"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/backup"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/config"
	"go.uber.org/zap"
)

// ManagerInterface defines the interface for backup manager operations
type ManagerInterface interface {
	ListBackups() []*backup.BackupMetadata
	GetBackup(id string) (*backup.BackupMetadata, bool)
	EnqueueBackup(backupType backup.BackupType, name string) (*backup.BackupMetadata, error)
	GetRunningBackupIfExists() (*backup.BackupMetadata, bool)
}

// Server handles HTTP API requests
type Server struct {
	config  *config.Config
	manager ManagerInterface
	engine  *gin.Engine
	server  *http.Server
}

// NewServer creates a new API server
func NewServer(cfg *config.Config, manager ManagerInterface) *Server {
	gin.SetMode(gin.ReleaseMode)

	router := gin.Default()
	server := &Server{
		config:  cfg,
		manager: manager,
		engine:  router,
		server: &http.Server{
			Addr:         cfg.HttpListenAddr,
			Handler:      router,
			ReadTimeout:  10 * time.Second,
			WriteTimeout: 10 * time.Second,
			IdleTimeout:  60 * time.Second,
		},
	}

	// Setup routes
	server.setupRoutes()
	return server
}

// setupRoutes configures the API routes
func (s *Server) setupRoutes() {
	// API version group
	v1 := s.engine.Group("/api/v1")

	// Backup routes
	v1.GET("/backups", s.listBackups)
	v1.GET("/backups/:id", s.getBackup)
	v1.POST("/backups", s.createBackup)

	// Metrics
	s.engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
	// Health check
	s.engine.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})
}

// Start begins listening for HTTP requests
func (s *Server) Start() error {
	log.Info("Starting API server", zap.String("address", s.config.HttpListenAddr))
	return s.server.ListenAndServe()
}

// Shutdown gracefully stops the server
func (s *Server) Shutdown(ctx context.Context) error {
	log.Info("Shutting down API server")
	return s.server.Shutdown(ctx)
}

// listBackups returns a list of all backups
func (s *Server) listBackups(c *gin.Context) {
	backups := s.manager.ListBackups()
	c.JSON(http.StatusOK, gin.H{
		"backups": backups,
		"count":   len(backups),
	})
}

// getBackup returns details of a specific backup
func (s *Server) getBackup(c *gin.Context) {
	id := c.Param("id")
	backup, exists := s.manager.GetBackup(id)
	if !exists {
		log.Warn("Backup not found", zap.String("id", id))
		c.JSON(http.StatusNotFound, gin.H{
			"message": fmt.Sprintf("Backup with ID %s not found", id),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"message": "backup details",
		"backup":  backup,
	})
}

// createBackup initiates a new full backup
func (s *Server) createBackup(c *gin.Context) {
	// fail-fast if a backup is already running
	if runningBackup, isRunning := s.manager.GetRunningBackupIfExists(); isRunning {
		log.Warn("Backup creation rejected", zap.String("reason", "backup already running"), zap.String("runningBackupID", runningBackup.Id))
		c.JSON(http.StatusConflict, gin.H{
			"message": fmt.Sprintf("Failed to create backup, another backup is already running: %s", runningBackup.Id),
		})
		return
	}

	// Enqueue the backup task
	id := uuid.New().String()
	backupMeta, err := s.manager.EnqueueBackup(backup.FullBackup, id)
	if err != nil {
		if err == backup.ErrQueueFull {
			log.Warn("Failed to enqueue backup, queue is full", zap.Error(err))
			c.JSON(http.StatusServiceUnavailable, gin.H{
				"message": "Failed to create backup, server busy or queue full. Please try again later.",
			})
		} else {
			log.Error("Failed to enqueue backup", zap.Error(err))
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": "Failed to create backup due to an internal error.",
			})
		}
		return
	}

	log.Info("Backup enqueued", zap.String("type", string(backupMeta.Type)))
	c.JSON(http.StatusAccepted, gin.H{
		"message": "backup created",
		"backup":  backupMeta,
	})
}
