package main

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

	"github.com/eduard256/Strix/internal/api"
	"github.com/eduard256/Strix/internal/config"
	"github.com/eduard256/Strix/internal/utils/logger"
	"github.com/eduard256/Strix/webui"
	"github.com/go-chi/chi/v5"
)

// Version is set at build time via ldflags:
//
//	go build -ldflags="-X main.Version=1.0.10" ./cmd/strix
var Version = "dev"

const Banner = `
███████╗████████╗██████╗ ██╗██╗  ██╗
██╔════╝╚══██╔══╝██╔══██╗██║╚██╗██╔╝
███████╗   ██║   ██████╔╝██║ ╚███╔╝
╚════██║   ██║   ██╔══██╗██║ ██╔██╗
███████║   ██║   ██║  ██║██║██╔╝ ██╗
╚══════╝   ╚═╝   ╚═╝  ╚═╝╚═╝╚═╝  ╚═╝

Smart IP Camera Stream Discovery System
Version: %s
`

func main() {
	// Print banner to stderr so it doesn't mix with structured log output on stdout
	fmt.Fprintf(os.Stderr, Banner, Version)
	fmt.Fprintln(os.Stderr)

	// Setup logger first, before anything else, so all messages use consistent format
	slogger, secrets := config.SetupLogger()
	slog.SetDefault(slogger)

	// Load configuration (uses the logger for startup messages)
	cfg := config.Load(slogger)
	cfg.Version = Version

	// Create adapter for our interface
	log := logger.NewAdapter(slogger, secrets)

	log.Info("starting Strix",
		slog.String("version", Version),
		slog.String("go_version", os.Getenv("GO_VERSION")),
		slog.String("listen", cfg.Server.Listen),
	)

	// Check if ffprobe is available
	if err := checkFFProbe(); err != nil {
		log.Warn("ffprobe not found, stream validation will be limited", slog.String("error", err.Error()))
	}

	// Create API server
	apiServer, err := api.NewServer(cfg, secrets, log)
	if err != nil {
		log.Error("failed to create API server", err)
		os.Exit(1)
	}

	// Create Web UI server
	webuiServer := webui.NewServer(log)

	// Create unified router combining API and WebUI
	unifiedRouter := chi.NewRouter()

	// Mount API routes at /api/v1/*
	unifiedRouter.Mount("/api/v1", apiServer.GetRouter())

	// Mount WebUI routes at /* (serves everything else including root)
	unifiedRouter.Mount("/", webuiServer.GetRouter())

	// Create unified HTTP server
	httpServer := &http.Server{
		Addr:         cfg.Server.Listen,
		Handler:      unifiedRouter,
		ReadTimeout:  cfg.Server.ReadTimeout,
		WriteTimeout: cfg.Server.WriteTimeout,
		IdleTimeout:  120 * time.Second,
	}

	// Start server in goroutine
	go func() {
		log.Info("server starting",
			slog.String("address", httpServer.Addr),
			slog.String("api_version", "v1"),
		)

		if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Error("server failed", err)
			os.Exit(1)
		}
	}()

	// Print endpoints
	printEndpoints(cfg.Server.Listen)

	// Wait for interrupt signal
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
	<-quit

	log.Info("shutting down server...")

	// Graceful shutdown with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Shutdown server
	if err := httpServer.Shutdown(ctx); err != nil {
		log.Error("server shutdown failed", err)
		os.Exit(1)
	}

	log.Info("server stopped gracefully")
}

// checkFFProbe checks if ffprobe is available
func checkFFProbe() error {
	// Try to execute ffprobe -version
	cmd := os.Getenv("PATH")
	if cmd == "" {
		return fmt.Errorf("PATH environment variable not set")
	}

	// For now, just check if ffprobe exists in common locations
	locations := []string{
		"/usr/bin/ffprobe",
		"/usr/local/bin/ffprobe",
		"/opt/homebrew/bin/ffprobe",
	}

	for _, loc := range locations {
		if _, err := os.Stat(loc); err == nil {
			return nil
		}
	}

	return fmt.Errorf("ffprobe not found in common locations")
}

// getLocalIP returns the local IP address of the machine
func getLocalIP() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "localhost"
	}

	for _, addr := range addrs {
		if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}

	return "localhost"
}

// printEndpoints prints available endpoints
func printEndpoints(listen string) {
	// Extract port from listen address
	port := "4567"
	if len(listen) > 0 {
		if listen[0] == ':' {
			port = listen[1:]
		} else {
			// Parse host:port format
			for i := len(listen) - 1; i >= 0; i-- {
				if listen[i] == ':' {
					port = listen[i+1:]
					break
				}
			}
		}
	}

	// Get local IP
	localIP := getLocalIP()
	url := fmt.Sprintf("http://%s:%s", localIP, port)

	// ANSI escape codes for clickable link (OSC 8 hyperlink)
	clickableURL := fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, url)

	fmt.Fprintln(os.Stderr, "\nWeb Interface:")
	fmt.Fprintln(os.Stderr, "────────────────────────────────────────────────")
	fmt.Fprintf(os.Stderr, "  Open in browser: %s\n", clickableURL)
	fmt.Fprintln(os.Stderr, "────────────────────────────────────────────────")

	fmt.Fprintln(os.Stderr, "\nDocumentation: https://github.com/eduard256/Strix")
}
