package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/Warky-Devs/vecna.git/pkg/config"
	"github.com/Warky-Devs/vecna.git/pkg/embedclient"
	"github.com/Warky-Devs/vecna.git/pkg/forwardproxy"
	"github.com/Warky-Devs/vecna.git/pkg/metrics"
	"github.com/Warky-Devs/vecna.git/pkg/server"
)

var serveCmd = &cobra.Command{
	Use:   "serve",
	Short: "Start the vecna embedding proxy server",
	RunE:  runServe,
}

func init() {
	serveCmd.Flags().String("host", "", "bind host (overrides config)")
	serveCmd.Flags().Int("port", 0, "bind port (overrides config)")
}

func runServe(cmd *cobra.Command, _ []string) error {
	cfg, err := config.Load(cfgFile)
	if err != nil {
		return fmt.Errorf("load config: %w", err)
	}

	// Flag overrides
	if h, _ := cmd.Flags().GetString("host"); h != "" {
		cfg.Server.Host = h
	}
	if p, _ := cmd.Flags().GetInt("port"); p != 0 {
		cfg.Server.Port = p
	}

	adp, err := buildAdapter(cfg)
	if err != nil {
		return fmt.Errorf("build adapter: %w", err)
	}

	extraMaps, err := buildExtraMapAdapters(cfg)
	if err != nil {
		return fmt.Errorf("build extra_maps: %w", err)
	}

	clients, err := buildClients(cfg)
	if err != nil {
		return fmt.Errorf("build clients: %w", err)
	}

	forwarders, err := buildForwarders(cfg)
	if err != nil {
		return fmt.Errorf("build forwarders: %w", err)
	}

	var reg *metrics.Registry
	if cfg.Metrics.Enabled {
		reg = metrics.New()
	}

	router, err := server.New(cfg, clients, adp, extraMaps, forwarders, reg, logger)
	if err != nil {
		return fmt.Errorf("build router: %w", err)
	}

	addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
	srv := &http.Server{
		Addr:    addr,
		Handler: router,
	}

	// Graceful shutdown
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		logger.Info("vecna listening", zap.String("addr", addr))
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Error("server error", zap.Error(err))
			quit <- syscall.SIGTERM
		}
	}()

	<-quit
	logger.Info("shutting down")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		return fmt.Errorf("graceful shutdown: %w", err)
	}
	return nil
}

// buildClients constructs one embedclient.Client per named forward target.
func buildClients(cfg *config.Config) (map[string]embedclient.Client, error) {
	clients := make(map[string]embedclient.Client, len(cfg.Forward.Targets))

	for name, target := range cfg.Forward.Targets {
		if len(target.Endpoints) == 0 {
			return nil, fmt.Errorf("target %q has no endpoints", name)
		}

		timeout := time.Duration(target.TimeoutSecs) * time.Second
		httpClient := &http.Client{Timeout: timeout}

		slots := make([]embedclient.RouterSlot, len(target.Endpoints))
		for i, ep := range target.Endpoints {
			apiKey := ep.APIKey
			if apiKey == "" {
				apiKey = target.APIKey
			}

			var c embedclient.Client
			switch target.APIType {
			case "google":
				c = embedclient.NewGoogle(ep.URL, apiKey, target.Model, httpClient)
			default: // "openai" or unset
				c = embedclient.NewOpenAI(ep.URL, apiKey, httpClient)
			}

			slots[i] = embedclient.RouterSlot{
				Client:   c,
				URL:      ep.URL,
				Priority: ep.Priority,
			}
		}

		routerCfg := embedclient.RouterConfig{
			TargetName:       name,
			TimeoutSecs:      target.TimeoutSecs,
			CooldownSecs:     target.CooldownSecs,
			PriorityDecay:    target.PriorityDecay,
			PriorityRecovery: target.PriorityRecovery,
		}

		// metrics registry may be nil (disabled)
		var reg *metrics.Registry
		if cfg.Metrics.Enabled {
			// Registry is created in runServe before buildClients; pass nil here,
			// caller wires it in after creation. See note below.
			_ = reg
		}

		router, err := embedclient.NewTargetRouter(slots, routerCfg, nil)
		if err != nil {
			return nil, fmt.Errorf("build router for target %q: %w", name, err)
		}
		clients[name] = router
	}

	return clients, nil
}

// buildForwarders constructs one forwardproxy.Router per named forward target,
// used for the generic OpenAI-compatible passthrough endpoints (chat
// completions, models list, audio, images, etc.). No client-level timeout is
// set here — streaming responses (SSE) are open-ended by design; non-streaming
// requests get a per-attempt timeout from RouterConfig.TimeoutSecs.
func buildForwarders(cfg *config.Config) (map[string]*forwardproxy.Router, error) {
	forwarders := make(map[string]*forwardproxy.Router, len(cfg.Forward.Targets))
	httpClient := &http.Client{}

	for name, target := range cfg.Forward.Targets {
		if len(target.Endpoints) == 0 {
			return nil, fmt.Errorf("target %q has no endpoints", name)
		}

		endpoints := make([]forwardproxy.Endpoint, len(target.Endpoints))
		for i, ep := range target.Endpoints {
			apiKey := ep.APIKey
			if apiKey == "" {
				apiKey = target.APIKey
			}
			endpoints[i] = forwardproxy.Endpoint{URL: ep.URL, APIKey: apiKey, Priority: ep.Priority}
		}

		routerCfg := forwardproxy.RouterConfig{
			TargetName:       name,
			TimeoutSecs:      target.TimeoutSecs,
			CooldownSecs:     target.CooldownSecs,
			PriorityDecay:    target.PriorityDecay,
			PriorityRecovery: target.PriorityRecovery,
		}

		router, err := forwardproxy.NewRouter(endpoints, routerCfg, httpClient)
		if err != nil {
			return nil, fmt.Errorf("build forwarder for target %q: %w", name, err)
		}
		forwarders[name] = router
	}

	return forwarders, nil
}
