// Package main is the entry point for the RemoteRig central hub.
package main

import (
	"context"
	"embed"
	"fmt"
	"io/fs"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/cubecraft/remoterig/internal/api"
	"github.com/cubecraft/remoterig/internal/auth"
	"github.com/cubecraft/remoterig/internal/db"
	"github.com/cubecraft/remoterig/internal/events"
	"github.com/cubecraft/remoterig/internal/mqtt"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"gopkg.in/yaml.v3"
)

//go:embed all:src/dist
var frontendFS embed.FS

// Config holds the application configuration.
type Config struct {
	DBPath       string        `yaml:"db_path"`
	APIKey       string        `yaml:"api_key"`
	Port         string        `yaml:"port"`
	ReadTimeout  time.Duration `yaml:"read_timeout"`
	WriteTimeout time.Duration `yaml:"write_timeout"`
	IdleTimeout  time.Duration `yaml:"idle_timeout"`
	MQTT         struct {
		Broker   string `yaml:"broker"`
		ClientID string `yaml:"client_id"`
	} `yaml:"mqtt"`
	Platform struct {
		Type       string `yaml:"type"`
		MaxCameras int    `yaml:"max_cameras"`
	} `yaml:"platform"`
}

func main() {
	log.Println("RemoteRig hub starting...")

	// Load config
	cfg, err := loadConfig("config.yaml")
	if err != nil {
		log.Fatalf("Failed to load config: %v", err)
	}

	// Open database
	sqlDB, err := db.Open(cfg.DBPath)
	if err != nil {
		log.Fatalf("Failed to open database: %v", err)
	}
	defer sqlDB.Close()
	log.Printf("Database open: %s", cfg.DBPath)

	// Create SSE hub for real-time updates
	sseHub := events.NewHub()

	// Start MQTT subscriber for ESP32 camera status ingestion
	mqttSub := mqtt.NewSubscriber(cfg.MQTT.Broker, cfg.MQTT.ClientID, sqlDB, sseHub)
	if err := mqttSub.Connect(); err != nil {
		log.Printf("WARNING: MQTT subscriber failed to connect: %v (running without MQTT)", err)
	}
	defer mqttSub.Close()

	// Set up router
	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Timeout(cfg.WriteTimeout))

	// Health check (no auth)
	r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"status":"ok"}`))
	})

	// API routes (auth required if API key is configured)
	r.Mount("/api/v1", auth.Middleware(cfg.APIKey)(apiRouter(sseHub, sqlDB)))

	// Serve embedded React frontend with SPA fallback
	r.Mount("/", frontendHandler())

	// Create server
	httpServer := &http.Server{
		Addr:         ":" + cfg.Port,
		Handler:      r,
		ReadTimeout:  cfg.ReadTimeout,
		WriteTimeout: cfg.WriteTimeout,
		IdleTimeout:  cfg.IdleTimeout,
	}

	// Graceful shutdown
	go func() {
		sigInt := make(chan os.Signal, 1)
		signal.Notify(sigInt, syscall.SIGINT, syscall.SIGTERM)
		<-sigInt
		log.Println("Shutting down server...")
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		httpServer.Shutdown(ctx)
	}()

	log.Printf("Server listening on port %s", cfg.Port)
	if err := httpServer.ListenAndServe(); err != nil {
		log.Fatalf("Server failed: %v", err)
	}
}

// apiRouter creates the API route tree.
func apiRouter(sseHub *events.Hub, database *db.DB) http.Handler {
	r := chi.NewRouter()

	// Camera management routes
	r.Get("/cameras", api.ListCameras(database))
	r.Post("/cameras", api.RegisterCamera(database))
	r.Get("/cameras/{id}", api.GetCameraDetail(database))

	// Recording control routes
	r.Post("/cameras/{id}/start", api.StartRecording(database))
	r.Post("/cameras/{id}/stop", api.StopRecording(database))

	// Status ingestion (from ESP32 nodes)
	r.Post("/cameras/{id}/status", api.PushStatus(database))

	// Real-time events (SSE)
	r.Handle("/events/stream", sseHub.Handler())

	return r
}

func loadConfig(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read config file: %w", err)
	}

	var cfg Config
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parse config: %w", err)
	}

	// Defaults
	if cfg.Port == "" {
		cfg.Port = "8080"
	}
	if cfg.DBPath == "" {
		cfg.DBPath = "remoterig.db"
	}
	if cfg.MQTT.Broker == "" {
		cfg.MQTT.Broker = "localhost:1883"
	}
	if cfg.MQTT.ClientID == "" {
		cfg.MQTT.ClientID = "remoterig-hub"
	}

	return &cfg, nil
}

// frontendHandler returns an http.Handler that serves the embedded React
// frontend from src/dist/ with SPA-style fallback: any path that doesn't
// match a static file serves index.html for client-side routing.
//
// The frontend is embedded via //go:embed all:src/dist at build time.
// If src/dist/ is empty or missing at build time, the embedded fallback
// index.html (committed to the repo) is served instead, showing a
// "run npm run build" message.
func frontendHandler() http.Handler {
	distFS, err := fs.Sub(frontendFS, "src/dist")
	if err != nil {
		// Shouldn't happen if embed worked, but be defensive.
		panic("embedded frontend filesystem not found: " + err.Error())
	}

	fileServer := http.FileServer(http.FS(distFS))

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Try to serve the requested file.
		f, err := distFS.Open(r.URL.Path[1:]) // strip leading "/"
		if err != nil {
			// File not found — serve index.html for SPA routing.
			r.URL.Path = "/"
			fileServer.ServeHTTP(w, r)
			return
		}
		f.Close()

		// File exists, serve it.
		fileServer.ServeHTTP(w, r)
	})
}