package server

import (
	"database/sql"
	"fmt"
	"net/http"
	"strings"

	"github.com/a-h/templ/examples/integration-gin/gintemplrenderer"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/haydenhargreaves/Potion/internal/app/handlers"
	"github.com/haydenhargreaves/Potion/internal/app/service"
	domain "github.com/haydenhargreaves/Potion/internal/domain/server"
	"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
	"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"

	_ "github.com/lib/pq"
)

type Server struct {
	port   int
	Router *gin.Engine
	config cors.Config
	DB     *sql.DB
}

// Init initializes the server with the provided port. CORS settings are defined here.
// A pointer to a server object is returned which allows for method chaining.
func Init(port int) *Server {
	server := &Server{
		Router: gin.Default(),
		port:   port,
		config: cors.DefaultConfig(),
	}

	// Some stuff for templ rendering
	htmlRenderer := server.Router.HTMLRender
	server.Router.HTMLRender = &gintemplrenderer.HTMLTemplRenderer{FallbackHtmlRenderer: htmlRenderer}

	// Disable proxy warnings
	server.Router.SetTrustedProxies(nil)

	// Setup the CORS settings and active them
	server.config.AllowAllOrigins = true
	server.Router.Use(cors.New(server.config))

	return server
}

// Start starts the server on the port provided when the server was initialized
func (s *Server) Start() {
	s.Router.Run(fmt.Sprintf(":%d", s.port))
}

func (s *Server) Setup() *Server {
	// SETUP THE ENVIRONMENT CONFIGURATION
	cfg, err := domain.LoadEnvironment()
	if err != nil {
		panic(err.Error())
	}
	if cfg == nil {
		panic("Environment configuration is nil, crashing.")
	}

	if cfg.Environment == "dev" {
		gin.SetMode(gin.DebugMode)
	} else if cfg.Environment == "prod" {
		gin.SetMode(gin.ReleaseMode)
	} else {
		gin.SetMode(gin.TestMode)
	}

	// SETUP GOOGLE AUTH
	var (
		redirectUrl  string   = fmt.Sprintf("%s%s", cfg.Domain, domain.API_AUTH_CALLBACK)
		clientId     string   = cfg.GoogleClientId
		clientSecret string   = cfg.GoogleClientSecret
		scope        []string = []string{
			"https://www.googleapis.com/auth/userinfo.email",
			"https://www.googleapis.com/auth/userinfo.profile",
		}
	)

	// Setup Google OAuth
	auth.NewGoogleConfig(redirectUrl, clientId, clientSecret, scope)

	// SETUP DATABASE
	db, err := sql.Open("postgres", cfg.DatabaseUrl)
	if err != nil {
		panic("Could not connect to database: " + err.Error())
	}

	if err = db.Ping(); err != nil {
		panic("Error pinging database: " + err.Error())
	}

	s.DB = db

	// SETUP JWT
	jwtSecret := []byte(cfg.JwtSecret)

	// Initialize and inject dependencies
	userRepo := repository.NewUserRepository(s.DB)
	recipeRepo := repository.NewRecipeRepository(s.DB)
	engagementRepo := repository.NewEngagementRepository(s.DB)
	userService := service.NewUserService(userRepo)
	authService := service.NewAuthService(userRepo, jwtSecret)
	recipeService := service.NewRecipeService(recipeRepo, engagementRepo)
	engagementService := service.NewEngagementService(engagementRepo, recipeRepo)

	deps := &domain.InjectedDependencies{
		UserService:       userService,
		AuthService:       authService,
		RecipeService:     recipeService,
		EngagementService: engagementService,
		EnvironmentConfig: *cfg,
	}

	// Apply middleware
	s.Router.Use(RecoveryMiddleware())
	s.Router.Use(DepedencyInjectionMiddleware(deps))
	s.Router.Use(JwtAuthMiddleWare(jwtSecret))

	// Redirect index to home page: Update this as needed
	s.Router.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })

	// Wrap all routes with a version
	router_v1 := s.Router.Group(domain.VERSION)

	// Domain specific routers
	router_web := router_v1.Group(domain.WEB)
	router_api := router_v1.Group(domain.API)
	router_state := router_web.Group("state")

	// Static routes
	router_web.Static("/static", "./web/static")

	// API router endpoints
	router_api.GET("/", func(ctx *gin.Context) { ctx.JSON(200, gin.H{"message": "Server is active."}) })
	router_api.GET("/tmp", func(ctx *gin.Context) {
		deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
		ctx.JSON(200, gin.H{"config": deps.EnvironmentConfig})
	})

	// WEB router endpoints
	router_web.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
	router_web.GET("/login", handlers.LoginPage)
	router_web.GET("/home", handlers.HomePage)
	router_web.GET("/favorites", handlers.FavoritesPage)
	router_web.GET("/create", handlers.CreatePage)
	router_web.GET("/profile", handlers.ProfilePage)
	router_web.GET("/list", handlers.ListPage)
	router_web.GET("/recipe/:id", handlers.RecipePage)
	router_web.GET("/search", handlers.SearchPage)
	router_web.GET("/404", handlers.NotFoundPage)

	// WEB state endpoints
	router_state.POST("/tags", handlers.NewTag)
	router_state.POST("/tags/delete", handlers.DeleteTag)

	// Authentication
	router_api.GET("/auth/login", handlers.GoogleLogin)
	router_api.GET("/auth/callback", handlers.GoogleCallback)
	router_api.GET("/auth/logout", handlers.Logout)

	// Recipe endpoints
	router_api.POST("/recipe", handlers.CreateRecipe)
	router_api.POST("/recipe/search", handlers.SearchRecipes)
	router_api.POST("/recipe/search/favorites", handlers.SearchRecipesFavorites)
	router_api.GET("/user/recipes", handlers.GetUserRecipes)
	router_api.GET("/user/favorites", handlers.GetUserFavoriteRecipes)

	// Engagement endpoints
	router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe)
	router_api.POST("/engagement/share/:id", handlers.EngagementShareRecipe)
	router_api.POST("/engagement/favorite/:id", handlers.EngagementFavoriteRecipe)
	router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe)

	// Catch un-routed URLS
	s.Router.NoRoute(func(ctx *gin.Context) {
		path := ctx.Request.URL.Path

		// TODO: Use constants for errors?
		if strings.HasPrefix(path, domain.VERSION+domain.API) {
			ctx.JSON(http.StatusNotFound, gin.H{
				"status":  http.StatusNotFound,
				"error":   "API_NOT_FOUND",
				"message": "The request endpoint does not exist.",
				"path":    path,
			})
			return
		}

		ctx.Redirect(http.StatusSeeOther, domain.WEB_NOT_FOUND)
	})

	return s
}
