package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"tercul/internal/adapters/graphql"
	"tercul/internal/app"
	"tercul/internal/app/analytics"
	"tercul/internal/app/auth"
	"tercul/internal/app/author"
	"tercul/internal/app/authz"
	"tercul/internal/app/book"
	"tercul/internal/app/bookmark"
	"tercul/internal/app/category"
	"tercul/internal/app/collection"
	"tercul/internal/app/comment"
	"tercul/internal/app/contribution"
	"tercul/internal/app/like"
	"tercul/internal/app/localization"
	appsearch "tercul/internal/app/search"
	"tercul/internal/app/tag"
	"tercul/internal/app/translation"
	"tercul/internal/app/user"
	"tercul/internal/app/work"
	dbsql "tercul/internal/data/sql"
	"tercul/internal/jobs/linguistics"
	"tercul/internal/observability"
	platform_auth "tercul/internal/platform/auth"
	"tercul/internal/platform/config"
	"tercul/internal/platform/db"
	app_log "tercul/internal/platform/log"
	"tercul/internal/platform/search"
	"time"

	"github.com/pressly/goose/v3"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/weaviate/weaviate-go-client/v5/weaviate"
	"gorm.io/gorm"
)

// runMigrations applies database migrations using goose.
func runMigrations(gormDB *gorm.DB, migrationPath string) error {
	sqlDB, err := gormDB.DB()
	if err != nil {
		return err
	}

	if err := goose.SetDialect("postgres"); err != nil {
		return err
	}

	app_log.Info(fmt.Sprintf("Applying database migrations from %s", migrationPath))
	if err := goose.Up(sqlDB, migrationPath); err != nil {
		return err
	}
	app_log.Info("Database migrations applied successfully")
	return nil
}

// main is the entry point for the Tercul application.
func main() {
	// Load configuration from environment variables
	cfg, err := config.LoadConfig()
	if err != nil {
		log.Fatalf("cannot load config: %v", err)
	}

	// Initialize Bleve search
	search.InitBleve()

	// Initialize logger
	app_log.Init("tercul-api", cfg.Environment)
	obsLogger := observability.NewLogger("tercul-api", cfg.Environment)

	// Initialize OpenTelemetry Tracer Provider
	tp, err := observability.TracerProvider("tercul-api", cfg.Environment)
	if err != nil {
		app_log.Fatal(err, "Failed to initialize OpenTelemetry tracer")
	}
	defer func() {
		if err := tp.Shutdown(context.Background()); err != nil {
			app_log.Error(err, "Error shutting down tracer provider")
		}
	}()

	// Initialize Prometheus metrics
	reg := prometheus.NewRegistry()
	metrics := observability.NewMetrics(reg) // Metrics are registered automatically

	app_log.Info(fmt.Sprintf("Starting Tercul application in %s environment, version 1.0.0", cfg.Environment))

	// Initialize database connection
	database, err := db.InitDB(cfg, metrics)
	if err != nil {
		app_log.Fatal(err, "Failed to initialize database")
	}
	defer func() {
		if err := db.Close(database); err != nil {
			app_log.Error(err, "Error closing database")
		}
	}()

	if err := runMigrations(database, cfg.MigrationPath); err != nil {
		app_log.Fatal(err, "Failed to apply database migrations")
	}

	// Initialize Weaviate client
	weaviateCfg := weaviate.Config{
		Host:   cfg.WeaviateHost,
		Scheme: cfg.WeaviateScheme,
	}
	weaviateClient, err := weaviate.NewClient(weaviateCfg)
	if err != nil {
		app_log.Fatal(err, "Failed to create weaviate client")
	}

	// Create search client
	searchClient := search.NewWeaviateWrapper(weaviateClient)

	// Create repositories
	repos := dbsql.NewRepositories(database, cfg)

	// Create linguistics dependencies
	analysisRepo := linguistics.NewGORMAnalysisRepository(database)
	sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
	if err != nil {
		app_log.Fatal(err, "Failed to create sentiment provider")
	}

	// Create platform components
	jwtManager := platform_auth.NewJWTManager(cfg)

	// Create application services
	analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
	localizationService := localization.NewService(repos.Localization)
	searchService := appsearch.NewService(searchClient, localizationService)
	authzService := authz.NewService(repos.Work, repos.Author, repos.User, repos.Translation)
	authorService := author.NewService(repos.Author)
	bookService := book.NewService(repos.Book, authzService)
	bookmarkService := bookmark.NewService(repos.Bookmark, analyticsService)
	categoryService := category.NewService(repos.Category)
	collectionService := collection.NewService(repos.Collection)
	commentService := comment.NewService(repos.Comment, authzService, analyticsService)
	contributionCommands := contribution.NewCommands(repos.Contribution, authzService)
	contributionService := contribution.NewService(contributionCommands)
	likeService := like.NewService(repos.Like, analyticsService)
	tagService := tag.NewService(repos.Tag)
	translationService := translation.NewService(repos.Translation, authzService)
	userService := user.NewService(repos.User, authzService, repos.UserProfile)
	authService := auth.NewService(repos.User, jwtManager)
	workService := work.NewService(repos.Work, repos.Author, repos.User, searchClient, authzService, analyticsService)

	// Create application
	application := app.NewApplication(
		authorService,
		bookService,
		bookmarkService,
		categoryService,
		collectionService,
		commentService,
		contributionService,
		likeService,
		tagService,
		translationService,
		userService,
		localizationService,
		authService,
		authzService,
		workService,
		searchService,
		analyticsService,
	)

	// Create GraphQL server
	resolver := &graphql.Resolver{
		App: application,
	}

	// Create the consolidated API server with all routes.
	apiHandler := NewAPIServer(resolver, jwtManager, metrics, obsLogger, reg)

	// Create the main HTTP server.
	mainServer := &http.Server{
		Addr:    cfg.ServerPort,
		Handler: apiHandler,
	}
	app_log.Info(fmt.Sprintf("API server listening on port %s", cfg.ServerPort))

	// Start the main server in a goroutine
	go func() {
		if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			app_log.Fatal(err, "Failed to start server")
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	app_log.Info("Shutting down server...")

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

	if err := mainServer.Shutdown(ctx); err != nil {
		app_log.Error(err, "Server forced to shutdown")
	}

	app_log.Info("Server shut down successfully")
}
