// Package app provides a service bootstrapper for HTTP services.
//
// App is the main application struct that provides infrastructure for HTTP services.
// It manages configuration, logging, routing, and graceful shutdown.
//
// Example usage:
//
//	func main() {
//	    app := app.New("my-service", app.WithDefaultPort(8080))
//	    app.GET("/users/{id}", handlers.GetUser)
//	    app.POST("/users", handlers.CreateUser)
//	    app.Run()
//	}
package app

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

	"github.com/go-chi/chi/v5"

	"{{GO_MODULE}}/pkg/config"
	"{{GO_MODULE}}/pkg/httpresponse"
	"{{GO_MODULE}}/pkg/logging"
	"{{GO_MODULE}}/pkg/middleware"
	"{{GO_MODULE}}/pkg/openapi"
)

// Router is an alias for chi.Router, exposing it for handler mounting.
type Router = chi.Router

// App is the main application struct that provides infrastructure for HTTP services.
type App struct {
	name        string
	defaultPort int
	logger      *logging.Logger
	router      chi.Router
	server      *http.Server

	// Configuration
	appConfig    config.AppConfig
	serverConfig config.ServerConfig

	// Lifecycle hooks
	onShutdown []func(context.Context) error
}

// Option configures the App.
type Option func(*App)

// WithLogger sets a custom logger for the application.
func WithLogger(logger *logging.Logger) Option {
	return func(a *App) {
		a.logger = logger
	}
}

// WithDefaultPort sets the default port if not configured via environment.
func WithDefaultPort(port int) Option {
	return func(a *App) {
		a.defaultPort = port
	}
}

// New creates a new App instance with the given service name.
// It initializes configuration, logging, and routing infrastructure.
//
// The service name is used for:
//   - Configuration defaults (APP_NAME)
//   - Logging context (service attribute)
//   - Health check identification
//
// Configuration is loaded from environment variables with support for:
//   - .env file (in development)
//   - Environment variables (highest priority)
func New(serviceName string, opts ...Option) *App {
	app := &App{
		name:        serviceName,
		defaultPort: 8080,
		onShutdown:  make([]func(context.Context) error, 0),
	}

	// Apply options before initialization (to capture defaultPort)
	for _, opt := range opts {
		opt(app)
	}

	// Initialize configuration
	config.MustInit(config.Options{
		AppName:     serviceName,
		DefaultPort: app.defaultPort,
	})

	// Load configuration
	app.appConfig = config.ReadAppConfig()
	app.serverConfig = config.ReadServerConfig()

	// Initialize logger if not provided
	if app.logger == nil {
		logCfg := config.ReadLoggingConfig()
		app.logger = logging.New(logging.Config{
			Level:       logging.ParseLevel(logCfg.Level),
			Format:      logging.ParseFormat(logCfg.Format),
			Environment: app.appConfig.Environment,
			AddSource:   app.appConfig.IsDevelopment(),
		}).WithService(serviceName)
	}

	// Initialize router with standard middleware
	app.router = chi.NewRouter()
	app.setupMiddleware()
	app.setupHealthEndpoints()

	return app
}

// setupMiddleware configures the standard middleware stack.
func (a *App) setupMiddleware() {
	// Core middleware (order matters)
	a.router.Use(middleware.RequestID())
	a.router.Use(middleware.Tracing())
	a.router.Use(middleware.RequestLogger(a.logger))
	a.router.Use(middleware.Recoverer(a.logger))

	// CORS (configurable via environment)
	a.router.Use(middleware.CORS(middleware.DefaultCORSConfig()))
}

// setupHealthEndpoints registers /health and /ready endpoints.
func (a *App) setupHealthEndpoints() {
	// Liveness probe - returns 200 if the process is running
	a.router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
		httpresponse.OK(w, r, map[string]string{
			"status":  "ok",
			"service": a.name,
		})
	})

	// Readiness probe - returns 200 if the service is ready to accept traffic
	a.router.Get("/ready", func(w http.ResponseWriter, r *http.Request) {
		httpresponse.OK(w, r, map[string]string{
			"status":  "ready",
			"service": a.name,
		})
	})
}

// Logger returns the application logger.
func (a *App) Logger() *logging.Logger {
	return a.logger
}

// Config returns the application configuration.
func (a *App) Config() config.AppConfig {
	return a.appConfig
}

// ServerConfig returns the server configuration.
func (a *App) ServerConfig() config.ServerConfig {
	return a.serverConfig
}

// Router returns the underlying chi router for advanced configuration.
func (a *App) Router() chi.Router {
	return a.router
}

// Use appends middleware to the router middleware stack.
func (a *App) Use(middlewares ...func(http.Handler) http.Handler) {
	a.router.Use(middlewares...)
}

// GET registers a handler for GET requests to the given pattern.
func (a *App) GET(pattern string, handler http.HandlerFunc) {
	a.router.Get(pattern, handler)
}

// POST registers a handler for POST requests to the given pattern.
func (a *App) POST(pattern string, handler http.HandlerFunc) {
	a.router.Post(pattern, handler)
}

// PUT registers a handler for PUT requests to the given pattern.
func (a *App) PUT(pattern string, handler http.HandlerFunc) {
	a.router.Put(pattern, handler)
}

// PATCH registers a handler for PATCH requests to the given pattern.
func (a *App) PATCH(pattern string, handler http.HandlerFunc) {
	a.router.Patch(pattern, handler)
}

// DELETE registers a handler for DELETE requests to the given pattern.
func (a *App) DELETE(pattern string, handler http.HandlerFunc) {
	a.router.Delete(pattern, handler)
}

// Route creates a new sub-router with the given pattern prefix.
//
// Example:
//
//	app.Route("/api/v1", func(r chi.Router) {
//	    r.Get("/users", listUsers)
//	    r.Post("/users", createUser)
//	})
func (a *App) Route(pattern string, fn func(r chi.Router)) {
	a.router.Route(pattern, fn)
}

// Mount attaches a sub-router or http.Handler at the given pattern.
func (a *App) Mount(pattern string, handler http.Handler) {
	a.router.Mount(pattern, handler)
}

// OnShutdown registers a function to be called during graceful shutdown.
// Functions are called in the order they were registered.
func (a *App) OnShutdown(fn func(context.Context) error) {
	a.onShutdown = append(a.onShutdown, fn)
}

// Run starts the HTTP server and blocks until shutdown.
// It handles graceful shutdown on SIGINT and SIGTERM signals.
func (a *App) Run() {
	addr := a.serverConfig.Addr()

	a.server = &http.Server{
		Addr:         addr,
		Handler:      a.router,
		ReadTimeout:  a.serverConfig.ReadTimeout,
		WriteTimeout: a.serverConfig.WriteTimeout,
		IdleTimeout:  a.serverConfig.IdleTimeout,
	}

	// Start server in a goroutine
	errChan := make(chan error, 1)
	go func() {
		a.logger.Info("starting server",
			"service", a.name,
			"address", addr,
			"environment", a.appConfig.Environment,
		)
		if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			errChan <- err
		}
	}()

	// Wait for shutdown signal or server error
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	select {
	case err := <-errChan:
		a.logger.Error("server error", "error", err)
		os.Exit(1)
	case sig := <-quit:
		a.logger.Info("received shutdown signal", "signal", sig.String())
	}

	// Graceful shutdown
	a.shutdown()
}

// shutdown performs graceful shutdown of the application.
func (a *App) shutdown() {
	// Create shutdown context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	a.logger.Info("shutting down server")

	// Shutdown HTTP server
	if err := a.server.Shutdown(ctx); err != nil {
		a.logger.Error("server shutdown error", "error", err)
	}

	// Run shutdown hooks
	for _, fn := range a.onShutdown {
		if err := fn(ctx); err != nil {
			a.logger.Error("shutdown hook error", "error", err)
		}
	}

	a.logger.Info("server stopped", "service", a.name)
}

// ListenAddr returns the address the server is configured to listen on.
func (a *App) ListenAddr() string {
	return a.serverConfig.Addr()
}

// EnableDocs adds OpenAPI endpoints to the application.
// It mounts:
//   - /openapi.json: The raw OpenAPI spec (consumed by CI for Slate doc generation)
//   - /docs: Redirects to the Slate documentation site at docs.{domain}
//
// During CI builds, Widdershins converts /openapi.json to Slate markdown,
// which is then built into static HTML and deployed to docs.{domain}.
//
// Example:
//
//	spec := openapi.NewOpenAPISpec("My Service", "1.0.0")
//	// ... add paths and schemas ...
//	application.EnableDocs(spec)
func (a *App) EnableDocs(spec *openapi.OpenAPISpec) {
	openapi.Mount(a.router, spec)
	a.logger.Info("API documentation enabled", "spec", "/openapi.json", "docs_redirect", "/docs")
}

// ServeHTTP implements http.Handler, allowing App to be used in tests.
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	a.router.ServeHTTP(w, r)
}
